From 360aba3e8fbd23a02da9dbf4fd050b7d3132f1be Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 21 Apr 2026 23:20:48 +0100 Subject: [PATCH 01/77] 6.5.0.16 --- simplex-chat.cabal | 2 +- src/Simplex/Chat/Remote.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 320e51c8af..e777c6eeba 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.5.0.15 +version: 6.5.0.16 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index 1aadfb507e..cfe8e944a5 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -79,7 +79,7 @@ minRemoteCtrlVersion = AppVersion [6, 5, 0, 12] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [6, 4, 6, 0] +minRemoteHostVersion = AppVersion [6, 5, 0, 12] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version From b40c5a4f3e91203cbf57cdf750c485de771b3165 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 22 Apr 2026 08:35:13 +0100 Subject: [PATCH 02/77] ui: better icon for channel filter, remove separate copy button from link on iOS (#6856) --- .../Shared/Views/ChatList/ChatListView.swift | 28 +++++++++---------- .../Shared/Views/NewChat/NewChatView.swift | 8 ------ .../common/views/chatlist/ChatListView.kt | 26 ++++++++--------- 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 967fedf293..3080a2f403 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -839,11 +839,11 @@ struct TagsView: View { nil } let active = tag == selectedPresetTag - let (icon, text) = presetTagLabel(tag: tag, active: active) + let (icon, menuIcon, text) = presetTagLabel(tag: tag, active: active) let color: Color = active ? .accentColor : .secondary HStack(spacing: 4) { - Image(systemName: icon) + Image(systemName: menuIcon ?? icon) .foregroundColor(color) ZStack { Text(text).fontWeight(.semibold).foregroundColor(.clear) @@ -886,9 +886,9 @@ struct TagsView: View { Button { setActiveFilter(filter: .presetTag(tag)) } label: { - let (systemName, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag) + let (icon, _, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag) HStack { - Image(systemName: systemName) + Image(systemName: icon) Text(text) } } @@ -896,8 +896,8 @@ struct TagsView: View { } } label: { if let tag = selectedPresetTag, tag.сollapse { - let (systemName, _) = presetTagLabel(tag: tag, active: true) - Image(systemName: systemName) + let (icon, menuIcon, _) = presetTagLabel(tag: tag, active: true) + Image(systemName: menuIcon ?? icon) .foregroundColor(.accentColor) } else { Image(systemName: "list.bullet") @@ -907,15 +907,15 @@ struct TagsView: View { .frame(minWidth: 28) } - private func presetTagLabel(tag: PresetTag, active: Bool) -> (String, LocalizedStringKey) { + private func presetTagLabel(tag: PresetTag, active: Bool) -> (item: String, menu: String?, label: LocalizedStringKey) { switch tag { - case .groupReports: (active ? "flag.fill" : "flag", "Reports") - case .favorites: (active ? "star.fill" : "star", "Favorites") - case .contacts: (active ? "person.fill" : "person", "Contacts") - case .groups: (active ? "person.2.fill" : "person.2", "Groups") - case .channels: (active ? "antenna.radiowaves.left.and.right.circle.fill" : "antenna.radiowaves.left.and.right.circle", "Channels") - case .business: (active ? "briefcase.fill" : "briefcase", "Businesses") - case .notes: (active ? "folder.fill" : "folder", "Notes") + case .groupReports: (item: active ? "flag.fill" : "flag", menu: nil, label: "Reports") + case .favorites: (item: active ? "star.fill" : "star", menu: nil, label: "Favorites") + case .contacts: (item: active ? "person.fill" : "person", menu: nil, label: "Contacts") + case .groups: (item: active ? "person.2.fill" : "person.2", menu: nil, label: "Groups") + case .channels: (item: active ? "antenna.radiowaves.left.and.right.circle.fill" : "antenna.radiowaves.left.and.right", menu: "antenna.radiowaves.left.and.right", label: "Channels") + case .business: (item: active ? "briefcase.fill" : "briefcase", menu: nil, label: "Businesses") + case .notes: (item: active ? "folder.fill" : "folder", menu: nil, label: "Notes") } } diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index fab8f8a143..9bcc326a66 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -338,14 +338,6 @@ private struct InviteView: View { HStack(spacing: 8) { let link = connLinkInvitation.simplexChatUri(short: showShortLink) linkTextView(link) - Button { - UIPasteboard.general.string = link - setInvitationUsed() - } label: { - Image(systemName: "doc.on.doc") - .padding(.top, -7) - .padding(.horizontal, 8) - } Button { showShareSheet(items: [link]) setInvitationUsed() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 8f836c3c29..663b42413f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -1144,7 +1144,7 @@ private fun ExpandedTagFilterView(tag: PresetTagKind) { is ActiveFilter.PresetTag -> af.tag == tag else -> false } - val (icon, text) = presetTagLabel(tag, active) + val (icon, menuIcon, text) = presetTagLabel(tag, active) val color = if (active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary Row( @@ -1164,7 +1164,7 @@ private fun ExpandedTagFilterView(tag: PresetTagKind) { horizontalArrangement = Arrangement.Center ) { Icon( - painterResource(icon), + painterResource(menuIcon ?: icon), stringResource(text), Modifier.size(18.sp.toDp()), tint = color @@ -1206,9 +1206,9 @@ private fun CollapsedTagsFilterView(searchText: MutableState) { contentAlignment = Alignment.Center ) { if (selectedPresetTag != null) { - val (icon, text) = presetTagLabel(selectedPresetTag, true) + val (icon, menuIcon, text) = presetTagLabel(selectedPresetTag, true) Icon( - painterResource(icon), + painterResource(menuIcon ?: icon), stringResource(text), Modifier.size(18.sp.toDp()), tint = MaterialTheme.colors.primary @@ -1254,7 +1254,7 @@ fun ItemPresetFilterAction( showMenu: MutableState, onCloseMenuAction: MutableState<(() -> Unit)> ) { - val (icon, text) = presetTagLabel(presetTag, active) + val (icon, _, text) = presetTagLabel(presetTag, active) ItemAction( stringResource(text), painterResource(icon), @@ -1336,15 +1336,15 @@ fun presetTagMatchesChat(tag: PresetTagKind, chatInfo: ChatInfo, chatStats: Chat } } -private fun presetTagLabel(tag: PresetTagKind, active: Boolean): Pair = +private fun presetTagLabel(tag: PresetTagKind, active: Boolean): Triple = when (tag) { - PresetTagKind.GROUP_REPORTS -> (if (active) MR.images.ic_flag_filled else MR.images.ic_flag) to MR.strings.chat_list_group_reports - PresetTagKind.FAVORITES -> (if (active) MR.images.ic_star_filled else MR.images.ic_star) to MR.strings.chat_list_favorites - PresetTagKind.CONTACTS -> (if (active) MR.images.ic_person_filled else MR.images.ic_person) to MR.strings.chat_list_contacts - PresetTagKind.GROUPS -> (if (active) MR.images.ic_group_filled else MR.images.ic_group) to MR.strings.chat_list_groups - PresetTagKind.CHANNELS -> (if (active) MR.images.ic_bigtop_updates_circle_filled else MR.images.ic_bigtop_updates) to MR.strings.chat_list_channels - PresetTagKind.BUSINESS -> (if (active) MR.images.ic_work_filled else MR.images.ic_work) to MR.strings.chat_list_businesses - PresetTagKind.NOTES -> (if (active) MR.images.ic_folder_closed_filled else MR.images.ic_folder_closed) to MR.strings.chat_list_notes + PresetTagKind.GROUP_REPORTS -> Triple(if (active) MR.images.ic_flag_filled else MR.images.ic_flag, null, MR.strings.chat_list_group_reports) + PresetTagKind.FAVORITES -> Triple(if (active) MR.images.ic_star_filled else MR.images.ic_star, null, MR.strings.chat_list_favorites) + PresetTagKind.CONTACTS -> Triple(if (active) MR.images.ic_person_filled else MR.images.ic_person, null, MR.strings.chat_list_contacts) + PresetTagKind.GROUPS -> Triple(if (active) MR.images.ic_group_filled else MR.images.ic_group, null, MR.strings.chat_list_groups) + PresetTagKind.CHANNELS -> Triple(if (active) MR.images.ic_bigtop_updates_circle_filled else MR.images.ic_bigtop_updates, MR.images.ic_bigtop_updates, MR.strings.chat_list_channels) + PresetTagKind.BUSINESS -> Triple(if (active) MR.images.ic_work_filled else MR.images.ic_work, null, MR.strings.chat_list_businesses) + PresetTagKind.NOTES -> Triple(if (active) MR.images.ic_folder_closed_filled else MR.images.ic_folder_closed, null, MR.strings.chat_list_notes) } private fun presetCanBeCollapsed(tag: PresetTagKind): Boolean = when (tag) { From 858e955afd37e817e4f69b6730dfd5264debc6e3 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:03:15 +0000 Subject: [PATCH 03/77] ci: cancel stale runs, filter PR paths (#6857) --- .github/workflows/build.yml | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3ef9fa088..7c6cb7a3a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,17 +10,25 @@ on: - "!*-fdroid" - "!*-armv7a" pull_request: - paths-ignore: - - "apps/ios" - - "apps/multiplatform" - - "blog" - - "docs" - - "fastlane" - - "images" - - "packages" - - "website" - - "README.md" - - "PRIVACY.md" + paths: + - "src/**" + - "apps/simplex-chat/**" + - "apps/simplex-bot/**" + - "apps/simplex-bot-advanced/**" + - "apps/simplex-broadcast-bot/**" + - "apps/simplex-directory-service/**" + - "tests/**" + - "bots/src/**" + - "simplex-chat.cabal" + - "cabal.project" + - "Dockerfile*" + - "scripts/ci/**" + - "scripts/desktop/**" + - ".github/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') }} # This workflow uses custom actions (prepare-build and prepare-release) defined in: # From 55fb7299460d47a461cb71f04726e3868c5b9436 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:37:02 +0000 Subject: [PATCH 04/77] ios: remove copy button from onboarding address view (#6858) --- apps/ios/Shared/Views/UserSettings/UserAddressView.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index 4df58f8b0c..e22042fa24 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -215,11 +215,6 @@ struct UserAddressView: View { HStack(spacing: 8) { let link = userAddress.connLinkContact.simplexChatUri(short: showShortLink) linkTextView(link) - Button { UIPasteboard.general.string = link } label: { - Image(systemName: "doc.on.doc") - .padding(.top, -7) - .padding(.horizontal, 8) - } Button { showShareSheet(items: [link]) } label: { Image(systemName: "square.and.arrow.up") .padding(.top, -7) From 75d990654b007fe4eb889aa10f9a668481e79fc2 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 22 Apr 2026 10:21:23 +0100 Subject: [PATCH 05/77] ui: OKLCH colors for gradients in onboarding cards (#6859) * ui: OKLCH colors for gradients in onboarding cards * add wide gamut to manifest --- .../Views/NewChat/OnboardingCards.swift | 16 ++--- apps/ios/SimpleXChat/Theme/Color.swift | 49 +++++++++++++++ .../android/src/main/AndroidManifest.xml | 1 + .../chat/simplex/common/ui/theme/Color.kt | 62 ++++++++++++++++--- .../common/views/newchat/OnboardingCards.kt | 16 ++--- 5 files changed, 121 insertions(+), 23 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/OnboardingCards.swift b/apps/ios/Shared/Views/NewChat/OnboardingCards.swift index 913fdf5577..dc0cf54afe 100644 --- a/apps/ios/Shared/Views/NewChat/OnboardingCards.swift +++ b/apps/ios/Shared/Views/NewChat/OnboardingCards.swift @@ -23,17 +23,17 @@ struct OnboardingCardView: View { let action: () -> Void static let lightStops: [Gradient.Stop] = [ - .init(color: Color(red: 0.824, green: 0.910, blue: 1.0), location: 0.0), - .init(color: Color(red: 0.800, green: 0.914, blue: 1.0), location: 0.5), - .init(color: Color(red: 0.875, green: 1.0, blue: 1.0), location: 0.9), - .init(color: Color(red: 1.0, green: 0.988, blue: 0.918), location: 1.0) + .init(color: oklch(0.9219, 0.0431, 249.4), location: 0.0), + .init(color: oklch(0.9198, 0.0471, 240.7), location: 0.5), + .init(color: oklch(0.9772, 0.0358, 196.6), location: 0.9), + .init(color: oklch(0.9886, 0.0272, 99.1), location: 1.0) ] static let darkStops: [Gradient.Stop] = [ - .init(color: Color(red: 0.016, green: 0.039, blue: 0.141), location: 0.4), - .init(color: Color(red: 0.220, green: 0.329, blue: 0.671), location: 0.72), - .init(color: Color(red: 0.659, green: 0.929, blue: 0.953), location: 0.9), - .init(color: Color(red: 1.0, green: 0.965, blue: 0.878), location: 1.0) + .init(color: oklch(0.1578, 0.0609, 267.3), location: 0.4), + .init(color: oklch(0.4729, 0.1574, 267.3), location: 0.72), + .init(color: oklch(0.9024, 0.0760, 202.8), location: 0.9), + .init(color: oklch(0.9744, 0.0370, 88.4), location: 1.0) ] static let gradientAngle: Double = 80.0 * .pi / 180.0 diff --git a/apps/ios/SimpleXChat/Theme/Color.swift b/apps/ios/SimpleXChat/Theme/Color.swift index f307eaa5aa..86eefa4482 100644 --- a/apps/ios/SimpleXChat/Theme/Color.swift +++ b/apps/ios/SimpleXChat/Theme/Color.swift @@ -33,6 +33,55 @@ let HighOrLowlight = Color(139, 135, 134, a: 255) //let FileLight = Color(183, 190, 199, a: 255) //let FileDark = Color(101, 101, 106, a: 255) +// Create a Display P3 Color from oklch components. H in degrees +public func oklch(_ L: Double, _ C: Double, _ H: Double, alpha: Double = 1.0) -> Color { + let hRad = H * .pi / 180.0 + let cosH = cos(hRad) + let sinH = sin(hRad) + + func linearP3(C: Double) -> (Double, Double, Double) { + let a = C * cosH + let b = C * sinH + // oklab → LMS (Ottosson 2021) + let l_ = L + 0.3963377774 * a + 0.2158037573 * b + let m_ = L - 0.1055613458 * a - 0.0638541728 * b + let s_ = L - 0.0894841775 * a - 1.2914855480 * b + let l = l_ * l_ * l_ + let m = m_ * m_ * m_ + let s = s_ * s_ * s_ + // LMS → linear Display P3 (direct, no sRGB clamping) + return ( + 3.1281105148 * l - 2.2570749853 * m + 0.1293047593 * s, + -1.0911282009 * l + 2.4132668169 * m - 0.3221681599 * s, + -0.0260136845 * l - 0.5080276339 * m + 1.5333166364 * s + ) + } + + func inGamut(_ r: Double, _ g: Double, _ b: Double) -> Bool { + r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1 + } + + // linear P3 → gamma-encoded P3 (same transfer function as sRGB) + func gammaEncode(_ x: Double) -> Double { + x >= 0.0031308 + ? 1.055 * pow(min(x, 1.0), 1.0 / 2.4) - 0.055 + : 12.92 * max(x, 0) + } + + var (r, g, b) = linearP3(C: C) + if !inGamut(r, g, b) { + var lo = 0.0, hi = C + while hi - lo > 1e-5 { + let mid = (lo + hi) / 2 + let (mr, mg, mb) = linearP3(C: mid) + if inGamut(mr, mg, mb) { lo = mid; r = mr; g = mg; b = mb } + else { hi = mid } + } + } + + return Color(.displayP3, red: gammaEncode(r), green: gammaEncode(g), blue: gammaEncode(b), opacity: alpha) +} + extension Color { public init(_ argb: Int64) { let a = Double((argb & 0xFF000000) >> 24) / 255.0 diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index d6059896a5..9e059afa14 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -51,6 +51,7 @@ { + val a = c * cosH + val b = c * sinH + // oklab → LMS (Ottosson 2021) + val l_ = L + 0.3963377774f * a + 0.2158037573f * b + val m_ = L - 0.1055613458f * a - 0.0638541728f * b + val s_ = L - 0.0894841775f * a - 1.2914855480f * b + val l = l_ * l_ * l_ + val m = m_ * m_ * m_ + val s = s_ * s_ * s_ + // LMS → linear Display P3 + return Triple( + 3.1281105148f * l - 2.2570749853f * m + 0.1293047593f * s, + -1.0911282009f * l + 2.4132668169f * m - 0.3221681599f * s, + -0.0260136845f * l - 0.5080276339f * m + 1.5333166364f * s + ) + } + + fun inGamut(r: Float, g: Float, b: Float) = r in 0f..1f && g in 0f..1f && b in 0f..1f + + // linear P3 → gamma-encoded P3 (same transfer function as sRGB) + fun gammaEncode(x: Float): Float = + if (x >= 0.0031308f) 1.055f * min(x, 1f).pow(1f / 2.4f) - 0.055f + else 12.92f * max(x, 0f) + + var (r, g, b) = linearP3(C) + if (!inGamut(r, g, b)) { + var lo = 0f; var hi = C + while (hi - lo > 1e-5f) { + val mid = (lo + hi) / 2 + val (mr, mg, mb) = linearP3(mid) + if (inGamut(mr, mg, mb)) { lo = mid; r = mr; g = mg; b = mb } + else hi = mid + } + } + + return Color( + red = gammaEncode(r), + green = gammaEncode(g), + blue = gammaEncode(b), + alpha = alpha, + colorSpace = ColorSpaces.DisplayP3 + ) +} -val Purple200 = Color(0xFFBB86FC) -val Purple500 = Color(0xFF6200EE) -val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) -val Gray = Color(0x22222222) val Indigo = Color(0xFF9966FF) val SimplexBlue = Color(0, 136, 255, 255) // If this value changes also need to update #0088ff in string resource files val SimplexGreen = Color(77, 218, 103, 255) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt index 98954eb74f..d287353ccc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt @@ -89,17 +89,17 @@ internal fun gradientPoints(aspectRatio: Float, scale: Float): GradientEndpoints } internal val lightStops = arrayOf( - 0.0f to Color(0xFFd2e8ff), - 0.5f to Color(0xFFcce9ff), - 0.9f to Color(0xFFdfffff), - 1.0f to Color(0xFFfffcea) + 0.0f to oklch(0.9219f, 0.0431f, 249.4f), + 0.5f to oklch(0.9198f, 0.0471f, 240.7f), + 0.9f to oklch(0.9772f, 0.0358f, 196.6f), + 1.0f to oklch(0.9886f, 0.0272f, 99.1f) ) internal val darkStops = arrayOf( - 0.4f to Color(0xFF040a24), - 0.72f to Color(0xFF3854ab), - 0.9f to Color(0xFFa8edf3), - 1.0f to Color(0xFFfff6e0) + 0.4f to oklch(0.1578f, 0.0609f, 267.3f), + 0.72f to oklch(0.4729f, 0.1574f, 267.3f), + 0.9f to oklch(0.9024f, 0.0760f, 202.8f), + 1.0f to oklch(0.9744f, 0.0370f, 88.4f) ) private fun Modifier.maxHeightByWidthRatio(ratio: Float) = layout { measurable, constraints -> From 63ea3d356556bc9eebdb20e1afeaf348630a486e Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 22 Apr 2026 11:36:33 +0100 Subject: [PATCH 06/77] ui: gradient colors (#6861) * ui: gradient colors * function --- .../Views/NewChat/OnboardingCards.swift | 2 + .../chat/simplex/common/ui/theme/Color.kt | 50 +------------------ .../common/views/newchat/OnboardingCards.kt | 3 ++ 3 files changed, 6 insertions(+), 49 deletions(-) diff --git a/apps/ios/Shared/Views/NewChat/OnboardingCards.swift b/apps/ios/Shared/Views/NewChat/OnboardingCards.swift index dc0cf54afe..76bba9447e 100644 --- a/apps/ios/Shared/Views/NewChat/OnboardingCards.swift +++ b/apps/ios/Shared/Views/NewChat/OnboardingCards.swift @@ -26,6 +26,7 @@ struct OnboardingCardView: View { .init(color: oklch(0.9219, 0.0431, 249.4), location: 0.0), .init(color: oklch(0.9198, 0.0471, 240.7), location: 0.5), .init(color: oklch(0.9772, 0.0358, 196.6), location: 0.9), + .init(color: oklch(0.9829, 0.0104, 70.0), location: 0.95), .init(color: oklch(0.9886, 0.0272, 99.1), location: 1.0) ] @@ -33,6 +34,7 @@ struct OnboardingCardView: View { .init(color: oklch(0.1578, 0.0609, 267.3), location: 0.4), .init(color: oklch(0.4729, 0.1574, 267.3), location: 0.72), .init(color: oklch(0.9024, 0.0760, 202.8), location: 0.9), + .init(color: oklch(0.9384, 0.0354, 65.0), location: 0.95), .init(color: oklch(0.9744, 0.0370, 88.4), location: 1.0) ] diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt index ac124a94c9..3df780ae24 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt @@ -6,59 +6,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.colorspace.ColorSpaces import kotlin.math.cos -import kotlin.math.max -import kotlin.math.min -import kotlin.math.pow import kotlin.math.sin fun oklch(L: Float, C: Float, H: Float, alpha: Float = 1f): Color { val hRad = H * (Math.PI.toFloat() / 180f) - val cosH = cos(hRad) - val sinH = sin(hRad) - - fun linearP3(c: Float): Triple { - val a = c * cosH - val b = c * sinH - // oklab → LMS (Ottosson 2021) - val l_ = L + 0.3963377774f * a + 0.2158037573f * b - val m_ = L - 0.1055613458f * a - 0.0638541728f * b - val s_ = L - 0.0894841775f * a - 1.2914855480f * b - val l = l_ * l_ * l_ - val m = m_ * m_ * m_ - val s = s_ * s_ * s_ - // LMS → linear Display P3 - return Triple( - 3.1281105148f * l - 2.2570749853f * m + 0.1293047593f * s, - -1.0911282009f * l + 2.4132668169f * m - 0.3221681599f * s, - -0.0260136845f * l - 0.5080276339f * m + 1.5333166364f * s - ) - } - - fun inGamut(r: Float, g: Float, b: Float) = r in 0f..1f && g in 0f..1f && b in 0f..1f - - // linear P3 → gamma-encoded P3 (same transfer function as sRGB) - fun gammaEncode(x: Float): Float = - if (x >= 0.0031308f) 1.055f * min(x, 1f).pow(1f / 2.4f) - 0.055f - else 12.92f * max(x, 0f) - - var (r, g, b) = linearP3(C) - if (!inGamut(r, g, b)) { - var lo = 0f; var hi = C - while (hi - lo > 1e-5f) { - val mid = (lo + hi) / 2 - val (mr, mg, mb) = linearP3(mid) - if (inGamut(mr, mg, mb)) { lo = mid; r = mr; g = mg; b = mb } - else hi = mid - } - } - - return Color( - red = gammaEncode(r), - green = gammaEncode(g), - blue = gammaEncode(b), - alpha = alpha, - colorSpace = ColorSpaces.DisplayP3 - ) + return Color(L, C * cos(hRad), C * sin(hRad), alpha, ColorSpaces.Oklab) } val Indigo = Color(0xFF9966FF) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt index d287353ccc..26007c74af 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/OnboardingCards.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.colorspace.ColorSpaces import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.layout @@ -92,6 +93,7 @@ internal val lightStops = arrayOf( 0.0f to oklch(0.9219f, 0.0431f, 249.4f), 0.5f to oklch(0.9198f, 0.0471f, 240.7f), 0.9f to oklch(0.9772f, 0.0358f, 196.6f), + 0.95f to oklch(0.9829f, 0.0104f, 70.0f), 1.0f to oklch(0.9886f, 0.0272f, 99.1f) ) @@ -99,6 +101,7 @@ internal val darkStops = arrayOf( 0.4f to oklch(0.1578f, 0.0609f, 267.3f), 0.72f to oklch(0.4729f, 0.1574f, 267.3f), 0.9f to oklch(0.9024f, 0.0760f, 202.8f), + 0.95f to oklch(0.9384f, 0.0354f, 65.0f), 1.0f to oklch(0.9744f, 0.0370f, 88.4f) ) From 01e33efcf46dc2763d82ba0eead1807d8b1520f4 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 22 Apr 2026 11:37:15 +0100 Subject: [PATCH 07/77] ios: update core library --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index ac9bb3ff43..d212219d43 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -182,8 +182,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -558,8 +558,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -727,8 +727,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -814,8 +814,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.16-45m1zumjYj2Eu6IsS525uz.a */, ); path = Libraries; sourceTree = ""; From d514e93b70799d993db5e9b582632c0c797a81b9 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:11:56 +0100 Subject: [PATCH 08/77] 6.5-beta.10: ios 327 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index d212219d43..0de696d430 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -2065,7 +2065,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2115,7 +2115,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2157,7 +2157,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2177,7 +2177,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2202,7 +2202,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2239,7 +2239,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2276,7 +2276,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2327,7 +2327,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2381,7 +2381,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2415,7 +2415,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 326; + CURRENT_PROJECT_VERSION = 327; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; From c9ef073f021b4a6b964b571cbb73bacde85fd7f3 Mon Sep 17 00:00:00 2001 From: SimpleX Chat Date: Wed, 22 Apr 2026 10:33:27 +0000 Subject: [PATCH 09/77] 6.5-beta.10: android 342, desktop 137 --- apps/multiplatform/gradle.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 45129a0d54..e47d493741 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,13 +24,13 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.5-beta.9 -android.version_code=341 +android.version_name=6.5-beta.10 +android.version_code=342 android.bundle=false -desktop.version_name=6.5-beta.9 -desktop.version_code=136 +desktop.version_name=6.5-beta.10 +desktop.version_code=137 kotlin.version=2.1.20 gradle.plugin.version=8.7.0 From b967cacc114686115247e5945fd41efe51b4bde6 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 22 Apr 2026 19:40:11 +0100 Subject: [PATCH 10/77] ios: export localizations --- .../bg.xcloc/Localized Contents/bg.xliff | 305 ++++++++++++-- .../cs.xcloc/Localized Contents/cs.xliff | 305 ++++++++++++-- .../de.xcloc/Localized Contents/de.xliff | 306 ++++++++++++-- .../en.xcloc/Localized Contents/en.xliff | 375 ++++++++++++++++-- .../es.xcloc/Localized Contents/es.xliff | 306 ++++++++++++-- .../fi.xcloc/Localized Contents/fi.xliff | 305 ++++++++++++-- .../fr.xcloc/Localized Contents/fr.xliff | 306 ++++++++++++-- .../hu.xcloc/Localized Contents/hu.xliff | 306 ++++++++++++-- .../it.xcloc/Localized Contents/it.xliff | 306 ++++++++++++-- .../ja.xcloc/Localized Contents/ja.xliff | 305 ++++++++++++-- .../nl.xcloc/Localized Contents/nl.xliff | 306 ++++++++++++-- .../pl.xcloc/Localized Contents/pl.xliff | 306 ++++++++++++-- .../ru.xcloc/Localized Contents/ru.xliff | 306 ++++++++++++-- .../th.xcloc/Localized Contents/th.xliff | 305 ++++++++++++-- .../tr.xcloc/Localized Contents/tr.xliff | 306 ++++++++++++-- .../uk.xcloc/Localized Contents/uk.xliff | 306 ++++++++++++-- .../Localized Contents/zh-Hans.xliff | 306 ++++++++++++-- 17 files changed, 4675 insertions(+), 591 deletions(-) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 3cf65c8b54..0c916ee30b 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -185,9 +185,20 @@ %d месеца time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u пропуснати съобщения. No comment provided by engineer. + + (from owner) + chat link info line + (new) (ново) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (това устройство v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - и още! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Още няколко неща No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Нов контакт @@ -670,9 +715,8 @@ swipe action Активни връзки No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Добавени сървъри за съобщения No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Допълнителен акцент @@ -859,6 +907,14 @@ swipe action Всички профили profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Всички доклади за нарушения ще бъдат архивирани за вас. @@ -1419,7 +1475,7 @@ swipe action Business address Бизнес адрес - No comment provided by engineer. + chat link info line Business chats @@ -1610,12 +1666,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1630,6 +1695,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1642,6 +1711,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Чат @@ -2060,6 +2133,10 @@ This is your own one-time link! Свърване чрез линк new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Свързване чрез еднократен линк за връзка @@ -2070,6 +2147,10 @@ This is your own one-time link! Свързване с %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Свързан @@ -2138,7 +2219,7 @@ This is your own one-time link! Connection error (AUTH) Грешка при свързване (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2193,6 +2274,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows Контактът позволява @@ -2285,11 +2370,6 @@ This is your own one-time link! Поправи име на %@? alert message - - Create - Създаване - No comment provided by engineer. - Create 1-time link Създаване на еднократна препратка @@ -2361,6 +2441,10 @@ This is your own one-time link! Създай своя профил No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2914,6 +2998,10 @@ alert button Личните съобщения между членовете са забранени в тази група. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Деактивиране (запазване на промените) @@ -3106,6 +3194,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Редактирай @@ -3127,7 +3219,7 @@ chat item action Enable Активирай - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3181,6 +3273,10 @@ chat item action Активирай незабавни известия? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Активирай заключване @@ -3355,7 +3451,7 @@ chat item action Error Грешка при свързване със сървъра - No comment provided by engineer. + conn error description Error aborting address change @@ -3674,6 +3770,10 @@ chat item action Грешка при настройването на потвърждениeто за доставка!! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Грешка при стартиране на чата @@ -4015,6 +4115,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -4207,7 +4311,7 @@ Error: %2$@ Group link Групов линк - No comment provided by engineer. + chat link info line Group links @@ -4606,7 +4710,7 @@ More improvements are coming soon! Invalid connection link Невалиден линк за връзка - No comment provided by engineer. + conn error description Invalid display name! @@ -4670,6 +4774,10 @@ More improvements are coming soon! Покани членове No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4863,6 +4971,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Нека да поговорим в SimpleX Chat @@ -4883,6 +4995,10 @@ This is your link for group %@! Свържете мобилни и настолни приложения! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Настройки на запомнени настолни устройства @@ -5364,6 +5480,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5391,6 +5511,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Нов kод за достъп @@ -5482,6 +5606,10 @@ This is your link for group %@! Не No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Приложението няма kод за достъп @@ -5627,6 +5755,10 @@ This is your link for group %@! Първата платформа без никакви потребителски идентификатори – поверителна по дизайн. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5682,7 +5814,7 @@ This is your link for group %@! OK ОК - No comment provided by engineer. + alert button Off @@ -5706,6 +5838,10 @@ new chat action Линк за еднократна покана No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5725,6 +5861,10 @@ Requires compatible VPN. Няма се използват Onion хостове. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5933,6 +6073,10 @@ Requires compatible VPN. Или сигурно споделете този линк към файла No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Или покажи този код @@ -5942,6 +6086,10 @@ Requires compatible VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5964,6 +6112,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING бройка @@ -6018,6 +6170,10 @@ Requires compatible VPN. Постави изображение No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Поставете линк, за да се свържете! @@ -6206,6 +6362,10 @@ Error: %@ Поверителността преосмислена No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. No comment provided by engineer. @@ -6272,9 +6432,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Актуализацията на профила ще бъде изпратена до вашите контакти. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6371,6 +6530,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push известия @@ -6590,6 +6753,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Реле сървър се използва само ако е необходимо. Друга страна може да наблюдава вашия IP адрес. @@ -6604,6 +6771,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Премахване @@ -6860,6 +7031,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6902,6 +7077,10 @@ chat item action Запази и уведоми членовете на групата No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -7086,6 +7265,10 @@ chat item action Код за сигурност No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Избери @@ -7205,6 +7388,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Изпрати от галерия или персонализирани клавиатури. @@ -7229,6 +7416,10 @@ chat item action Подателят може да е изтрил заявката за връзка. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Изпращането на потвърждениe за доставка ще бъде активирано за всички контакти във всички видими чат профили. @@ -7497,11 +7688,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Сподели адреса с контактите? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7536,9 +7730,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Сподели с контактите + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7951,6 +8148,10 @@ Relay address was used to set up this relay for the channel. Направи снимка No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7963,10 +8164,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8000,6 +8197,10 @@ Relay address was used to set up this relay for the channel. Докосни за инкогнито вход No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Докосни за поставяне на линк за връзка @@ -8096,6 +8297,10 @@ It can happen because of some bug or when the connection is compromised.QR кодът, който сканирахте, не е SimpleX линк за връзка. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -8312,6 +8517,10 @@ It can happen because of some bug or when the connection is compromised.Скриване на нежелани съобщения. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection За да направите нова връзка @@ -8570,7 +8779,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -8768,6 +8977,10 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8914,6 +9127,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Изчакване на настолно устройство… @@ -8952,6 +9169,10 @@ To connect, please ask your contact to create another connection link and check Предупреждение: Може да загубите някои данни! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE сървъри @@ -9511,6 +9732,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Вашият автоматично генериран профил @@ -9700,6 +9925,10 @@ marked deleted chat item preview text повикване… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -10332,6 +10561,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address премахнат адрес за контакт @@ -10636,6 +10869,10 @@ last received msg: %2$@ \~зачеркнат~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index e7d0fc2d4b..b786562a4f 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -185,9 +185,20 @@ %d měsíce time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u zpráv přeskočeno. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nový) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (toto zařízení v%@) @@ -445,6 +480,12 @@ channel relay bar progress with errors - a více! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -542,6 +583,10 @@ time interval Ještě pár věcí No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Nový kontakt @@ -665,9 +710,8 @@ swipe action Aktivní spojení No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -732,6 +776,10 @@ swipe action Přidané servery zpráv No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Další zbarvení @@ -848,6 +896,14 @@ swipe action Všechny profily profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -1384,7 +1440,7 @@ swipe action Business address Obchodní adresa - No comment provided by engineer. + chat link info line Business chats @@ -1569,12 +1625,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1589,6 +1654,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1601,6 +1670,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat No comment provided by engineer. @@ -1974,6 +2047,10 @@ Toto je váš vlastní jednorázový odkaz! Připojte se prostřednictvím odkazu new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Připojit se jednorázovým odkazem @@ -1983,6 +2060,10 @@ Toto je váš vlastní jednorázový odkaz! Connect with %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected No comment provided by engineer. @@ -2042,7 +2123,7 @@ Toto je váš vlastní jednorázový odkaz! Connection error (AUTH) Chyba spojení (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2091,6 +2172,10 @@ Toto je váš vlastní jednorázový odkaz! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kontakt povolil @@ -2182,11 +2267,6 @@ Toto je váš vlastní jednorázový odkaz! Correct name to %@? alert message - - Create - Vytvořit - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2255,6 +2335,10 @@ Toto je váš vlastní jednorázový odkaz! Vytvořte si profil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2798,6 +2882,10 @@ alert button Přímé zprávy mezi členy jsou v této skupině zakázány. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Vypnout (zachovat přepsání) @@ -2983,6 +3071,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Upravit @@ -3004,7 +3096,7 @@ chat item action Enable Zapnout - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3056,6 +3148,10 @@ chat item action Povolit okamžitá oznámení? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Povolit zámek @@ -3222,7 +3318,7 @@ chat item action Error Chyba - No comment provided by engineer. + conn error description Error aborting address change @@ -3536,6 +3632,10 @@ chat item action Chyba nastavování potvrzení o doručení! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Chyba při spuštění chatu @@ -3869,6 +3969,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -4053,7 +4157,7 @@ Error: %2$@ Group link Odkaz na skupinu - No comment provided by engineer. + chat link info line Group links @@ -4442,7 +4546,7 @@ More improvements are coming soon! Invalid connection link Neplatný odkaz na spojení - No comment provided by engineer. + conn error description Invalid display name! @@ -4501,6 +4605,10 @@ More improvements are coming soon! Pozvat členy No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4688,6 +4796,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Promluvme si v SimpleX Chatu @@ -4707,6 +4819,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -5171,6 +5287,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5197,6 +5317,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nové heslo @@ -5287,6 +5411,10 @@ This is your link for group %@! Ne No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Žádné heslo aplikace @@ -5431,6 +5559,10 @@ This is your link for group %@! Bez uživatelských identifikátorů. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5484,7 +5616,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5508,6 +5640,10 @@ new chat action Jednorázový zvací odkaz No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5527,6 +5663,10 @@ Vyžaduje povolení sítě VPN. Onion hostitelé nebudou použiti. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5729,6 +5869,10 @@ Vyžaduje povolení sítě VPN. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5737,6 +5881,10 @@ Vyžaduje povolení sítě VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5758,6 +5906,10 @@ Vyžaduje povolení sítě VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Počet PING @@ -5811,6 +5963,10 @@ Vyžaduje povolení sítě VPN. Vložit obrázek No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5993,6 +6149,10 @@ Error: %@ Nové vymezení soukromí No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. No comment provided by engineer. @@ -6057,9 +6217,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Aktualizace profilu bude zaslána vašim kontaktům. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6155,6 +6314,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Nabízená oznámení @@ -6370,6 +6533,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Přenosový server se používá pouze v případě potřeby. Jiná strana může sledovat vaši IP adresu. @@ -6384,6 +6551,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Odstranit @@ -6636,6 +6807,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6677,6 +6852,10 @@ chat item action Uložit a upozornit členy skupiny No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6855,6 +7034,10 @@ chat item action Bezpečnostní kód No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Vybrat @@ -6974,6 +7157,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Odeslat je z galerie nebo vlastní klávesnice. @@ -6997,6 +7184,10 @@ chat item action Odesílatel možná smazal požadavek připojení. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Odesílání potvrzení o doručení bude povoleno pro všechny kontakty ve všech viditelných profilech chatu. @@ -7262,11 +7453,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Sdílet adresu s kontakty? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7300,9 +7494,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Sdílet s kontakty + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7707,6 +7904,10 @@ Relay address was used to set up this relay for the channel. Vyfotit No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7719,10 +7920,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7755,6 +7952,10 @@ Relay address was used to set up this relay for the channel. Klepnutím se připojíte inkognito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7848,6 +8049,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -8058,6 +8263,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Vytvoření nového připojení @@ -8308,7 +8517,7 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -8500,6 +8709,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8638,6 +8851,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8674,6 +8891,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Upozornění: můžete ztratit nějaká data! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC servery ICE @@ -9211,6 +9432,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Váš náhodný profil @@ -9393,6 +9618,10 @@ marked deleted chat item preview text volání… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -10019,6 +10248,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10309,6 +10542,10 @@ last received msg: %2$@ \~stávka~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 5840cff078..09a5d750e3 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -185,9 +185,20 @@ %d Monate time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u übersprungene Nachrichten. No comment provided by engineer. + + (from owner) + chat link info line + (new) (Neu) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (Dieses Gerät hat v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - und mehr! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Ein paar weitere Dinge No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Ein neuer Kontakt @@ -670,9 +715,8 @@ swipe action Aktive Verbindungen No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Nachrichtenserver hinzugefügt No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Erste Akzentfarbe @@ -860,6 +908,14 @@ swipe action Alle Profile profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Alle Meldungen werden für Sie archiviert. @@ -1421,7 +1477,7 @@ swipe action Business address Geschäftliche Adresse - No comment provided by engineer. + chat link info line Business chats @@ -1612,12 +1668,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1697,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1713,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Chat @@ -2062,6 +2135,10 @@ Das ist Ihr eigener Einmal-Link! Über einen Link verbinden new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Über einen Einmal-Link verbinden @@ -2072,6 +2149,10 @@ Das ist Ihr eigener Einmal-Link! Mit %@ verbinden new chat action + + Connect with someone + No comment provided by engineer. + Connected Verbunden @@ -2140,7 +2221,7 @@ Das ist Ihr eigener Einmal-Link! Connection error (AUTH) Verbindungsfehler (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2280,10 @@ Das ist Ihr eigener Einmal-Link! Verbindungen No comment provided by engineer. + + Contact address + chat link info line + Contact allows Der Kontakt erlaubt @@ -2299,11 +2384,6 @@ Das ist Ihr eigener Einmal-Link! Richtiger Name für %@? alert message - - Create - Erstellen - No comment provided by engineer. - Create 1-time link Einmal-Link erstellen @@ -2377,6 +2457,10 @@ Das ist Ihr eigener Einmal-Link! Erstellen Sie Ihr Profil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Erstellt @@ -2962,6 +3046,10 @@ alert button In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Deaktivieren (vorgenommene Einstellungen bleiben erhalten) @@ -3168,6 +3256,10 @@ chat item action E2E-verschlüsselte Benachrichtigungen. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Bearbeiten @@ -3190,7 +3282,7 @@ chat item action Enable Aktivieren - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3246,6 +3338,10 @@ chat item action Sofortige Benachrichtigungen aktivieren? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Sperre aktivieren @@ -3422,7 +3518,7 @@ chat item action Error Fehler - No comment provided by engineer. + conn error description Error aborting address change @@ -3766,6 +3862,10 @@ chat item action Fehler beim Setzen von Empfangsbestätigungen! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Fehler beim Starten des Chats @@ -4134,6 +4234,10 @@ server test error Für alle Moderatoren No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Für das Chat-Profil %@: @@ -4346,7 +4450,7 @@ Fehler: %2$@ Group link Gruppen-Link - No comment provided by engineer. + chat link info line Group links @@ -4765,7 +4869,7 @@ Weitere Verbesserungen sind bald verfügbar! Invalid connection link Ungültiger Verbindungslink - No comment provided by engineer. + conn error description Invalid display name! @@ -4830,6 +4934,10 @@ Weitere Verbesserungen sind bald verfügbar! Mitglieder einladen No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Zum Chat einladen @@ -5030,6 +5138,10 @@ Das ist Ihr Link für die Gruppe %@! Weniger Datenverkehr in mobilen Netzen. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Lassen Sie uns in SimpleX Chat kommunizieren @@ -5050,6 +5162,10 @@ Das ist Ihr Link für die Gruppe %@! Verknüpfe Mobiltelefon- und Desktop-Apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Verknüpfte Desktop-Optionen @@ -5569,6 +5685,10 @@ Das ist Ihr Link für die Gruppe %@! Dezentralisiertes Netzwerk No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen. @@ -5599,6 +5719,10 @@ Das ist Ihr Link für die Gruppe %@! Neu token status text + + New 1-time link + No comment provided by engineer. + New Passcode Neuer Zugangscode @@ -5698,6 +5822,10 @@ Das ist Ihr Link für die Gruppe %@! Nein No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Kein App-Passwort @@ -5861,6 +5989,10 @@ Das ist Ihr Link für die Gruppe %@! Keine Benutzerkennungen. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5922,7 +6054,7 @@ Das ist Ihr Link für die Gruppe %@! OK OK - No comment provided by engineer. + alert button Off @@ -5946,6 +6078,10 @@ new chat action Einmal-Einladungslink No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,6 +6101,10 @@ Dies erfordert die Aktivierung eines VPNs. Onion-Hosts werden nicht verwendet. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Nur Chat-Eigentümer können die Präferenzen ändern. @@ -6193,6 +6333,10 @@ Dies erfordert die Aktivierung eines VPNs. Oder teilen Sie diesen Datei-Link sicher No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Oder diesen QR-Code anzeigen @@ -6203,6 +6347,10 @@ Dies erfordert die Aktivierung eines VPNs. Oder zum privaten Teilen No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Chats in Listen verwalten @@ -6228,6 +6376,10 @@ Dies erfordert die Aktivierung eines VPNs. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING-Zähler @@ -6283,6 +6435,10 @@ Dies erfordert die Aktivierung eines VPNs. Bild einfügen No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Zum Verbinden den Link einfügen! @@ -6485,6 +6641,10 @@ Fehler: %@ Datenschutz neu definiert No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. @@ -6559,9 +6719,8 @@ Fehler: %@ Profil-Design No comment provided by engineer. - - Profile update will be sent to your contacts. - Profil-Aktualisierung wird an Ihre Kontakte gesendet. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6666,6 +6825,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Der Proxy benötigt ein Passwort No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push-Benachrichtigungen @@ -6899,6 +7062,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Relais-Server werden nur genutzt, wenn sie benötigt werden. Ihre IP-Adresse kann von Anderen erfasst werden. @@ -6913,6 +7080,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Entfernen @@ -7196,6 +7367,10 @@ swipe action SOCKS-Proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Dateien sicher herunterladen @@ -7241,6 +7416,10 @@ chat item action Speichern und Gruppenmitglieder benachrichtigen No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Speichern und neu verbinden @@ -7439,6 +7618,10 @@ chat item action Sicherheitscode No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Auswählen @@ -7569,6 +7752,10 @@ chat item action Anfrage ohne Nachricht senden No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Senden Sie diese aus dem Fotoalbum oder von individuellen Tastaturen. @@ -7594,6 +7781,10 @@ chat item action Der Absender hat möglicherweise die Verbindungsanfrage gelöscht. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Das Senden von Empfangsbestätigungen an alle Kontakte in allen sichtbaren Chat-Profilen wird aktiviert. @@ -7889,11 +8080,14 @@ chat item action Die Adresse öffentlich teilen No comment provided by engineer. - - Share address with contacts? - Die Adresse mit Kontakten teilen? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Aus anderen Apps heraus teilen. @@ -7933,9 +8127,12 @@ chat item action Mit SimpleX teilen No comment provided by engineer. - - Share with contacts - Mit Kontakten teilen + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8382,6 +8579,10 @@ Relay address was used to set up this relay for the channel. Machen Sie ein Foto No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Verbinden tippen, um zu chatten @@ -8397,11 +8598,6 @@ Relay address was used to set up this relay for the channel. Verbinden tippen, um den Bot zu nutzen. No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8436,6 +8632,10 @@ Relay address was used to set up this relay for the channel. Zum Inkognito beitreten tippen No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Zum Link einfügen tippen @@ -8537,6 +8737,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Der von Ihnen gescannte Code ist kein SimpleX-Link-QR-Code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Diese Verbindung hat das Limit der nicht ausgelieferten Nachrichten erreicht. Ihr Kontakt ist möglicherweise offline. @@ -8770,6 +8974,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Um unerwünschte Nachrichten zu verbergen. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Um eine Verbindung mit einem neuen Kontakt zu erstellen @@ -9046,7 +9254,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Unsupported connection link Verbindungs-Link wird nicht unterstützt - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9266,6 +9474,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Die App mit einer Hand bedienen. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Web-Port nutzen @@ -9418,6 +9630,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Es wird auf den Desktop gewartet... @@ -9458,6 +9674,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Warnung: Sie könnten einige Daten verlieren! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE-Server @@ -10048,6 +10268,10 @@ Relays can access channel messages. Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet. alert message + + Your public address + No comment provided by engineer. + Your random profile Ihr Zufallsprofil @@ -10245,6 +10469,10 @@ marked deleted chat item preview text Anrufen… call status + + can't broadcast + No comment provided by engineer. + can't send messages Es können keine Nachrichten gesendet werden @@ -10901,6 +11129,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address Die Kontaktadresse wurde entfernt @@ -11222,6 +11454,10 @@ Zuletzt empfangene Nachricht: %2$@ \~durchstreichen~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 88b9939944..621bb1f128 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -185,10 +185,23 @@ %d months time interval - - %d relays - %d relays - channel relay bar + + %d relays failed + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -226,11 +239,21 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -240,7 +263,17 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -357,11 +390,21 @@ channel relay bar progress with errors %u messages skipped. No comment provided by engineer. + + (from owner) + (from owner) + chat link info line + (new) (new) No comment provided by engineer. + + (signed) + (signed) + chat link info line + (this device v%@) (this device v%@) @@ -455,6 +498,15 @@ channel relay bar progress with errors - and more! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -553,6 +605,11 @@ time interval A few more things No comment provided by engineer. + + A link for one person to connect + A link for one person to connect + No comment provided by engineer. + A new contact A new contact @@ -679,9 +736,9 @@ swipe action Active connections No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -749,6 +806,11 @@ swipe action Added message servers No comment provided by engineer. + + Adding relays will be supported later. + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Additional accent @@ -869,6 +931,16 @@ swipe action All profiles profile dropdown + + All relays failed + All relays failed + No comment provided by engineer. + + + All relays removed + All relays removed + No comment provided by engineer. + All reports will be archived for you. All reports will be archived for you. @@ -1432,7 +1504,7 @@ swipe action Business address Business address - No comment provided by engineer. + chat link info line Business chats @@ -1626,6 +1698,12 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image Channel image @@ -1634,6 +1712,11 @@ set passcode view Channel link Channel link + chat link info line + + + Channel preferences + Channel preferences No comment provided by engineer. @@ -1651,6 +1734,11 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! Channel will be deleted for all subscribers - this cannot be undone! @@ -1666,6 +1754,11 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + Channels + No comment provided by engineer. + Chat Chat @@ -2091,6 +2184,11 @@ This is your own one-time link! Connect via link new chat sheet title + + Connect via link or QR code + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Connect via one-time link @@ -2101,6 +2199,11 @@ This is your own one-time link! Connect with %@ new chat action + + Connect with someone + Connect with someone + No comment provided by engineer. + Connected Connected @@ -2169,7 +2272,7 @@ This is your own one-time link! Connection error (AUTH) Connection error (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2228,6 +2331,11 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + Contact address + chat link info line + Contact allows Contact allows @@ -2328,11 +2436,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - Create - No comment provided by engineer. - Create 1-time link Create 1-time link @@ -2408,6 +2511,11 @@ This is your own one-time link! Create your profile No comment provided by engineer. + + Create your public address + Create your public address + No comment provided by engineer. + Created Created @@ -2998,6 +3106,11 @@ alert button Direct messages between members are prohibited. No comment provided by engineer. + + Disable + Disable + alert button + Disable (keep overrides) Disable (keep overrides) @@ -3204,6 +3317,11 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Edit @@ -3227,7 +3345,7 @@ chat item action Enable Enable - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3284,6 +3402,11 @@ chat item action Enable instant notifications? No comment provided by engineer. + + Enable link previews? + Enable link previews? + alert title + Enable lock Enable lock @@ -3462,7 +3585,7 @@ chat item action Error Error - No comment provided by engineer. + conn error description Error aborting address change @@ -3809,6 +3932,11 @@ chat item action Error setting delivery receipts! No comment provided by engineer. + + Error sharing channel + Error sharing channel + alert title + Error starting chat Error starting chat @@ -4177,6 +4305,11 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + For anyone to reach you + No comment provided by engineer. + For chat profile %@: For chat profile %@: @@ -4390,7 +4523,7 @@ Error: %2$@ Group link Group link - No comment provided by engineer. + chat link info line Group links @@ -4809,7 +4942,7 @@ More improvements are coming soon! Invalid connection link Invalid connection link - No comment provided by engineer. + conn error description Invalid display name! @@ -4876,6 +5009,11 @@ More improvements are coming soon! Invite members No comment provided by engineer. + + Invite someone privately + Invite someone privately + No comment provided by engineer. + Invite to chat Invite to chat @@ -5079,6 +5217,11 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Let's talk in SimpleX Chat @@ -5099,6 +5242,11 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + Link signature verified. + owner verification + Linked desktop options Linked desktop options @@ -5619,6 +5767,11 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + Network error + conn error description + Network issues - message expired after many attempts to send it. Network issues - message expired after many attempts to send it. @@ -5649,6 +5802,11 @@ This is your link for group %@! New token status text + + New 1-time link + New 1-time link + No comment provided by engineer. + New Passcode New Passcode @@ -5749,6 +5907,11 @@ This is your link for group %@! No No comment provided by engineer. + + No active relays + No active relays + No comment provided by engineer. + No app password No app password @@ -5914,6 +6077,11 @@ This is your link for group %@! No user identifiers. No comment provided by engineer. + + Non-profit governance + Non-profit governance + No comment provided by engineer. + Not all relays connected Not all relays connected @@ -5976,7 +6144,7 @@ This is your link for group %@! OK OK - No comment provided by engineer. + alert button Off @@ -6000,6 +6168,11 @@ new chat action One-time invitation link No comment provided by engineer. + + One-time link + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -6019,6 +6192,11 @@ Requires compatible VPN. Onion hosts will not be used. No comment provided by engineer. + + Only channel owners can change channel preferences. + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Only chat owners can change preferences. @@ -6249,6 +6427,11 @@ Requires compatible VPN. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Or show this code @@ -6259,6 +6442,11 @@ Requires compatible VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organize chats into lists @@ -6286,6 +6474,11 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING count @@ -6341,6 +6534,11 @@ Requires compatible VPN. Paste image No comment provided by engineer. + + Paste link / Scan + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Paste link to connect! @@ -6545,6 +6743,11 @@ Error: %@ Privacy redefined No comment provided by engineer. + + Privacy: for owners and subscribers. + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Private chats, groups and your contacts are not accessible to server operators. @@ -6620,9 +6823,9 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Profile update will be sent to your contacts. + + Profile update will be sent to your SimpleX contacts. + Profile update will be sent to your SimpleX contacts. alert message @@ -6727,6 +6930,11 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push notifications @@ -6964,6 +7172,11 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Relay server is only used if necessary. Another party can observe your IP address. @@ -6979,6 +7192,11 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + Reliability: many relays per channel. + No comment provided by engineer. + Remove Remove @@ -7264,6 +7482,11 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + Safe web links + No comment provided by engineer. + Safely receive files Safely receive files @@ -7310,6 +7533,11 @@ chat item action Save and notify group members No comment provided by engineer. + + Save and notify subscribers + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Save and reconnect @@ -7510,6 +7738,11 @@ chat item action Security code No comment provided by engineer. + + Security: owners hold channel keys. + Security: owners hold channel keys. + No comment provided by engineer. + Select Select @@ -7640,6 +7873,11 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Send them from gallery or custom keyboards. @@ -7665,6 +7903,11 @@ chat item action Sender may have deleted the connection request. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Sending delivery receipts will be enabled for all contacts in all visible chat profiles. @@ -7961,11 +8204,16 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Share address with contacts? + + Share address with SimpleX contacts? + Share address with SimpleX contacts? alert title + + Share channel + Share channel + No comment provided by engineer. + Share from other apps. Share from other apps. @@ -8006,9 +8254,14 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Share with contacts + + Share via chat + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts + Share with SimpleX contacts No comment provided by engineer. @@ -8461,6 +8714,11 @@ Relay address was used to set up this relay for the channel. Take picture No comment provided by engineer. + + Talk to someone + Talk to someone + No comment provided by engineer. + Tap Connect to chat Tap Connect to chat @@ -8476,11 +8734,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel Tap Join channel @@ -8516,6 +8769,11 @@ Relay address was used to set up this relay for the channel. Tap to join incognito No comment provided by engineer. + + Tap to open + Tap to open + No comment provided by engineer. + Tap to paste link Tap to paste link @@ -8619,6 +8877,11 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. The connection reached the limit of undelivered messages, your contact may be offline. @@ -8854,6 +9117,11 @@ It can happen because of some bug or when the connection is compromised.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection To make a new connection @@ -9131,7 +9399,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9353,6 +9621,11 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Use web port @@ -9508,6 +9781,11 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Waiting for desktop... @@ -9548,6 +9826,11 @@ To connect, please ask your contact to create another connection link and check Warning: you may lose some data! No comment provided by engineer. + + We made connecting simpler for new users. + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE servers @@ -10144,6 +10427,11 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + Your public address + No comment provided by engineer. + Your random profile Your random profile @@ -10345,6 +10633,11 @@ marked deleted chat item preview text calling… call status + + can't broadcast + can't broadcast + No comment provided by engineer. + can't send messages can't send messages @@ -11009,6 +11302,11 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + removed by operator + No comment provided by engineer. + removed contact address removed contact address @@ -11333,6 +11631,11 @@ last received msg: %2$@ \~strike~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index fe6234e16b..c22ff04450 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -185,9 +185,20 @@ %d mes(es) time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u mensaje(s) omitido(s). No comment provided by engineer. + + (from owner) + chat link info line + (new) (nuevo) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (este dispositivo v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - ¡y más! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Algunas cosas más No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Contacto nuevo @@ -670,9 +715,8 @@ swipe action Conexiones activas No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Servidores de mensajes añadidos No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Acento adicional @@ -860,6 +908,14 @@ swipe action Todos los perfiles profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Todos los informes serán archivados para ti. @@ -1421,7 +1477,7 @@ swipe action Business address Dirección empresarial - No comment provided by engineer. + chat link info line Business chats @@ -1612,12 +1668,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1697,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1713,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Chat @@ -2062,6 +2135,10 @@ This is your own one-time link! Conectar mediante enlace new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Conectar mediante enlace de un sólo uso @@ -2072,6 +2149,10 @@ This is your own one-time link! Conectar con %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Conectadas @@ -2140,7 +2221,7 @@ This is your own one-time link! Connection error (AUTH) Error de conexión (Autenticación) - No comment provided by engineer. + conn error description Connection failed @@ -2198,6 +2279,10 @@ This is your own one-time link! Conexiones No comment provided by engineer. + + Contact address + chat link info line + Contact allows El contacto permite @@ -2298,11 +2383,6 @@ This is your own one-time link! ¿Corregir el nombre a %@? alert message - - Create - Crear - No comment provided by engineer. - Create 1-time link Crear enlace de un solo uso @@ -2376,6 +2456,10 @@ This is your own one-time link! Crea tu perfil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Creadas @@ -2961,6 +3045,10 @@ alert button Los mensajes directos entre miembros del grupo no están permitidos. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Desactivar (conservando anulaciones) @@ -3167,6 +3255,10 @@ chat item action Notificaciones cifradas E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Editar @@ -3189,7 +3281,7 @@ chat item action Enable Activar - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3245,6 +3337,10 @@ chat item action ¿Activar notificaciones instantáneas? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Activar bloqueo @@ -3421,7 +3517,7 @@ chat item action Error Error - No comment provided by engineer. + conn error description Error aborting address change @@ -3765,6 +3861,10 @@ chat item action ¡Error al configurar confirmaciones de entrega! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Error al iniciar Chat @@ -4133,6 +4233,10 @@ server test error Para todos los moderadores No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Para el perfil de chat %@: @@ -4345,7 +4449,7 @@ Error: %2$@ Group link Enlace de grupo - No comment provided by engineer. + chat link info line Group links @@ -4763,7 +4867,7 @@ More improvements are coming soon! Invalid connection link Enlace de conexión no válido - No comment provided by engineer. + conn error description Invalid display name! @@ -4828,6 +4932,10 @@ More improvements are coming soon! Invitar miembros No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Invitar al chat @@ -5028,6 +5136,10 @@ This is your link for group %@! Menos tráfico en redes móviles. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Hablemos en SimpleX Chat @@ -5048,6 +5160,10 @@ This is your link for group %@! ¡Enlazar aplicación móvil con ordenador! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Opciones ordenador enlazado @@ -5567,6 +5683,10 @@ This is your link for group %@! Descentralización de la red No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Problema en la red - el mensaje ha expirado tras muchos intentos de envío. @@ -5597,6 +5717,10 @@ This is your link for group %@! Nuevo token status text + + New 1-time link + No comment provided by engineer. + New Passcode Código Nuevo @@ -5696,6 +5820,10 @@ This is your link for group %@! No No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Sin contraseña de la aplicación @@ -5859,6 +5987,10 @@ This is your link for group %@! Sin identificadores de usuario. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5920,7 +6052,7 @@ This is your link for group %@! OK OK - No comment provided by engineer. + alert button Off @@ -5944,6 +6076,10 @@ new chat action Enlace de invitación de un solo uso No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5963,6 +6099,10 @@ Requiere activación de la VPN. No se usarán hosts .onion. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Sólo los propietarios del chat pueden cambiar las preferencias. @@ -6191,6 +6331,10 @@ Requiere activación de la VPN. O comparte de forma segura este enlace al archivo No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code O muestra este código @@ -6201,6 +6345,10 @@ Requiere activación de la VPN. O para compartir en privado No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organiza tus chats en listas @@ -6226,6 +6374,10 @@ Requiere activación de la VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Contador PING @@ -6281,6 +6433,10 @@ Requiere activación de la VPN. Pegar imagen No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Pegar enlace para conectar! @@ -6483,6 +6639,10 @@ Error: %@ Privacidad redefinida No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. @@ -6557,9 +6717,8 @@ Error: %@ Tema del perfil No comment provided by engineer. - - Profile update will be sent to your contacts. - La actualización del perfil se enviará a tus contactos. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6664,6 +6823,10 @@ Actívalo en ajustes de *Servidores y Redes*. El proxy requiere contraseña No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Notificaciones push @@ -6897,6 +7060,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP. @@ -6911,6 +7078,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Eliminar @@ -7194,6 +7365,10 @@ swipe action Proxy SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Recibe archivos de forma segura @@ -7239,6 +7414,10 @@ chat item action Guardar y notificar grupo No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Guardar y reconectar @@ -7437,6 +7616,10 @@ chat item action Código de seguridad No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Seleccionar @@ -7567,6 +7750,10 @@ chat item action Enviar solicitud sin mensaje No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Envíalos desde la galería o desde teclados personalizados. @@ -7592,6 +7779,10 @@ chat item action El remitente puede haber eliminado la solicitud de conexión. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. El envío de confirmaciones de entrega se activará para todos los contactos en todos los perfiles visibles. @@ -7887,11 +8078,14 @@ chat item action Campartir dirección públicamente No comment provided by engineer. - - Share address with contacts? - ¿Compartir la dirección con los contactos? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Comparte desde otras aplicaciones. @@ -7931,9 +8125,12 @@ chat item action Compartir con Simplex No comment provided by engineer. - - Share with contacts - Compartir con contactos + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8380,6 +8577,10 @@ Relay address was used to set up this relay for the channel. Hacer foto No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Pulsa Conectar para chatear @@ -8395,11 +8596,6 @@ Relay address was used to set up this relay for the channel. Pulsa Conectar para usar el bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Pulsa Crear dirección SimpleX en el menú para crearla más tarde. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8434,6 +8630,10 @@ Relay address was used to set up this relay for the channel. Pulsa para unirte en modo incógnito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Pulsa aquí para pegar el enlace @@ -8535,6 +8735,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El código QR escaneado no es un enlace de SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. La conexión ha alcanzado el límite de mensajes no entregados. es posible que tu contacto esté desconectado. @@ -8768,6 +8972,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Para ocultar mensajes no deseados. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Para hacer una conexión nueva @@ -9044,7 +9252,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Unsupported connection link Enlace de conexión no compatible - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9264,6 +9472,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Usa la aplicación con una sola mano. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Usar puerto web @@ -9416,6 +9628,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Esperando ordenador... @@ -9456,6 +9672,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Atención: ¡puedes perder algunos datos! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Servidores WebRTC ICE @@ -10046,6 +10266,10 @@ Relays can access channel messages. Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos. alert message + + Your public address + No comment provided by engineer. + Your random profile Tu perfil aleatorio @@ -10243,6 +10467,10 @@ marked deleted chat item preview text llamando… call status + + can't broadcast + No comment provided by engineer. + can't send messages no se pueden enviar mensajes @@ -10898,6 +11126,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address dirección de contacto eliminada @@ -11219,6 +11451,10 @@ last received msg: %2$@ \~strike~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 89bea61cee..ad2f500c0e 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -172,9 +172,20 @@ %d kuukautta time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -208,10 +219,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -219,7 +238,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -330,10 +357,18 @@ channel relay bar progress with errors %u viestit ohitettu. No comment provided by engineer. + + (from owner) + chat link info line + (new) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) No comment provided by engineer. @@ -417,6 +452,12 @@ channel relay bar progress with errors - ja paljon muuta! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -508,6 +549,10 @@ time interval Muutama asia lisää No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Uusi kontakti @@ -622,9 +667,8 @@ swipe action Active connections No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -684,6 +728,10 @@ swipe action Added message servers No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -789,6 +837,14 @@ swipe action All profiles profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -1292,7 +1348,7 @@ swipe action Business address - No comment provided by engineer. + chat link info line Business chats @@ -1465,12 +1521,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1485,6 +1550,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1497,6 +1566,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat No comment provided by engineer. @@ -1863,6 +1936,10 @@ This is your own one-time link! Yhdistä linkin kautta new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Yhdistä kertalinkillä @@ -1872,6 +1949,10 @@ This is your own one-time link! Connect with %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected No comment provided by engineer. @@ -1931,7 +2012,7 @@ This is your own one-time link! Connection error (AUTH) Yhteysvirhe (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -1980,6 +2061,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kontakti sallii @@ -2071,11 +2156,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - Luo - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2144,6 +2224,10 @@ This is your own one-time link! Luo profiilisi No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2687,6 +2771,10 @@ alert button Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Poista käytöstä (pidä ohitukset) @@ -2872,6 +2960,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Muokkaa @@ -2893,7 +2985,7 @@ chat item action Enable Salli - No comment provided by engineer. + alert button Enable (keep overrides) @@ -2945,6 +3037,10 @@ chat item action Salli välittömät ilmoitukset? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Ota lukitus käyttöön @@ -3110,7 +3206,7 @@ chat item action Error Virhe - No comment provided by engineer. + conn error description Error aborting address change @@ -3422,6 +3518,10 @@ chat item action Virhe toimituskuittauksien asettamisessa! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Virhe käynnistettäessä keskustelua @@ -3755,6 +3855,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -3939,7 +4043,7 @@ Error: %2$@ Group link Ryhmälinkki - No comment provided by engineer. + chat link info line Group links @@ -4328,7 +4432,7 @@ More improvements are coming soon! Invalid connection link Virheellinen yhteyslinkki - No comment provided by engineer. + conn error description Invalid display name! @@ -4387,6 +4491,10 @@ More improvements are coming soon! Kutsu jäseniä No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4574,6 +4682,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Jutellaan SimpleX Chatissa @@ -4593,6 +4705,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -5057,6 +5173,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5083,6 +5203,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Uusi pääsykoodi @@ -5172,6 +5296,10 @@ This is your link for group %@! Ei No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Ei sovelluksen salasanaa @@ -5316,6 +5444,10 @@ This is your link for group %@! Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5369,7 +5501,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5393,6 +5525,10 @@ new chat action Kertakutsulinkki No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5412,6 +5548,10 @@ Edellyttää VPN:n sallimista. Onion-isäntiä ei käytetä. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5613,6 +5753,10 @@ Edellyttää VPN:n sallimista. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5621,6 +5765,10 @@ Edellyttää VPN:n sallimista. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5642,6 +5790,10 @@ Edellyttää VPN:n sallimista. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING-määrä @@ -5695,6 +5847,10 @@ Edellyttää VPN:n sallimista. Liitä kuva No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5877,6 +6033,10 @@ Error: %@ Yksityisyys uudelleen määritettynä No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. No comment provided by engineer. @@ -5941,9 +6101,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - Profiilipäivitys lähetetään kontakteillesi. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6039,6 +6198,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push-ilmoitukset @@ -6253,6 +6416,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Välityspalvelinta käytetään vain tarvittaessa. Toinen osapuoli voi tarkkailla IP-osoitettasi. @@ -6267,6 +6434,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Poista @@ -6519,6 +6690,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6560,6 +6735,10 @@ chat item action Tallenna ja ilmoita ryhmän jäsenille No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6738,6 +6917,10 @@ chat item action Turvakoodi No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Valitse @@ -6856,6 +7039,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Lähetä ne galleriasta tai mukautetuista näppäimistöistä. @@ -6879,6 +7066,10 @@ chat item action Lähettäjä on saattanut poistaa yhteyspyynnön. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Toimituskuittauksien lähettäminen otetaan käyttöön kaikille kontakteille näkyvissä keskusteluprofiileissa. @@ -7144,11 +7335,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - Jaa osoite kontakteille? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7182,9 +7376,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - Jaa kontaktien kanssa + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7588,6 +7785,10 @@ Relay address was used to set up this relay for the channel. Ota kuva No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7600,10 +7801,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7636,6 +7833,10 @@ Relay address was used to set up this relay for the channel. Napauta liittyäksesi incognito-tilassa No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7729,6 +7930,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -7939,6 +8144,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Uuden yhteyden luominen @@ -8188,7 +8397,7 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -8380,6 +8589,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8518,6 +8731,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8554,6 +8771,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Varoitus: saatat menettää joitain tietoja! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE -palvelimet @@ -9091,6 +9312,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Satunnainen profiilisi @@ -9273,6 +9498,10 @@ marked deleted chat item preview text soittaa… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -9899,6 +10128,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10189,6 +10422,10 @@ last received msg: %2$@ \~strike~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 5fcada4ccf..e7a5ef470a 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -185,9 +185,20 @@ %d mois time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u messages sautés. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nouveau) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (cet appareil v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - et bien d'autres choses encore ! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Encore quelques points No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Un nouveau contact @@ -670,9 +715,8 @@ swipe action Connections actives No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Ajout de serveurs de messages No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Accent additionnel @@ -859,6 +907,14 @@ swipe action Tous les profiles profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Tous les rapports seront archivés pour vous. @@ -1414,7 +1470,7 @@ swipe action Business address Adresse professionnelle - No comment provided by engineer. + chat link info line Business chats @@ -1603,12 +1659,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1623,6 +1688,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1635,6 +1704,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Discussions @@ -2048,6 +2121,10 @@ Il s'agit de votre propre lien unique ! Se connecter via un lien new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Se connecter via un lien unique @@ -2058,6 +2135,10 @@ Il s'agit de votre propre lien unique ! Se connecter avec %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Connecté @@ -2126,7 +2207,7 @@ Il s'agit de votre propre lien unique ! Connection error (AUTH) Erreur de connexion (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2184,6 +2265,10 @@ Il s'agit de votre propre lien unique ! Connexions No comment provided by engineer. + + Contact address + chat link info line + Contact allows Votre contact autorise @@ -2283,11 +2368,6 @@ Il s'agit de votre propre lien unique ! Corriger le nom pour %@ ? alert message - - Create - Créer - No comment provided by engineer. - Create 1-time link Créer un lien unique @@ -2360,6 +2440,10 @@ Il s'agit de votre propre lien unique ! Créez votre profil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Créées @@ -2940,6 +3024,10 @@ alert button Les messages directs entre membres sont interdits dans ce groupe. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Désactiver (conserver les remplacements) @@ -3146,6 +3234,10 @@ chat item action Notifications chiffrées E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Modifier @@ -3167,7 +3259,7 @@ chat item action Enable Activer - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3222,6 +3314,10 @@ chat item action Activer les notifications instantanées ? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Activer le verrouillage @@ -3398,7 +3494,7 @@ chat item action Error Erreur - No comment provided by engineer. + conn error description Error aborting address change @@ -3735,6 +3831,10 @@ chat item action Erreur lors de la configuration des accusés de réception ! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Erreur lors du démarrage du chat @@ -4096,6 +4196,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Pour le profil de discussion %@ : @@ -4306,7 +4410,7 @@ Erreur : %2$@ Group link Lien du groupe - No comment provided by engineer. + chat link info line Group links @@ -4712,7 +4816,7 @@ D'autres améliorations sont à venir ! Invalid connection link Lien de connection invalide - No comment provided by engineer. + conn error description Invalid display name! @@ -4776,6 +4880,10 @@ D'autres améliorations sont à venir ! Inviter des membres No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Inviter à discuter @@ -4974,6 +5082,10 @@ Voici votre lien pour le groupe %@ ! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Discutons sur SimpleX Chat @@ -4994,6 +5106,10 @@ Voici votre lien pour le groupe %@ ! Liez vos applications mobiles et de bureau ! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Options de bureau lié @@ -5495,6 +5611,10 @@ Voici votre lien pour le groupe %@ ! Décentralisation du réseau No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Problèmes de réseau - le message a expiré après plusieurs tentatives d'envoi. @@ -5524,6 +5644,10 @@ Voici votre lien pour le groupe %@ ! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nouveau code d'accès @@ -5621,6 +5745,10 @@ Voici votre lien pour le groupe %@ ! Non No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Pas de mot de passe pour l'app @@ -5776,6 +5904,10 @@ Voici votre lien pour le groupe %@ ! Aucun identifiant d'utilisateur. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5834,7 +5966,7 @@ Voici votre lien pour le groupe %@ ! OK OK - No comment provided by engineer. + alert button Off @@ -5858,6 +5990,10 @@ new chat action Lien d'invitation unique No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5877,6 +6013,10 @@ Nécessite l'activation d'un VPN. Les hôtes .onion ne seront pas utilisés. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Seuls les propriétaires peuvent modifier les préférences. @@ -6092,6 +6232,10 @@ Nécessite l'activation d'un VPN. Ou partagez en toute sécurité le lien de ce fichier No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Ou montrez ce code @@ -6102,6 +6246,10 @@ Nécessite l'activation d'un VPN. Ou à partager en privé No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -6126,6 +6274,10 @@ Nécessite l'activation d'un VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Nombre de PING @@ -6181,6 +6333,10 @@ Nécessite l'activation d'un VPN. Coller l'image No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Collez le lien pour vous connecter ! @@ -6378,6 +6534,10 @@ Erreur : %@ La vie privée redéfinie No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. No comment provided by engineer. @@ -6449,9 +6609,8 @@ Erreur : %@ Thème de profil No comment provided by engineer. - - Profile update will be sent to your contacts. - La mise à jour du profil sera envoyée à vos contacts. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6554,6 +6713,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Le proxy est protégé par un mot de passe No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Notifications push @@ -6783,6 +6946,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Le serveur relais n'est utilisé que si nécessaire. Un tiers peut observer votre adresse IP. @@ -6797,6 +6964,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Supprimer @@ -7063,6 +7234,10 @@ swipe action proxy SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Réception de fichiers en toute sécurité @@ -7106,6 +7281,10 @@ chat item action Enregistrer et en informer les membres du groupe No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Sauvegarder et se reconnecter @@ -7297,6 +7476,10 @@ chat item action Code de sécurité No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Choisir @@ -7423,6 +7606,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Envoyez-les depuis la phototèque ou des claviers personnalisés. @@ -7447,6 +7634,10 @@ chat item action L'expéditeur a peut-être supprimé la demande de connexion. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. L'envoi d'accusés de réception sera activé pour tous les contacts dans tous les profils de chat visibles. @@ -7738,11 +7929,14 @@ chat item action Partager publiquement votre adresse No comment provided by engineer. - - Share address with contacts? - Partager l'adresse avec vos contacts ? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Partager depuis d'autres applications. @@ -7780,9 +7974,12 @@ chat item action Partager sur SimpleX No comment provided by engineer. - - Share with contacts - Partager avec vos contacts + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8220,6 +8417,10 @@ Relay address was used to set up this relay for the channel. Prendre une photo No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -8232,11 +8433,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Appuyez sur Créer une adresse SimpleX dans le menu pour la créer ultérieurement. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8270,6 +8466,10 @@ Relay address was used to set up this relay for the channel. Appuyez pour rejoindre incognito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Appuyez pour coller le lien @@ -8369,6 +8569,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le code scanné n'est pas un code QR de lien SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. La connexion a atteint la limite des messages non délivrés, votre contact est peut-être hors ligne. @@ -8596,6 +8800,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Pour cacher les messages indésirables. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Pour établir une nouvelle connexion @@ -8867,7 +9075,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9077,6 +9285,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser l'application d'une main. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -9227,6 +9439,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... En attente du bureau... @@ -9267,6 +9483,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Attention : vous risquez de perdre des données ! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Serveurs WebRTC ICE @@ -9847,6 +10067,10 @@ Relays can access channel messages. Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile Votre profil aléatoire @@ -10040,6 +10264,10 @@ marked deleted chat item preview text appel… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -10680,6 +10908,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address suppression de l'adresse de contact @@ -10993,6 +11225,10 @@ dernier message reçu : %2$@ \~barré~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 4837804697..1b568f6157 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -185,9 +185,20 @@ %d hónap time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u üzenet kihagyva. No comment provided by engineer. + + (from owner) + chat link info line + (new) (új) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (ez az eszköz: v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - és még sok más! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Néhány további dolog No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Egy új partner @@ -670,9 +715,8 @@ swipe action Aktív kapcsolatok száma No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Hozzáadott üzenetkiszolgálók No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent További kiemelőszín @@ -860,6 +908,14 @@ swipe action Összes profil profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Az összes jelentés archiválva lesz az Ön számára. @@ -1421,7 +1477,7 @@ swipe action Business address Üzleti cím - No comment provided by engineer. + chat link info line Business chats @@ -1612,12 +1668,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1697,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1713,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Csevegés @@ -2062,6 +2135,10 @@ Ez a saját egyszer használható meghívója! Kapcsolódás egy hivatkozáson keresztül new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Kapcsolódás az egyszer használható meghívón keresztül @@ -2072,6 +2149,10 @@ Ez a saját egyszer használható meghívója! Kapcsolódás a következővel: %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Kapcsolódott @@ -2140,7 +2221,7 @@ Ez a saját egyszer használható meghívója! Connection error (AUTH) Kapcsolódási hiba (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2280,10 @@ Ez a saját egyszer használható meghívója! Kapcsolatok No comment provided by engineer. + + Contact address + chat link info line + Contact allows Partner engedélyezi @@ -2299,11 +2384,6 @@ Ez a saját egyszer használható meghívója! Helyesbíti a nevet a következőre: %@? alert message - - Create - Létrehozás - No comment provided by engineer. - Create 1-time link Egyszer használható meghívó létrehozása @@ -2377,6 +2457,10 @@ Ez a saját egyszer használható meghívója! Profil létrehozása No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Létrehozva @@ -2962,6 +3046,10 @@ alert button A tagok közötti közvetlen üzenetek le vannak tiltva. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Letiltás (egyéni beállítások megtartása) @@ -3168,6 +3256,10 @@ chat item action Végpontok között titkosított értesítések. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Szerkesztés @@ -3190,7 +3282,7 @@ chat item action Enable Engedélyezés - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3246,6 +3338,10 @@ chat item action Engedélyezi az azonnali értesítéseket? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Zárolás engedélyezése @@ -3422,7 +3518,7 @@ chat item action Error Hiba - No comment provided by engineer. + conn error description Error aborting address change @@ -3766,6 +3862,10 @@ chat item action Hiba történt a kézbesítési jelentések beállításakor! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Hiba történt a csevegés elindításakor @@ -4134,6 +4234,10 @@ server test error Az összes moderátor számára No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: A(z) %@ nevű csevegési profilhoz: @@ -4346,7 +4450,7 @@ Hiba: %2$@ Group link Csoporthivatkozás - No comment provided by engineer. + chat link info line Group links @@ -4765,7 +4869,7 @@ További fejlesztések hamarosan! Invalid connection link Érvénytelen kapcsolattartási hivatkozás - No comment provided by engineer. + conn error description Invalid display name! @@ -4830,6 +4934,10 @@ További fejlesztések hamarosan! Tagok meghívása No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Meghívás a csevegésbe @@ -5030,6 +5138,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Kevesebb adatforgalom a mobilhálózatokon. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Beszélgessünk a SimpleX Chatben @@ -5050,6 +5162,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Társított számítógép beállítások @@ -5569,6 +5685,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Hálózati decentralizáció No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Hálózati problémák – az üzenet többszöri elküldési kísérlet után lejárt. @@ -5599,6 +5719,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Új token status text + + New 1-time link + No comment provided by engineer. + New Passcode Új jelkód @@ -5698,6 +5822,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nem No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Nincs alkalmazás jelszó @@ -5861,6 +5989,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nincsenek felhasználói azonosítók. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5922,7 +6054,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! OK Rendben - No comment provided by engineer. + alert button Off @@ -5946,6 +6078,10 @@ new chat action Egyszer használható meghívó No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,6 +6101,10 @@ VPN engedélyezése szükséges. Az onion kiszolgálók nem lesznek használva. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Csak a csevegés tulajdonosai módosíthatják a csevegési beállításokat. @@ -6193,6 +6333,10 @@ VPN engedélyezése szükséges. Vagy ossza meg biztonságosan ezt a fájlhivatkozást No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Vagy mutassa meg ezt a kódot @@ -6203,6 +6347,10 @@ VPN engedélyezése szükséges. Vagy a privát megosztáshoz No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Csevegések listákba szervezése @@ -6228,6 +6376,10 @@ VPN engedélyezése szükséges. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING-ek száma @@ -6283,6 +6435,10 @@ VPN engedélyezése szükséges. Kép beillesztése No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Hivatkozás beillesztése a kapcsolódáshoz! @@ -6485,6 +6641,10 @@ Hiba: %@ Újraértelmezett adatvédelem No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára. @@ -6559,9 +6719,8 @@ Hiba: %@ Profiltéma No comment provided by engineer. - - Profile update will be sent to your contacts. - A profilfrissítés el lesz küldve a partnerei számára. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6666,6 +6825,10 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. A proxy jelszót igényel No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Leküldéses értesítések @@ -6899,6 +7062,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Az átjátszó csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét. @@ -6913,6 +7080,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Eltávolítás @@ -7196,6 +7367,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Fájlok biztonságos fogadása @@ -7241,6 +7416,10 @@ chat item action Mentés és a csoporttagok értesítése No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Mentés és újrakapcsolódás @@ -7439,6 +7618,10 @@ chat item action Biztonsági kód No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Kiválasztás @@ -7569,6 +7752,10 @@ chat item action Kérés küldése üzenet nélkül No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Küldje el őket a galériából vagy az egyéni billentyűzetekről. @@ -7594,6 +7781,10 @@ chat item action A kérés küldője törölhette a kapcsolódási kérést. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. A kézbesítési jelentések küldése engedélyezve lesz az összes látható csevegési profilban lévő összes partnere számára. @@ -7889,11 +8080,14 @@ chat item action Cím nyilvános megosztása No comment provided by engineer. - - Share address with contacts? - Megosztja a címet a partnereivel? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Megosztás más alkalmazásokból. @@ -7933,9 +8127,12 @@ chat item action Megosztás a SimpleXben No comment provided by engineer. - - Share with contacts - Megosztás a partnerekkel + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8382,6 +8579,10 @@ Relay address was used to set up this relay for the channel. Kép készítése No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Koppintson a „Kapcsolódás” gombra a csevegéshez @@ -8397,11 +8598,6 @@ Relay address was used to set up this relay for the channel. Koppintson a „Kapcsolódás” gombra a bot használatához No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8436,6 +8632,10 @@ Relay address was used to set up this relay for the channel. Koppintson ide az inkognitóban való kapcsolódáshoz No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Koppintson ide a hivatkozás beillesztéséhez @@ -8537,6 +8737,10 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A beolvasott QR-kód nem egy SimpleX-hivatkozás. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. A kapcsolat elérte a kézbesítetlen üzenetek számának határát, a partnere lehet, hogy offline állapotban van. @@ -8770,6 +8974,10 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. Kéretlen üzenetek elrejtése. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Új kapcsolat létrehozásához @@ -9046,7 +9254,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Unsupported connection link Nem támogatott kapcsolattartási hivatkozás - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9266,6 +9474,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Alkalmazás egy kézzel való használata. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Webport használata @@ -9418,6 +9630,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Várakozás a számítógép-alkalmazásra… @@ -9458,6 +9674,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Figyelmeztetés: néhány adat elveszhet! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE-kiszolgálók @@ -10048,6 +10268,10 @@ Relays can access channel messages. A profilja módosult. Ha menti, akkor a profilfrissítés el lesz küldve a partnerei számára. alert message + + Your public address + No comment provided by engineer. + Your random profile Véletlenszerű profil @@ -10245,6 +10469,10 @@ marked deleted chat item preview text hívás… call status + + can't broadcast + No comment provided by engineer. + can't send messages nem lehet üzeneteket küldeni @@ -10901,6 +11129,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address eltávolította a kapcsolattartási címet @@ -11222,6 +11454,10 @@ utoljára fogadott üzenet: %2$@ \~áthúzott~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 2d0566afc6..951860ccfe 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -185,9 +185,20 @@ %d mesi time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u messaggi saltati. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nuovo) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (questo dispositivo v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - e altro ancora! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Qualche altra cosa No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Un contatto nuovo @@ -670,9 +715,8 @@ swipe action Connessioni attive No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Server dei messaggi aggiunti No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Principale aggiuntivo @@ -860,6 +908,14 @@ swipe action Tutti gli profili profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Tutte le segnalazioni verranno archiviate per te. @@ -1421,7 +1477,7 @@ swipe action Business address Indirizzo di lavoro - No comment provided by engineer. + chat link info line Business chats @@ -1612,12 +1668,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1697,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1713,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Chat @@ -2062,6 +2135,10 @@ Questo è il tuo link una tantum! Connetti via link new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Connetti via link una tantum @@ -2072,6 +2149,10 @@ Questo è il tuo link una tantum! Connettersi con %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Connesso @@ -2140,7 +2221,7 @@ Questo è il tuo link una tantum! Connection error (AUTH) Errore di connessione (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2280,10 @@ Questo è il tuo link una tantum! Connessioni No comment provided by engineer. + + Contact address + chat link info line + Contact allows Il contatto lo consente @@ -2299,11 +2384,6 @@ Questo è il tuo link una tantum! Correggere il nome a %@? alert message - - Create - Crea - No comment provided by engineer. - Create 1-time link Crea link una tantum @@ -2377,6 +2457,10 @@ Questo è il tuo link una tantum! Crea il tuo profilo No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Creato @@ -2962,6 +3046,10 @@ alert button I messaggi diretti tra i membri sono vietati in questo gruppo. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Disattiva (mantieni sostituzioni) @@ -3168,6 +3256,10 @@ chat item action Notifiche crittografate E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Modifica @@ -3190,7 +3282,7 @@ chat item action Enable Attiva - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3246,6 +3338,10 @@ chat item action Attivare le notifiche istantanee? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Attiva blocco @@ -3422,7 +3518,7 @@ chat item action Error Errore - No comment provided by engineer. + conn error description Error aborting address change @@ -3766,6 +3862,10 @@ chat item action Errore nell'impostazione delle ricevute di consegna! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Errore di avvio della chat @@ -4134,6 +4234,10 @@ server test error Per tutti i moderatori No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Per il profilo di chat %@: @@ -4346,7 +4450,7 @@ Errore: %2$@ Group link Link del gruppo - No comment provided by engineer. + chat link info line Group links @@ -4765,7 +4869,7 @@ Altri miglioramenti sono in arrivo! Invalid connection link Link di connessione non valido - No comment provided by engineer. + conn error description Invalid display name! @@ -4830,6 +4934,10 @@ Altri miglioramenti sono in arrivo! Invita membri No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Invita in chat @@ -5030,6 +5138,10 @@ Questo è il tuo link per il gruppo %@! Meno traffico sulle reti mobili. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Parliamo in SimpleX Chat @@ -5050,6 +5162,10 @@ Questo è il tuo link per il gruppo %@! Collega le app mobile e desktop! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Opzioni del desktop collegato @@ -5569,6 +5685,10 @@ Questo è il tuo link per il gruppo %@! Decentralizzazione della rete No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo. @@ -5599,6 +5719,10 @@ Questo è il tuo link per il gruppo %@! Nuovo token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nuovo codice di accesso @@ -5698,6 +5822,10 @@ Questo è il tuo link per il gruppo %@! No No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Nessuna password dell'app @@ -5861,6 +5989,10 @@ Questo è il tuo link per il gruppo %@! Nessun identificatore utente. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5922,7 +6054,7 @@ Questo è il tuo link per il gruppo %@! OK OK - No comment provided by engineer. + alert button Off @@ -5946,6 +6078,10 @@ new chat action Link di invito una tantum No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,6 +6101,10 @@ Richiede l'attivazione della VPN. Gli host Onion non verranno usati. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Solo i proprietari della chat possono modificarne le preferenze. @@ -6193,6 +6333,10 @@ Richiede l'attivazione della VPN. O condividi in modo sicuro questo link del file No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code O mostra questo codice @@ -6203,6 +6347,10 @@ Richiede l'attivazione della VPN. O per condividere in modo privato No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organizza le chat in elenchi @@ -6228,6 +6376,10 @@ Richiede l'attivazione della VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Conteggio PING @@ -6283,6 +6435,10 @@ Richiede l'attivazione della VPN. Incolla immagine No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Incolla un link per connettere! @@ -6485,6 +6641,10 @@ Errore: %@ Privacy ridefinita No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server. @@ -6559,9 +6719,8 @@ Errore: %@ Tema del profilo No comment provided by engineer. - - Profile update will be sent to your contacts. - L'aggiornamento del profilo verrà inviato ai tuoi contatti. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6666,6 +6825,10 @@ Attivalo nelle impostazioni *Rete e server*. Il proxy richiede una password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Notifiche push @@ -6899,6 +7062,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Il server relay viene usato solo se necessario. Un altro utente può osservare il tuo indirizzo IP. @@ -6913,6 +7080,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Rimuovi @@ -7196,6 +7367,10 @@ swipe action Proxy SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Ricevi i file in sicurezza @@ -7241,6 +7416,10 @@ chat item action Salva e avvisa i membri del gruppo No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Salva e riconnetti @@ -7439,6 +7618,10 @@ chat item action Codice di sicurezza No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Seleziona @@ -7569,6 +7752,10 @@ chat item action Invia richiesta senza messaggio No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Inviali dalla galleria o dalle tastiere personalizzate. @@ -7594,6 +7781,10 @@ chat item action Il mittente potrebbe aver eliminato la richiesta di connessione. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. L'invio delle ricevute di consegna sarà attivo per tutti i contatti in tutti i profili di chat visibili. @@ -7889,11 +8080,14 @@ chat item action Condividi indirizzo pubblicamente No comment provided by engineer. - - Share address with contacts? - Condividere l'indirizzo con i contatti? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Condividi da altre app. @@ -7933,9 +8127,12 @@ chat item action Condividi in SimpleX No comment provided by engineer. - - Share with contacts - Condividi con i contatti + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8382,6 +8579,10 @@ Relay address was used to set up this relay for the channel. Scatta foto No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Tocca Connetti per chattare @@ -8397,11 +8598,6 @@ Relay address was used to set up this relay for the channel. Tocca Connetti per usare il bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8436,6 +8632,10 @@ Relay address was used to set up this relay for the channel. Toccare per entrare in incognito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Tocca per incollare il link @@ -8537,6 +8737,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il codice che hai scansionato non è un codice QR di link SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. La connessione ha raggiunto il limite di messaggi non consegnati, il contatto potrebbe essere offline. @@ -8770,6 +8974,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Per nascondere messaggi indesiderati. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Per creare una nuova connessione @@ -9046,7 +9254,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Unsupported connection link Link di connessione non supportato - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9266,6 +9474,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa l'app con una mano sola. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Usa porta web @@ -9418,6 +9630,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... In attesa del desktop... @@ -9458,6 +9674,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Attenzione: potresti perdere alcuni dati! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Server WebRTC ICE @@ -10048,6 +10268,10 @@ Relays can access channel messages. Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti. alert message + + Your public address + No comment provided by engineer. + Your random profile Il tuo profilo casuale @@ -10245,6 +10469,10 @@ marked deleted chat item preview text chiamata… call status + + can't broadcast + No comment provided by engineer. + can't send messages impossibile inviare messaggi @@ -10901,6 +11129,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address indirizzo di contatto rimosso @@ -11222,6 +11454,10 @@ ultimo msg ricevuto: %2$@ \~barrato~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index a95203da15..ed338a0aac 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -185,9 +185,20 @@ %d 月 time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -348,11 +375,19 @@ channel relay bar progress with errors %u 件のメッセージがスキップされました。 No comment provided by engineer. + + (from owner) + chat link info line + (new) (新規) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (このデバイス v%@) @@ -445,6 +480,12 @@ channel relay bar progress with errors - などなど! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -543,6 +584,10 @@ time interval その他 No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact 新しい連絡先 @@ -666,9 +711,8 @@ swipe action アクティブな接続 No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。 + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -734,6 +778,10 @@ swipe action 追加されたメッセージサーバー No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -844,6 +892,14 @@ swipe action すべてのプロフィール profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -1364,7 +1420,7 @@ swipe action Business address - No comment provided by engineer. + chat link info line Business chats @@ -1540,12 +1596,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1560,6 +1625,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1572,6 +1641,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat チャット @@ -1949,6 +2022,10 @@ This is your own one-time link! リンク経由で接続 new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link ワンタイムリンクで接続 @@ -1958,6 +2035,10 @@ This is your own one-time link! Connect with %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected 接続中 @@ -2025,7 +2106,7 @@ This is your own one-time link! Connection error (AUTH) 接続エラー (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2075,6 +2156,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows 連絡先の許可 @@ -2166,11 +2251,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - 作成 - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2239,6 +2319,10 @@ This is your own one-time link! プロフィールを作成する No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2787,6 +2871,10 @@ alert button このグループではメンバー間のダイレクトメッセージが使用禁止です。 No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) 無効にする(設定の優先を維持) @@ -2972,6 +3060,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit 編集する @@ -2993,7 +3085,7 @@ chat item action Enable 有効 - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3045,6 +3137,10 @@ chat item action 即時通知を有効にしますか? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock ロックモード @@ -3211,7 +3307,7 @@ chat item action Error エラー - No comment provided by engineer. + conn error description Error aborting address change @@ -3523,6 +3619,10 @@ chat item action Error setting delivery receipts! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat チャット開始にエラー発生 @@ -3856,6 +3956,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -4040,7 +4144,7 @@ Error: %2$@ Group link グループのリンク - No comment provided by engineer. + chat link info line Group links @@ -4429,7 +4533,7 @@ More improvements are coming soon! Invalid connection link 無効な接続リンク - No comment provided by engineer. + conn error description Invalid display name! @@ -4488,6 +4592,10 @@ More improvements are coming soon! メンバーを招待する No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4675,6 +4783,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat SimpleXチャットで会話しよう @@ -4694,6 +4806,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -5160,6 +5276,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5186,6 +5306,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode 新しいパスコード @@ -5276,6 +5400,10 @@ This is your link for group %@! いいえ No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password アプリのパスワードはありません @@ -5420,6 +5548,10 @@ This is your link for group %@! 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。 No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5473,7 +5605,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5497,6 +5629,10 @@ new chat action 使い捨ての招待リンク No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5516,6 +5652,10 @@ VPN を有効にする必要があります。 オニオンのホストが使われません。 No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5718,6 +5858,10 @@ VPN を有効にする必要があります。 Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5726,6 +5870,10 @@ VPN を有効にする必要があります。 Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5747,6 +5895,10 @@ VPN を有効にする必要があります。 Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING回数 @@ -5800,6 +5952,10 @@ VPN を有効にする必要があります。 画像の貼り付け No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5982,6 +6138,10 @@ Error: %@ プライバシーの基準を新境地に No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. No comment provided by engineer. @@ -6047,9 +6207,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - 連絡先にプロフィール更新のお知らせが届きます。 + + Profile update will be sent to your SimpleX contacts. alert message @@ -6145,6 +6304,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications プッシュ通知 @@ -6358,6 +6521,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. 中継サーバーは必要な場合にのみ使用されます。 別の当事者があなたの IP アドレスを監視できます。 @@ -6372,6 +6539,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove 削除 @@ -6624,6 +6795,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6665,6 +6840,10 @@ chat item action 保存して、グループのメンバーにに知らせる No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6843,6 +7022,10 @@ chat item action セキュリティコード No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select 選択 @@ -6960,6 +7143,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. ギャラリーまたはカスタム キーボードから送信します。 @@ -6983,6 +7170,10 @@ chat item action 送信元が繋がりリクエストを削除したかもしれません。 No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. No comment provided by engineer. @@ -7242,11 +7433,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - アドレスを連絡先と共有しますか? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7280,9 +7474,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - 連絡先と共有する + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7687,6 +7884,10 @@ Relay address was used to set up this relay for the channel. 写真を撮影 No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7699,10 +7900,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7735,6 +7932,10 @@ Relay address was used to set up this relay for the channel. タップしてシークレットモードで参加 No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7828,6 +8029,10 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -8037,6 +8242,10 @@ It can happen because of some bug or when the connection is compromised.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection 新規に接続する場合 @@ -8286,7 +8495,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -8478,6 +8687,10 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8616,6 +8829,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8652,6 +8869,10 @@ To connect, please ask your contact to create another connection link and check 警告: 一部のデータが失われる可能性があります! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICEサーバ @@ -9190,6 +9411,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile あなたのランダム・プロフィール @@ -9372,6 +9597,10 @@ marked deleted chat item preview text 発信中… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -9998,6 +10227,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10288,6 +10521,10 @@ last received msg: %2$@ \~取り消し線~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 60d888e309..1e97b6721c 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -185,9 +185,20 @@ %d maanden time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u berichten zijn overgeslagen. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nieuw) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (dit apparaat v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - en meer! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Nog een paar dingen No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Een nieuw contact @@ -669,9 +714,8 @@ swipe action Actieve verbindingen No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -738,6 +782,10 @@ swipe action Berichtservers toegevoegd No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Extra accent @@ -857,6 +905,14 @@ swipe action Alle profielen profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Alle rapporten worden voor u gearchiveerd. @@ -1411,7 +1467,7 @@ swipe action Business address Zakelijk adres - No comment provided by engineer. + chat link info line Business chats @@ -1600,12 +1656,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1620,6 +1685,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1632,6 +1701,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Chat @@ -2048,6 +2121,10 @@ Dit is uw eigen eenmalige link! Maak verbinding via link new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Verbinden via een eenmalige link? @@ -2058,6 +2135,10 @@ Dit is uw eigen eenmalige link! Verbonden met %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Verbonden @@ -2126,7 +2207,7 @@ Dit is uw eigen eenmalige link! Connection error (AUTH) Verbindingsfout (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2184,6 +2265,10 @@ Dit is uw eigen eenmalige link! Verbindingen No comment provided by engineer. + + Contact address + chat link info line + Contact allows Contact maakt het mogelijk @@ -2283,11 +2368,6 @@ Dit is uw eigen eenmalige link! Juiste naam voor %@? alert message - - Create - Maak - No comment provided by engineer. - Create 1-time link Eenmalige link maken @@ -2360,6 +2440,10 @@ Dit is uw eigen eenmalige link! Maak je profiel aan No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Gemaakt @@ -2941,6 +3025,10 @@ alert button Directe berichten tussen leden zijn niet toegestaan. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Uitschakelen (overschrijvingen behouden) @@ -3147,6 +3235,10 @@ chat item action E2E versleutelde meldingen. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Bewerk @@ -3168,7 +3260,7 @@ chat item action Enable Inschakelen - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3223,6 +3315,10 @@ chat item action Onmiddellijke meldingen inschakelen? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Vergrendeling inschakelen @@ -3399,7 +3495,7 @@ chat item action Error Fout - No comment provided by engineer. + conn error description Error aborting address change @@ -3738,6 +3834,10 @@ chat item action Fout bij het instellen van ontvangst bevestiging! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Fout bij het starten van de chat @@ -4100,6 +4200,10 @@ server test error Voor alle moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Voor chatprofiel %@: @@ -4312,7 +4416,7 @@ Fout: %2$@ Group link Groep link - No comment provided by engineer. + chat link info line Group links @@ -4728,7 +4832,7 @@ Binnenkort meer verbeteringen! Invalid connection link Ongeldige verbinding link - No comment provided by engineer. + conn error description Invalid display name! @@ -4792,6 +4896,10 @@ Binnenkort meer verbeteringen! Nodig leden uit No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Uitnodigen voor een chat @@ -4990,6 +5098,10 @@ Dit is jouw link voor groep %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Laten we praten in SimpleX Chat @@ -5010,6 +5122,10 @@ Dit is jouw link voor groep %@! Koppel mobiele en desktop-apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Gekoppelde desktop opties @@ -5522,6 +5638,10 @@ Dit is jouw link voor groep %@! Netwerk decentralisatie No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden. @@ -5552,6 +5672,10 @@ Dit is jouw link voor groep %@! Nieuw token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nieuwe toegangscode @@ -5650,6 +5774,10 @@ Dit is jouw link voor groep %@! Nee No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Geen app wachtwoord @@ -5812,6 +5940,10 @@ Dit is jouw link voor groep %@! Geen gebruikers-ID's. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5873,7 +6005,7 @@ Dit is jouw link voor groep %@! OK OK - No comment provided by engineer. + alert button Off @@ -5897,6 +6029,10 @@ new chat action Eenmalige uitnodiging link No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5916,6 +6052,10 @@ Vereist het inschakelen van VPN. Onion hosts worden niet gebruikt. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Alleen chateigenaren kunnen voorkeuren wijzigen. @@ -6134,6 +6274,10 @@ Vereist het inschakelen van VPN. Of deel deze bestands link veilig No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Of laat deze code zien @@ -6144,6 +6288,10 @@ Vereist het inschakelen van VPN. Of om privé te delen No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organiseer chats in lijsten @@ -6169,6 +6317,10 @@ Vereist het inschakelen van VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING count @@ -6224,6 +6376,10 @@ Vereist het inschakelen van VPN. Afbeelding plakken No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Plak een link om te verbinden! @@ -6426,6 +6582,10 @@ Fout: %@ Privacy opnieuw gedefinieerd No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. @@ -6499,9 +6659,8 @@ Fout: %@ Profiel thema No comment provided by engineer. - - Profile update will be sent to your contacts. - Profiel update wordt naar uw contacten verzonden. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6605,6 +6764,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Proxy vereist wachtwoord No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push meldingen @@ -6838,6 +7001,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Relay server wordt alleen gebruikt als dat nodig is. Een andere partij kan uw IP-adres zien. @@ -6852,6 +7019,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Verwijderen @@ -7131,6 +7302,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Veilig bestanden ontvangen @@ -7175,6 +7350,10 @@ chat item action Opslaan en groep leden melden No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Opslaan en opnieuw verbinden @@ -7367,6 +7546,10 @@ chat item action Beveiligingscode No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Selecteer @@ -7494,6 +7677,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Stuur ze vanuit de galerij of aangepaste toetsenborden. @@ -7518,6 +7705,10 @@ chat item action De afzender heeft mogelijk het verbindingsverzoek verwijderd. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Het verzenden van ontvangst bevestiging wordt ingeschakeld voor alle contacten in alle zichtbare chatprofielen. @@ -7812,11 +8003,14 @@ chat item action Adres openbaar delen No comment provided by engineer. - - Share address with contacts? - Adres delen met contacten? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Delen vanuit andere apps. @@ -7854,9 +8048,12 @@ chat item action Delen op SimpleX No comment provided by engineer. - - Share with contacts - Delen met contacten + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8299,6 +8496,10 @@ Relay address was used to set up this relay for the channel. Foto nemen No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -8311,11 +8512,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Tik op SimpleX-adres maken in het menu om het later te maken. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8349,6 +8545,10 @@ Relay address was used to set up this relay for the channel. Tik hier om incognito lid te worden No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Tik hier om de link te plakken @@ -8449,6 +8649,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De code die u heeft gescand is geen SimpleX link QR-code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. De verbinding heeft de limiet van niet-afgeleverde berichten bereikt. Uw contactpersoon is mogelijk offline. @@ -8679,6 +8883,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Om ongewenste berichten te verbergen. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Om een nieuwe verbinding te maken @@ -8952,7 +9160,7 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Unsupported connection link Niet-ondersteunde verbindingslink - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9165,6 +9373,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik de app met één hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Gebruik een webpoort @@ -9316,6 +9528,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Wachten op desktop... @@ -9356,6 +9572,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Waarschuwing: u kunt sommige gegevens verliezen! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE servers @@ -9938,6 +10158,10 @@ Relays can access channel messages. Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden. alert message + + Your public address + No comment provided by engineer. + Your random profile Je willekeurige profiel @@ -10135,6 +10359,10 @@ marked deleted chat item preview text bellen… call status + + can't broadcast + No comment provided by engineer. + can't send messages kan geen berichten versturen @@ -10787,6 +11015,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address contactadres verwijderd @@ -11105,6 +11337,10 @@ laatst ontvangen bericht: %2$@ \~staking~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 57d22dd63b..104505c05c 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -185,9 +185,20 @@ %d miesięcy time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u pominiętych wiadomości. No comment provided by engineer. + + (from owner) + chat link info line + (new) (nowy) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (to urządzenie v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - i więcej! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Jeszcze kilka rzeczy No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Nowy kontakt @@ -670,9 +715,8 @@ swipe action Aktywne połączenia No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Dodano serwery wiadomości No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Dodatkowy akcent @@ -860,6 +908,14 @@ swipe action Wszystkie profile profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Wszystkie raporty zostaną dla Ciebie zarchiwizowane. @@ -1421,7 +1477,7 @@ swipe action Business address Adres firmowy - No comment provided by engineer. + chat link info line Business chats @@ -1612,12 +1668,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1697,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1713,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Czat @@ -2062,6 +2135,10 @@ To jest twój jednorazowy link! Połącz się przez link new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Połącz przez jednorazowy link @@ -2072,6 +2149,10 @@ To jest twój jednorazowy link! Połącz z %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Połączony @@ -2140,7 +2221,7 @@ To jest twój jednorazowy link! Connection error (AUTH) Błąd połączenia (UWIERZYTELNIANIE) - No comment provided by engineer. + conn error description Connection failed @@ -2199,6 +2280,10 @@ To jest twój jednorazowy link! Połączenia No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kontakt pozwala @@ -2299,11 +2384,6 @@ To jest twój jednorazowy link! Poprawić imię na %@? alert message - - Create - Utwórz - No comment provided by engineer. - Create 1-time link Utwórz jednorazowy link @@ -2377,6 +2457,10 @@ To jest twój jednorazowy link! Utwórz swój profil No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Utworzono @@ -2962,6 +3046,10 @@ alert button Bezpośrednie wiadomości między członkami są zabronione w tej grupie. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Wyłącz (zachowaj nadpisania) @@ -3168,6 +3256,10 @@ chat item action Powiadomienia szyfrowane E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Edytuj @@ -3190,7 +3282,7 @@ chat item action Enable Włącz - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3246,6 +3338,10 @@ chat item action Włączyć natychmiastowe powiadomienia? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Włącz blokadę @@ -3422,7 +3518,7 @@ chat item action Error Błąd - No comment provided by engineer. + conn error description Error aborting address change @@ -3766,6 +3862,10 @@ chat item action Błąd ustawiania potwierdzeń dostawy! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Błąd uruchamiania czatu @@ -4134,6 +4234,10 @@ server test error Dla wszystkich moderatorów No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Dla profilu czatu %@: @@ -4346,7 +4450,7 @@ Błąd: %2$@ Group link Link do grupy - No comment provided by engineer. + chat link info line Group links @@ -4765,7 +4869,7 @@ Wkrótce pojawią się kolejne ulepszenia! Invalid connection link Nieprawidłowy link połączenia - No comment provided by engineer. + conn error description Invalid display name! @@ -4830,6 +4934,10 @@ Wkrótce pojawią się kolejne ulepszenia! Zaproś członków No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Zaproś do czatu @@ -5030,6 +5138,10 @@ To jest twój link do grupy %@! Mniejszy ruch w sieciach komórkowych. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Porozmawiajmy w SimpleX Chat @@ -5050,6 +5162,10 @@ To jest twój link do grupy %@! Połącz mobile i komputerowe aplikacje! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Połączone opcje komputera @@ -5569,6 +5685,10 @@ To jest twój link do grupy %@! Decentralizacja sieci No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej. @@ -5599,6 +5719,10 @@ To jest twój link do grupy %@! Nowy token status text + + New 1-time link + No comment provided by engineer. + New Passcode Nowy Pin @@ -5698,6 +5822,10 @@ To jest twój link do grupy %@! Nie No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Brak hasła aplikacji @@ -5861,6 +5989,10 @@ To jest twój link do grupy %@! Brak identyfikatorów użytkownika. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5922,7 +6054,7 @@ To jest twój link do grupy %@! OK OK - No comment provided by engineer. + alert button Off @@ -5946,6 +6078,10 @@ new chat action Jednorazowy link zaproszenia No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5965,6 +6101,10 @@ Wymaga włączenia VPN. Hosty onion nie będą używane. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Tylko właściciele czatu mogą zmieniać preferencje. @@ -6193,6 +6333,10 @@ Wymaga włączenia VPN. Lub bezpiecznie udostępnij ten link pliku No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Lub pokaż ten kod @@ -6203,6 +6347,10 @@ Wymaga włączenia VPN. Lub udostępnij prywatnie No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Organizuj czaty jako listy @@ -6228,6 +6376,10 @@ Wymaga włączenia VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Liczba PINGÓW @@ -6283,6 +6435,10 @@ Wymaga włączenia VPN. Wklej obraz No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Wklej link, aby połączyć! @@ -6485,6 +6641,10 @@ Błąd: %@ Redefinicja prywatności No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów. @@ -6559,9 +6719,8 @@ Błąd: %@ Motyw profilu No comment provided by engineer. - - Profile update will be sent to your contacts. - Aktualizacja profilu zostanie wysłana do Twoich kontaktów. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6666,6 +6825,10 @@ Włącz w ustawianiach *Sieć i serwery* . Proxy wymaga hasła No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Powiadomienia push @@ -6899,6 +7062,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Serwer przekaźnikowy jest używany tylko w razie potrzeby. Inna strona może obserwować Twój adres IP. @@ -6913,6 +7080,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Usuń @@ -7196,6 +7367,10 @@ swipe action Proxy SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Bezpiecznie otrzymuj pliki @@ -7241,6 +7416,10 @@ chat item action Zapisz i powiadom członków grupy No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Zapisz i połącz ponownie @@ -7439,6 +7618,10 @@ chat item action Kod bezpieczeństwa No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Wybierz @@ -7569,6 +7752,10 @@ chat item action Wyślij prośbę bez wiadomości No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Wyślij je z galerii lub niestandardowych klawiatur. @@ -7594,6 +7781,10 @@ chat item action Nadawca mógł usunąć prośbę o połączenie. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Wysyłanie potwierdzeń dostawy zostanie włączone dla wszystkich kontaktów we wszystkich widocznych profilach czatu. @@ -7889,11 +8080,14 @@ chat item action Udostępnij adres publicznie No comment provided by engineer. - - Share address with contacts? - Udostępnić adres kontaktom? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Udostępnij z innych aplikacji. @@ -7933,9 +8127,12 @@ chat item action Udostępnij do SimpleX No comment provided by engineer. - - Share with contacts - Udostępnij kontaktom + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8382,6 +8579,10 @@ Relay address was used to set up this relay for the channel. Zrób zdjęcie No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Dotknij Połącz aby rozpocząć czat @@ -8397,11 +8598,6 @@ Relay address was used to set up this relay for the channel. Dotknij Połącz aby użyć bota No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Dotknij Stwórz adres SimpleX w menu aby utworzyć go później. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8436,6 +8632,10 @@ Relay address was used to set up this relay for the channel. Dotnij, aby dołączyć w trybie incognito No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Dotknij, aby wkleić link @@ -8537,6 +8737,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Kod, który zeskanowałeś nie jest kodem QR linku SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Połączenie osiągnęło limit niedostarczonych wiadomości, Twój kontakt może być offline. @@ -8770,6 +8974,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aby ukryć niechciane wiadomości. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Aby nawiązać nowe połączenie @@ -9046,7 +9254,7 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Unsupported connection link Nieobsługiwane łącze połączenia - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9266,6 +9474,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Korzystaj z aplikacji jedną ręką. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Użyj portu internetowego @@ -9418,6 +9630,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Oczekiwanie na komputer... @@ -9458,6 +9674,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Uwaga: możesz stracić niektóre dane! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Serwery WebRTC ICE @@ -10048,6 +10268,10 @@ Relays can access channel messages. Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów. alert message + + Your public address + No comment provided by engineer. + Your random profile Twój losowy profil @@ -10245,6 +10469,10 @@ marked deleted chat item preview text dzwonie… call status + + can't broadcast + No comment provided by engineer. + can't send messages nie można wysłać wiadomości @@ -10901,6 +11129,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address usunięto adres kontaktu @@ -11222,6 +11454,10 @@ ostatnia otrzymana wiadomość: %2$@ \~strajk~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 64f2546b30..9f64637aa4 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -185,9 +185,20 @@ %d мес. time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u сообщений пропущено. No comment provided by engineer. + + (from owner) + chat link info line + (new) (новое) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (это устройство v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - и прочее! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Еще несколько изменений No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Новый контакт @@ -670,9 +715,8 @@ swipe action Активные соединения No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Дополнительные серверы сообщений No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Дополнительный акцент @@ -859,6 +907,14 @@ swipe action Все профили profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Все сообщения о нарушениях будут заархивированы для вас. @@ -1419,7 +1475,7 @@ swipe action Business address Бизнес адрес - No comment provided by engineer. + chat link info line Business chats @@ -1610,12 +1666,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1630,6 +1695,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1642,6 +1711,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Разговор @@ -2060,6 +2133,10 @@ This is your own one-time link! Соединиться через ссылку new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Соединиться через одноразовую ссылку @@ -2070,6 +2147,10 @@ This is your own one-time link! Соединиться с %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Соединено @@ -2138,7 +2219,7 @@ This is your own one-time link! Connection error (AUTH) Ошибка соединения (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2196,6 +2277,10 @@ This is your own one-time link! Соединения No comment provided by engineer. + + Contact address + chat link info line + Contact allows Контакт разрешает @@ -2296,11 +2381,6 @@ This is your own one-time link! Исправить имя на %@? alert message - - Create - Создать - No comment provided by engineer. - Create 1-time link Создать одноразовую ссылку @@ -2374,6 +2454,10 @@ This is your own one-time link! Создать профиль No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Создано @@ -2957,6 +3041,10 @@ alert button Прямые сообщения между членами запрещены. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Выключить (кроме исключений) @@ -3163,6 +3251,10 @@ chat item action E2E зашифрованные нотификации. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Редактировать @@ -3185,7 +3277,7 @@ chat item action Enable Включить - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3241,6 +3333,10 @@ chat item action Включить мгновенные уведомления? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Включить блокировку @@ -3417,7 +3513,7 @@ chat item action Error Ошибка - No comment provided by engineer. + conn error description Error aborting address change @@ -3761,6 +3857,10 @@ chat item action Ошибка настроек отчётов о доставке! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Ошибка при запуске чата @@ -4128,6 +4228,10 @@ server test error Для всех модераторов No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Для профиля чата %@: @@ -4340,7 +4444,7 @@ Error: %2$@ Group link Ссылка группы - No comment provided by engineer. + chat link info line Group links @@ -4756,7 +4860,7 @@ More improvements are coming soon! Invalid connection link Ошибка в ссылке контакта - No comment provided by engineer. + conn error description Invalid display name! @@ -4820,6 +4924,10 @@ More improvements are coming soon! Пригласить членов группы No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Пригласить в разговор @@ -5020,6 +5128,10 @@ This is your link for group %@! Меньше трафик в мобильных сетях. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Давайте поговорим в SimpleX Chat @@ -5040,6 +5152,10 @@ This is your link for group %@! Свяжите мобильное и настольное приложения! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Опции связанных компьютеров @@ -5557,6 +5673,10 @@ This is your link for group %@! Децентрализация сети No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Ошибка сети - сообщение не было отправлено после многократных попыток. @@ -5587,6 +5707,10 @@ This is your link for group %@! Новый token status text + + New 1-time link + No comment provided by engineer. + New Passcode Новый Код @@ -5686,6 +5810,10 @@ This is your link for group %@! Нет No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Нет кода доступа @@ -5849,6 +5977,10 @@ This is your link for group %@! Без идентификаторов пользователей. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5910,7 +6042,7 @@ This is your link for group %@! OK OK - No comment provided by engineer. + alert button Off @@ -5934,6 +6066,10 @@ new chat action Одноразовая ссылка No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5953,6 +6089,10 @@ Requires compatible VPN. Onion хосты не используются. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Только владельцы разговора могут поменять предпочтения. @@ -6181,6 +6321,10 @@ Requires compatible VPN. Или передайте эту ссылку No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Или покажите этот код @@ -6191,6 +6335,10 @@ Requires compatible VPN. Или поделиться конфиденциально No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Организуйте чаты в списки @@ -6216,6 +6364,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Количество PING @@ -6271,6 +6423,10 @@ Requires compatible VPN. Вставить изображение No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Вставьте ссылку, чтобы соединиться! @@ -6473,6 +6629,10 @@ Error: %@ Более конфиденциальный No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. @@ -6547,9 +6707,8 @@ Error: %@ Тема профиля No comment provided by engineer. - - Profile update will be sent to your contacts. - Обновлённый профиль будет отправлен Вашим контактам. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6654,6 +6813,10 @@ Enable in *Network & servers* settings. Прокси требует пароль No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Доставка уведомлений @@ -6887,6 +7050,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Relay сервер используется только при необходимости. Другая сторона может видеть Ваш IP адрес. @@ -6901,6 +7068,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Удалить @@ -7183,6 +7354,10 @@ swipe action SOCKS прокси No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Получайте файлы безопасно @@ -7228,6 +7403,10 @@ chat item action Сохранить и уведомить членов группы No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Сохранить и переподключиться @@ -7421,6 +7600,10 @@ chat item action Код безопасности No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Выбрать @@ -7551,6 +7734,10 @@ chat item action Отправить запрос без сообщения No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Отправьте из галереи или из дополнительных клавиатур. @@ -7576,6 +7763,10 @@ chat item action Отправитель мог удалить запрос на соединение. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Отправка отчётов о доставке будет включена для всех контактов во всех видимых профилях чата. @@ -7871,11 +8062,14 @@ chat item action Поделитесь адресом No comment provided by engineer. - - Share address with contacts? - Поделиться адресом с контактами? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Поделитесь из других приложений. @@ -7915,9 +8109,12 @@ chat item action Поделиться в SimpleX No comment provided by engineer. - - Share with contacts - Поделиться с контактами + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8364,6 +8561,10 @@ Relay address was used to set up this relay for the channel. Сделать фото No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Нажмите Соединиться @@ -8379,11 +8580,6 @@ Relay address was used to set up this relay for the channel. Нажмите Соединиться, чтобы использовать бот No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Нажмите Создать адрес SimpleX в меню, чтобы создать его позже. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8418,6 +8614,10 @@ Relay address was used to set up this relay for the channel. Нажмите, чтобы вступить инкогнито No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Нажмите, чтобы вставить ссылку @@ -8519,6 +8719,10 @@ It can happen because of some bug or when the connection is compromised.Этот QR код не является SimpleX-ccылкой. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Соединение достигло предела недоставленных сообщений. Возможно, Ваш контакт не в сети. @@ -8752,6 +8956,10 @@ It can happen because of some bug or when the connection is compromised.Чтобы скрыть нежелательные сообщения. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Чтобы соединиться @@ -9028,7 +9236,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link Ссылка не поддерживается - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9248,6 +9456,10 @@ To connect, please ask your contact to create another connection link and check Используйте приложение одной рукой. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Использовать веб-порт @@ -9399,6 +9611,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Ожидается подключение компьютера... @@ -9439,6 +9655,10 @@ To connect, please ask your contact to create another connection link and check Предупреждение: Вы можете потерять какие то данные! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE серверы @@ -10029,6 +10249,10 @@ Relays can access channel messages. Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам. alert message + + Your public address + No comment provided by engineer. + Your random profile Случайный профиль @@ -10226,6 +10450,10 @@ marked deleted chat item preview text входящий звонок… call status + + can't broadcast + No comment provided by engineer. + can't send messages нельзя отправлять @@ -10881,6 +11109,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address удалён адрес контакта @@ -11202,6 +11434,10 @@ last received msg: %2$@ \~зачеркнуть~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 6ffea9646d..985804e8de 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -167,9 +167,20 @@ %d เดือน time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -203,10 +214,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -214,7 +233,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -324,10 +351,18 @@ channel relay bar progress with errors %u ข้อความที่ถูกข้าม No comment provided by engineer. + + (from owner) + chat link info line + (new) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) No comment provided by engineer. @@ -411,6 +446,12 @@ channel relay bar progress with errors - และอื่น ๆ! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -502,6 +543,10 @@ time interval อีกสองสามอย่าง No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact ผู้ติดต่อใหม่ @@ -614,9 +659,8 @@ swipe action Active connections No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -676,6 +720,10 @@ swipe action Added message servers No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent No comment provided by engineer. @@ -781,6 +829,14 @@ swipe action All profiles profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. No comment provided by engineer. @@ -1284,7 +1340,7 @@ swipe action Business address - No comment provided by engineer. + chat link info line Business chats @@ -1457,12 +1513,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1477,6 +1542,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1489,6 +1558,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat No comment provided by engineer. @@ -1855,6 +1928,10 @@ This is your own one-time link! เชื่อมต่อผ่านลิงก์ new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link new chat sheet title @@ -1863,6 +1940,10 @@ This is your own one-time link! Connect with %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected No comment provided by engineer. @@ -1922,7 +2003,7 @@ This is your own one-time link! Connection error (AUTH) การเชื่อมต่อผิดพลาด (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -1971,6 +2052,10 @@ This is your own one-time link! Connections No comment provided by engineer. + + Contact address + chat link info line + Contact allows ผู้ติดต่ออนุญาต @@ -2062,11 +2147,6 @@ This is your own one-time link! Correct name to %@? alert message - - Create - สร้าง - No comment provided by engineer. - Create 1-time link No comment provided by engineer. @@ -2133,6 +2213,10 @@ This is your own one-time link! สร้างโปรไฟล์ของคุณ No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created No comment provided by engineer. @@ -2675,6 +2759,10 @@ alert button ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) ปิดใช้งาน (เก็บการแทนที่) @@ -2859,6 +2947,10 @@ chat item action E2E encrypted notifications. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit แก้ไข @@ -2880,7 +2972,7 @@ chat item action Enable เปิดใช้งาน - No comment provided by engineer. + alert button Enable (keep overrides) @@ -2932,6 +3024,10 @@ chat item action เปิดใช้งานการแจ้งเตือนทันที? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock เปิดใช้งานการล็อค @@ -3096,7 +3192,7 @@ chat item action Error ผิดพลาด - No comment provided by engineer. + conn error description Error aborting address change @@ -3407,6 +3503,10 @@ chat item action เกิดข้อผิดพลาดในการตั้งค่าใบตอบรับการจัดส่ง! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat เกิดข้อผิดพลาดในการเริ่มแชท @@ -3740,6 +3840,10 @@ server test error For all moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: servers error @@ -3924,7 +4028,7 @@ Error: %2$@ Group link ลิงค์กลุ่ม - No comment provided by engineer. + chat link info line Group links @@ -4312,7 +4416,7 @@ More improvements are coming soon! Invalid connection link ลิงค์เชื่อมต่อไม่ถูกต้อง - No comment provided by engineer. + conn error description Invalid display name! @@ -4370,6 +4474,10 @@ More improvements are coming soon! เชิญสมาชิก No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat No comment provided by engineer. @@ -4557,6 +4665,10 @@ This is your link for group %@! Less traffic on mobile networks. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat มาคุยกันใน SimpleX Chat @@ -4576,6 +4688,10 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options No comment provided by engineer. @@ -5039,6 +5155,10 @@ This is your link for group %@! Network decentralization No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. snd error text @@ -5065,6 +5185,10 @@ This is your link for group %@! New token status text + + New 1-time link + No comment provided by engineer. + New Passcode รหัสผ่านใหม่ @@ -5154,6 +5278,10 @@ This is your link for group %@! เลขที่ No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password ไม่มีรหัสผ่านสำหรับแอป @@ -5297,6 +5425,10 @@ This is your link for group %@! แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5350,7 +5482,7 @@ This is your link for group %@! OK - No comment provided by engineer. + alert button Off @@ -5374,6 +5506,10 @@ new chat action ลิงก์คำเชิญแบบใช้ครั้งเดียว No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5391,6 +5527,10 @@ Requires compatible VPN. โฮสต์หัวหอมจะไม่ถูกใช้ No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. No comment provided by engineer. @@ -5592,6 +5732,10 @@ Requires compatible VPN. Or securely share this file link No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -5600,6 +5744,10 @@ Requires compatible VPN. Or to share privately No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists No comment provided by engineer. @@ -5621,6 +5769,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count จํานวน PING @@ -5674,6 +5826,10 @@ Requires compatible VPN. แปะภาพ No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! No comment provided by engineer. @@ -5856,6 +6012,10 @@ Error: %@ นิยามความเป็นส่วนตัวใหม่ No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. No comment provided by engineer. @@ -5920,9 +6080,8 @@ Error: %@ Profile theme No comment provided by engineer. - - Profile update will be sent to your contacts. - การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ + + Profile update will be sent to your SimpleX contacts. alert message @@ -6018,6 +6177,10 @@ Enable in *Network & servers* settings. Proxy requires password No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications การแจ้งเตือนแบบทันที @@ -6230,6 +6393,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. ใช้เซิร์ฟเวอร์รีเลย์ในกรณีที่จำเป็นเท่านั้น บุคคลอื่นสามารถสังเกตที่อยู่ IP ของคุณได้ @@ -6244,6 +6411,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove ลบ @@ -6496,6 +6667,10 @@ swipe action SOCKS proxy No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files No comment provided by engineer. @@ -6537,6 +6712,10 @@ chat item action บันทึกและแจ้งให้สมาชิกในกลุ่มทราบ No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect No comment provided by engineer. @@ -6715,6 +6894,10 @@ chat item action รหัสความปลอดภัย No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select เลือก @@ -6833,6 +7016,10 @@ chat item action Send request without message No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. ส่งจากแกลเลอรีหรือแป้นพิมพ์แบบกำหนดเอง @@ -6856,6 +7043,10 @@ chat item action ผู้ส่งอาจลบคําขอการเชื่อมต่อแล้ว No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. การส่งใบเสร็จรับการจัดส่งข้อความจะถูกเปิดในโปรไฟล์แชทที่มองเห็นได้ทั้งหมด @@ -7119,11 +7310,14 @@ chat item action Share address publicly No comment provided by engineer. - - Share address with contacts? - แชร์ที่อยู่กับผู้ติดต่อ? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. No comment provided by engineer. @@ -7157,9 +7351,12 @@ chat item action Share to SimpleX No comment provided by engineer. - - Share with contacts - แชร์กับผู้ติดต่อ + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -7561,6 +7758,10 @@ Relay address was used to set up this relay for the channel. ถ่ายภาพ No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat No comment provided by engineer. @@ -7573,10 +7774,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -7609,6 +7806,10 @@ Relay address was used to set up this relay for the channel. แตะเพื่อเข้าร่วมโหมดไม่ระบุตัวตน No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link No comment provided by engineer. @@ -7703,6 +7904,10 @@ It can happen because of some bug or when the connection is compromised.The code you scanned is not a SimpleX link QR code. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. No comment provided by engineer. @@ -7911,6 +8116,10 @@ It can happen because of some bug or when the connection is compromised.To hide unwanted messages. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection เพื่อสร้างการเชื่อมต่อใหม่ @@ -8160,7 +8369,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -8350,6 +8559,10 @@ To connect, please ask your contact to create another connection link and check Use the app with one hand. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port No comment provided by engineer. @@ -8488,6 +8701,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... No comment provided by engineer. @@ -8524,6 +8741,10 @@ To connect, please ask your contact to create another connection link and check คำเตือน: คุณอาจสูญเสียข้อมูลบางส่วน! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers เซิร์ฟเวอร์ WebRTC ICE @@ -9059,6 +9280,10 @@ Relays can access channel messages. Your profile was changed. If you save it, the updated profile will be sent to all your contacts. alert message + + Your public address + No comment provided by engineer. + Your random profile โปรไฟล์แบบสุ่มของคุณ @@ -9241,6 +9466,10 @@ marked deleted chat item preview text กำลังโทร… call status + + can't broadcast + No comment provided by engineer. + can't send messages No comment provided by engineer. @@ -9866,6 +10095,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address profile update event chat item @@ -10156,6 +10389,10 @@ last received msg: %2$@ \~ตี~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index f8890ca077..9bd287a3dd 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -185,9 +185,20 @@ %d ay time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u mesajlar atlandı. No comment provided by engineer. + + (from owner) + chat link info line + (new) (yeni) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (bu cihaz v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - ve fazlası! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Birkaç şey daha No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Yeni kişi @@ -670,9 +715,8 @@ swipe action Aktif bağlantılar No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Mesaj sunucuları eklendi No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Ek ana renk @@ -859,6 +907,14 @@ swipe action Tüm Profiller profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Tüm raporlar sizin için arşivlenecek. @@ -1419,7 +1475,7 @@ swipe action Business address İş adresi - No comment provided by engineer. + chat link info line Business chats @@ -1610,12 +1666,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1630,6 +1695,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1642,6 +1711,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Sohbet @@ -2060,6 +2133,10 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantı aracılığıyla bağlan new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Tek kullanımlık bağlantı aracılığıyla bağlan @@ -2070,6 +2147,10 @@ Bu senin kendi tek kullanımlık bağlantın! %@ ile bağlan new chat action + + Connect with someone + No comment provided by engineer. + Connected Bağlandı @@ -2138,7 +2219,7 @@ Bu senin kendi tek kullanımlık bağlantın! Connection error (AUTH) Bağlantı hatası (DOĞRULAMA) - No comment provided by engineer. + conn error description Connection failed @@ -2196,6 +2277,10 @@ Bu senin kendi tek kullanımlık bağlantın! Bağlantılar No comment provided by engineer. + + Contact address + chat link info line + Contact allows Kişi izin veriyor @@ -2296,11 +2381,6 @@ Bu senin kendi tek kullanımlık bağlantın! İsim %@ olarak düzeltilsin mi? alert message - - Create - Oluştur - No comment provided by engineer. - Create 1-time link Tek kullanımlık bağlantı oluştur @@ -2374,6 +2454,10 @@ Bu senin kendi tek kullanımlık bağlantın! Profilini oluştur No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Yaratıldı @@ -2957,6 +3041,10 @@ alert button Bu grupta üyeler arasında direkt mesajlaşma yasaktır. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Devre dışı bırak (geçersiz kılmaları koru) @@ -3163,6 +3251,10 @@ chat item action Uçtan uca şifrelenmiş bildirimler. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Düzenle @@ -3185,7 +3277,7 @@ chat item action Enable Etkinleştir - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3241,6 +3333,10 @@ chat item action Anlık bildirimler etkinleştirilsin mi? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Kilidi etkinleştir @@ -3417,7 +3513,7 @@ chat item action Error Hata - No comment provided by engineer. + conn error description Error aborting address change @@ -3760,6 +3856,10 @@ chat item action Görüldü ayarlanırken hata oluştu! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Sohbet başlatılırken hata oluştu @@ -4123,6 +4223,10 @@ server test error Tüm moderatörler için No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Sohbet profili için %@: @@ -4335,7 +4439,7 @@ Hata: %2$@ Group link Grup bağlantısı - No comment provided by engineer. + chat link info line Group links @@ -4752,7 +4856,7 @@ Daha fazla iyileştirme yakında geliyor! Invalid connection link Geçersiz bağlanma bağlantısı - No comment provided by engineer. + conn error description Invalid display name! @@ -4816,6 +4920,10 @@ Daha fazla iyileştirme yakında geliyor! Üyeleri davet et No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Sohbete davet et @@ -5016,6 +5124,10 @@ Bu senin grup için bağlantın %@! Mobil ağlarda daha az trafik. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Hadi SimpleX Chat'te konuşalım @@ -5036,6 +5148,10 @@ Bu senin grup için bağlantın %@! Telefon ve bilgisayar uygulamalarını bağla! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Bağlanmış bilgisayar ayarları @@ -5553,6 +5669,10 @@ Bu senin grup için bağlantın %@! Ağ merkeziyetsizliği No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu. @@ -5583,6 +5703,10 @@ Bu senin grup için bağlantın %@! Yeni token status text + + New 1-time link + No comment provided by engineer. + New Passcode Yeni şifre @@ -5682,6 +5806,10 @@ Bu senin grup için bağlantın %@! Hayır No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Uygulama şifresi yok @@ -5845,6 +5973,10 @@ Bu senin grup için bağlantın %@! Herhangi bir kullanıcı tanımlayıcısı yok. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5906,7 +6038,7 @@ Bu senin grup için bağlantın %@! OK TAMAM - No comment provided by engineer. + alert button Off @@ -5930,6 +6062,10 @@ new chat action Tek zamanlı bağlantı daveti No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5949,6 +6085,10 @@ VPN'nin etkinleştirilmesi gerekir. Onion ana bilgisayarları kullanılmayacaktır. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Yalnızca sohbet sahipleri tercihleri değiştirebilir. @@ -6177,6 +6317,10 @@ VPN'nin etkinleştirilmesi gerekir. Veya bu dosya bağlantısını güvenli bir şekilde paylaşın No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Veya bu kodu göster @@ -6187,6 +6331,10 @@ VPN'nin etkinleştirilmesi gerekir. Veya özel olarak paylaşmak için No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Sohbetleri listelere ayır @@ -6212,6 +6360,10 @@ VPN'nin etkinleştirilmesi gerekir. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING sayısı @@ -6267,6 +6419,10 @@ VPN'nin etkinleştirilmesi gerekir. Fotoğraf yapıştır No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Bağlanmak için bağlantıyı yapıştır! @@ -6469,6 +6625,10 @@ Hata: %@ Gizlilik yeniden tanımlandı No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez. @@ -6543,9 +6703,8 @@ Hata: %@ Profil teması No comment provided by engineer. - - Profile update will be sent to your contacts. - Profil güncellemesi kişilerinize gönderilecektir. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6650,6 +6809,10 @@ Enable in *Network & servers* settings. Proxy şifre gerektirir No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Anında bildirimler @@ -6883,6 +7046,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Yönlendirici sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir. @@ -6897,6 +7064,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Sil @@ -7179,6 +7350,10 @@ swipe action SOCKS vekili No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Dosyaları güvenle alın @@ -7224,6 +7399,10 @@ chat item action Kaydet ve grup üyelerine bildir No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Kayıt et ve yeniden bağlan @@ -7417,6 +7596,10 @@ chat item action Güvenlik kodu No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Seç @@ -7547,6 +7730,10 @@ chat item action Mesaj olmadan istek gönder No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Bunları galeriden veya özel klavyelerden gönder. @@ -7572,6 +7759,10 @@ chat item action Gönderici bağlantı isteğini silmiş olabilir. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Görüldü bilgisi, tüm görünür sohbet profillerindeki tüm kişiler için etkinleştirilecektir. @@ -7867,11 +8058,14 @@ chat item action Adresinizi herkese açık olarak paylaşın No comment provided by engineer. - - Share address with contacts? - Kişilerle adres paylaşılsın mı? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Diğer uygulamalardan paylaşın. @@ -7911,9 +8105,12 @@ chat item action SimpleX ile paylaş No comment provided by engineer. - - Share with contacts - Kişilerle paylaş + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8360,6 +8557,10 @@ Relay address was used to set up this relay for the channel. Fotoğraf çek No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Sohbet etmek için Bağlan'a dokunun @@ -8375,11 +8576,6 @@ Relay address was used to set up this relay for the channel. Botu kullanmak için Bağlan tuşuna bas No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Daha sonra oluşturmak için menüden BasitX adresi oluştur'a dokunun. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8414,6 +8610,10 @@ Relay address was used to set up this relay for the channel. Gizli katılmak için tıkla No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Bağlantıyı yapıştırmak için tıkla @@ -8515,6 +8715,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Taradığınız kod bir SimpleX bağlantı QR kodu değildir. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. Bağlantı, teslim edilmemiş mesajlar limitine ulaştı, kişiniz çevrimdışı olabilir. @@ -8748,6 +8952,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. İstenmeyen mesajları gizlemek için. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Yeni bir bağlantı oluşturmak için @@ -9023,7 +9231,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Unsupported connection link Desteklenmeyen bağlantı bağlantısı - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9243,6 +9451,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Uygulamayı tek elle kullan. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Web portunu kullan @@ -9394,6 +9606,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Bilgisayar için bekleniyor... @@ -9434,6 +9650,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Uyarı: Bazı verileri kaybedebilirsin! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE sunucuları @@ -10022,6 +10242,10 @@ Relays can access channel messages. Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir. alert message + + Your public address + No comment provided by engineer. + Your random profile Rasgele profiliniz @@ -10219,6 +10443,10 @@ marked deleted chat item preview text aranıyor… call status + + can't broadcast + No comment provided by engineer. + can't send messages mesaj gönderilemiyor @@ -10873,6 +11101,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address kişi adresi silindi @@ -11194,6 +11426,10 @@ son alınan msj: %2$@ \~çizik~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 839b4e2652..edff3a8a16 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -185,9 +185,20 @@ %d місяців time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors %u повідомлень пропущено. No comment provided by engineer. + + (from owner) + chat link info line + (new) (новий) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (цей пристрій v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - і багато іншого! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval Ще кілька речей No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact Новий контакт @@ -670,9 +715,8 @@ swipe action Активні з'єднання No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action Додано сервери повідомлень No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent Додатковий акцент @@ -859,6 +907,14 @@ swipe action Всі профілі profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. Всі скарги будуть заархівовані для вас. @@ -1415,7 +1471,7 @@ swipe action Business address Адреса підприємства - No comment provided by engineer. + chat link info line Business chats @@ -1606,12 +1662,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1626,6 +1691,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1638,6 +1707,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat Чат @@ -2056,6 +2129,10 @@ This is your own one-time link! Підключіться за посиланням new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link Під'єднатися за одноразовим посиланням @@ -2066,6 +2143,10 @@ This is your own one-time link! Підключитися до %@ new chat action + + Connect with someone + No comment provided by engineer. + Connected Підключено @@ -2134,7 +2215,7 @@ This is your own one-time link! Connection error (AUTH) Помилка підключення (AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2192,6 +2273,10 @@ This is your own one-time link! З'єднання No comment provided by engineer. + + Contact address + chat link info line + Contact allows Контакт дозволяє @@ -2291,11 +2376,6 @@ This is your own one-time link! Виправити ім'я на %@? alert message - - Create - Створити - No comment provided by engineer. - Create 1-time link Створити одноразове посилання @@ -2369,6 +2449,10 @@ This is your own one-time link! Створіть свій профіль No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created Створено @@ -2951,6 +3035,10 @@ alert button У цій групі заборонені прямі повідомлення між учасниками. No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) Вимкнути (зберегти перевизначення) @@ -3157,6 +3245,10 @@ chat item action Зашифровані сповіщення E2E. No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit Редагувати @@ -3179,7 +3271,7 @@ chat item action Enable Увімкнути - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3235,6 +3327,10 @@ chat item action Увімкнути миттєві сповіщення? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock Увімкнути блокування @@ -3411,7 +3507,7 @@ chat item action Error Помилка - No comment provided by engineer. + conn error description Error aborting address change @@ -3753,6 +3849,10 @@ chat item action Помилка встановлення підтвердження доставлення! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat Помилка запуску чату @@ -4115,6 +4215,10 @@ server test error Для всіх модераторів No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: Для профілю чату %@: @@ -4327,7 +4431,7 @@ Error: %2$@ Group link Посилання на групу - No comment provided by engineer. + chat link info line Group links @@ -4744,7 +4848,7 @@ More improvements are coming soon! Invalid connection link Неправильне посилання для підключення - No comment provided by engineer. + conn error description Invalid display name! @@ -4808,6 +4912,10 @@ More improvements are coming soon! Запросити учасників No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat Запросити в чат @@ -5008,6 +5116,10 @@ This is your link for group %@! Менше трафіку в мобільних мережах. No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat Поговоримо в чаті SimpleX @@ -5028,6 +5140,10 @@ This is your link for group %@! Зв'яжіть мобільні та десктопні додатки! 🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options Параметри пов'язаного робочого столу @@ -5543,6 +5659,10 @@ This is your link for group %@! Децентралізація мережі No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його. @@ -5573,6 +5693,10 @@ This is your link for group %@! Новий token status text + + New 1-time link + No comment provided by engineer. + New Passcode Новий пароль @@ -5672,6 +5796,10 @@ This is your link for group %@! Ні No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password Немає пароля програми @@ -5835,6 +5963,10 @@ This is your link for group %@! Ніяких ідентифікаторів користувачів. No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5896,7 +6028,7 @@ This is your link for group %@! OK ОК - No comment provided by engineer. + alert button Off @@ -5920,6 +6052,10 @@ new chat action Посилання на одноразове запрошення No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5939,6 +6075,10 @@ Requires compatible VPN. Onion хости не будуть використовуватися. No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. Лише власники чату можуть змінювати налаштування. @@ -6162,6 +6302,10 @@ Requires compatible VPN. Або безпечно поділіться цим посиланням на файл No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code Або покажіть цей код @@ -6172,6 +6316,10 @@ Requires compatible VPN. Або поділитися приватно No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists Організовуйте чати в списки @@ -6197,6 +6345,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count Кількість PING @@ -6252,6 +6404,10 @@ Requires compatible VPN. Вставити зображення No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! Вставте посилання для підключення! @@ -6454,6 +6610,10 @@ Error: %@ Конфіденційність переглянута No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. Приватні чати, групи та ваші контакти недоступні для операторів сервера. @@ -6528,9 +6688,8 @@ Error: %@ Тема профілю No comment provided by engineer. - - Profile update will be sent to your contacts. - Оновлення профілю буде надіслано вашим контактам. + + Profile update will be sent to your SimpleX contacts. alert message @@ -6635,6 +6794,10 @@ Enable in *Network & servers* settings. Проксі вимагає пароль No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications Push-сповіщення @@ -6868,6 +7031,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. Релейний сервер використовується тільки в разі потреби. Інша сторона може бачити вашу IP-адресу. @@ -6882,6 +7049,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove Видалити @@ -7163,6 +7334,10 @@ swipe action Проксі SOCKS No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files Безпечне отримання файлів @@ -7208,6 +7383,10 @@ chat item action Зберегти та повідомити учасників групи No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect Збережіть і підключіться знову @@ -7401,6 +7580,10 @@ chat item action Код безпеки No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select Виберіть @@ -7531,6 +7714,10 @@ chat item action Надіслати запит без повідомлення No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. Надсилайте їх із галереї чи власних клавіатур. @@ -7556,6 +7743,10 @@ chat item action Можливо, відправник видалив запит на підключення. No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. Надсилання підтверджень доставки буде ввімкнено для всіх контактів у всіх видимих профілях чату. @@ -7851,11 +8042,14 @@ chat item action Поділіться адресою публічно No comment provided by engineer. - - Share address with contacts? - Поділіться адресою з контактами? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. Діліться з інших програм. @@ -7895,9 +8089,12 @@ chat item action Поділіться з SimpleX No comment provided by engineer. - - Share with contacts - Поділіться з контактами + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8344,6 +8541,10 @@ Relay address was used to set up this relay for the channel. Сфотографуйте No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat Натисніть Підключитися до чату @@ -8358,11 +8559,6 @@ Relay address was used to set up this relay for the channel. Tap Connect to use bot No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше. - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8397,6 +8593,10 @@ Relay address was used to set up this relay for the channel. Натисніть, щоб приєднатися інкогніто No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link Натисніть, щоб вставити посилання @@ -8498,6 +8698,10 @@ It can happen because of some bug or when the connection is compromised.Відсканований вами код не є QR-кодом посилання SimpleX. No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. З'єднання досягло ліміту недоставлених повідомлень, ваш контакт може бути офлайн. @@ -8730,6 +8934,10 @@ It can happen because of some bug or when the connection is compromised.Приховати небажані повідомлення. No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection Щоб створити нове з'єднання @@ -9004,7 +9212,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link Несумісне посилання для підключення - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9224,6 +9432,10 @@ To connect, please ask your contact to create another connection link and check Використовуйте додаток однією рукою. No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port Використовувати веб-порт @@ -9375,6 +9587,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... Чекаємо на десктопну версію... @@ -9415,6 +9631,10 @@ To connect, please ask your contact to create another connection link and check Попередження: ви можете втратити деякі дані! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers Сервери WebRTC ICE @@ -10003,6 +10223,10 @@ Relays can access channel messages. Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам. alert message + + Your public address + No comment provided by engineer. + Your random profile Ваш випадковий профіль @@ -10200,6 +10424,10 @@ marked deleted chat item preview text дзвоніть… call status + + can't broadcast + No comment provided by engineer. + can't send messages не можна надсилати @@ -10854,6 +11082,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address видалено контактну адресу @@ -11173,6 +11405,10 @@ last received msg: %2$@ \~закреслити~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index d981147cfe..3a95e6aaa0 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -185,9 +185,20 @@ %d 月 time interval - - %d relays - channel relay bar + + %d relays failed + channel relay bar +channel subscriber relay bar + + + %d relays not active + channel relay bar +channel subscriber relay bar + + + %d relays removed + channel relay bar +channel subscriber relay bar %d sec @@ -222,10 +233,18 @@ channel creation progress channel relay bar progress + + %1$d/%2$d relays active, %3$d errors + channel relay bar + %1$d/%2$d relays active, %3$d failed channel creation progress with errors -channel relay bar progress with errors +channel relay bar + + + %1$d/%2$d relays active, %3$d removed + channel relay bar %1$d/%2$d relays connected @@ -233,7 +252,15 @@ channel relay bar progress with errors %1$d/%2$d relays connected, %3$d errors - channel subscriber relay bar progress with errors + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d failed + channel subscriber relay bar + + + %1$d/%2$d relays connected, %3$d removed + channel subscriber relay bar %lld @@ -349,11 +376,19 @@ channel relay bar progress with errors 已跳过 %u 条消息。 No comment provided by engineer. + + (from owner) + chat link info line + (new) (新) No comment provided by engineer. + + (signed) + chat link info line + (this device v%@) (此设备 v%@) @@ -446,6 +481,12 @@ channel relay bar progress with errors - 以及更多! No comment provided by engineer. + + - opt-in to send link previews. +- prevent hyperlink phishing. +- remove link tracking. + No comment provided by engineer. + - optionally notify deleted contacts. - profile names with spaces. @@ -544,6 +585,10 @@ time interval 一些杂项 No comment provided by engineer. + + A link for one person to connect + No comment provided by engineer. + A new contact 新联系人 @@ -670,9 +715,8 @@ swipe action 活动连接 No comment provided by engineer. - - Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 + + Add address to your profile, so that your SimpleX contacts can share it with other people. Profile update will be sent to your SimpleX contacts. No comment provided by engineer. @@ -740,6 +784,10 @@ swipe action 已添加消息服务器 No comment provided by engineer. + + Adding relays will be supported later. + No comment provided by engineer. + Additional accent 附加重音 @@ -860,6 +908,14 @@ swipe action 所有配置文件 profile dropdown + + All relays failed + No comment provided by engineer. + + + All relays removed + No comment provided by engineer. + All reports will be archived for you. 将为你存档所有举报。 @@ -1421,7 +1477,7 @@ swipe action Business address 企业地址 - No comment provided by engineer. + chat link info line Business chats @@ -1612,12 +1668,21 @@ set passcode view Channel full name (optional) No comment provided by engineer. + + Channel has no active relays. Please try to join later. + alert message +alert subtitle + Channel image No comment provided by engineer. Channel link + chat link info line + + + Channel preferences No comment provided by engineer. @@ -1632,6 +1697,10 @@ set passcode view Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. alert message + + Channel temporarily unavailable + alert title + Channel will be deleted for all subscribers - this cannot be undone! No comment provided by engineer. @@ -1644,6 +1713,10 @@ set passcode view Channel will start working with %1$d of %2$d relays. Proceed? alert message + + Channels + No comment provided by engineer. + Chat 聊天 @@ -2062,6 +2135,10 @@ This is your own one-time link! 通过链接连接 new chat sheet title + + Connect via link or QR code + No comment provided by engineer. + Connect via one-time link 通过一次性链接连接 @@ -2072,6 +2149,10 @@ This is your own one-time link! 与 %@连接 new chat action + + Connect with someone + No comment provided by engineer. + Connected 已连接 @@ -2140,7 +2221,7 @@ This is your own one-time link! Connection error (AUTH) 连接错误(AUTH) - No comment provided by engineer. + conn error description Connection failed @@ -2197,6 +2278,10 @@ This is your own one-time link! 连接 No comment provided by engineer. + + Contact address + chat link info line + Contact allows 联系人允许 @@ -2297,11 +2382,6 @@ This is your own one-time link! 将名称更正为 %@? alert message - - Create - 创建 - No comment provided by engineer. - Create 1-time link 创建一次性链接 @@ -2375,6 +2455,10 @@ This is your own one-time link! 创建您的资料 No comment provided by engineer. + + Create your public address + No comment provided by engineer. + Created 已创建 @@ -2959,6 +3043,10 @@ alert button 此群禁止成员间私信。 No comment provided by engineer. + + Disable + alert button + Disable (keep overrides) 禁用(保留覆盖) @@ -3165,6 +3253,10 @@ chat item action 端到端加密的通知。 No comment provided by engineer. + + Easier to invite your friends 👋 + No comment provided by engineer. + Edit 编辑 @@ -3187,7 +3279,7 @@ chat item action Enable 启用 - No comment provided by engineer. + alert button Enable (keep overrides) @@ -3243,6 +3335,10 @@ chat item action 启用即时通知? No comment provided by engineer. + + Enable link previews? + alert title + Enable lock 启用锁定 @@ -3419,7 +3515,7 @@ chat item action Error 错误 - No comment provided by engineer. + conn error description Error aborting address change @@ -3762,6 +3858,10 @@ chat item action 设置送达回执出错! No comment provided by engineer. + + Error sharing channel + alert title + Error starting chat 启动聊天错误 @@ -4130,6 +4230,10 @@ server test error 所有 moderators No comment provided by engineer. + + For anyone to reach you + No comment provided by engineer. + For chat profile %@: 为聊天资料 %@: @@ -4342,7 +4446,7 @@ Error: %2$@ Group link 群组链接 - No comment provided by engineer. + chat link info line Group links @@ -4760,7 +4864,7 @@ More improvements are coming soon! Invalid connection link 无效的连接链接 - No comment provided by engineer. + conn error description Invalid display name! @@ -4825,6 +4929,10 @@ More improvements are coming soon! 邀请成员 No comment provided by engineer. + + Invite someone privately + No comment provided by engineer. + Invite to chat 邀请加入聊天 @@ -5025,6 +5133,10 @@ This is your link for group %@! 消耗更少的移动网络数据。 No comment provided by engineer. + + Let someone connect to you + No comment provided by engineer. + Let's talk in SimpleX Chat 让我们一起在 SimpleX Chat 里聊天 @@ -5045,6 +5157,10 @@ This is your link for group %@! 连接移动端和桌面端应用程序!🔗 No comment provided by engineer. + + Link signature verified. + owner verification + Linked desktop options 已链接桌面选项 @@ -5563,6 +5679,10 @@ This is your link for group %@! 网络去中心化 No comment provided by engineer. + + Network error + conn error description + Network issues - message expired after many attempts to send it. 网络问题 - 消息在多次尝试发送后过期。 @@ -5593,6 +5713,10 @@ This is your link for group %@! token status text + + New 1-time link + No comment provided by engineer. + New Passcode 新密码 @@ -5692,6 +5816,10 @@ This is your link for group %@! No comment provided by engineer. + + No active relays + No comment provided by engineer. + No app password 没有应用程序密码 @@ -5855,6 +5983,10 @@ This is your link for group %@! 没有用户标识符。 No comment provided by engineer. + + Non-profit governance + No comment provided by engineer. + Not all relays connected alert title @@ -5916,7 +6048,7 @@ This is your link for group %@! OK 好的 - No comment provided by engineer. + alert button Off @@ -5940,6 +6072,10 @@ new chat action 一次性邀请链接 No comment provided by engineer. + + One-time link + chat link info line + Onion hosts will be **required** for connection. Requires compatible VPN. @@ -5959,6 +6095,10 @@ Requires compatible VPN. 将不会使用 Onion 主机。 No comment provided by engineer. + + Only channel owners can change channel preferences. + No comment provided by engineer. + Only chat owners can change preferences. 仅聊天所有人可更改首选项。 @@ -6187,6 +6327,10 @@ Requires compatible VPN. 或安全地分享此文件链接 No comment provided by engineer. + + Or show QR in person or via video call. + No comment provided by engineer. + Or show this code 或者显示此码 @@ -6197,6 +6341,10 @@ Requires compatible VPN. 或者私下分享 No comment provided by engineer. + + Or use this QR - print or show online. + No comment provided by engineer. + Organize chats into lists 将聊天组织到列表 @@ -6222,6 +6370,10 @@ Requires compatible VPN. Owners No comment provided by engineer. + + Ownership: you can run your own relays. + No comment provided by engineer. + PING count PING 次数 @@ -6277,6 +6429,10 @@ Requires compatible VPN. 粘贴图片 No comment provided by engineer. + + Paste link / Scan + No comment provided by engineer. + Paste link to connect! 粘贴链接以连接! @@ -6479,6 +6635,10 @@ Error: %@ 重新定义隐私 No comment provided by engineer. + + Privacy: for owners and subscribers. + No comment provided by engineer. + Private chats, groups and your contacts are not accessible to server operators. 服务器运营方无法访问私密聊天、群组和你的联系人。 @@ -6553,9 +6713,8 @@ Error: %@ 个人资料主题 No comment provided by engineer. - - Profile update will be sent to your contacts. - 个人资料更新将被发送给您的联系人。 + + Profile update will be sent to your SimpleX contacts. alert message @@ -6660,6 +6819,10 @@ Enable in *Network & servers* settings. 代理需要密码 No comment provided by engineer. + + Public channels - speak freely 🚀 + No comment provided by engineer. + Push notifications 推送通知 @@ -6892,6 +7055,10 @@ swipe action Relay link No comment provided by engineer. + + Relay results: + alert message + Relay server is only used if necessary. Another party can observe your IP address. 中继服务器仅在必要时使用。其他人可能会观察到您的IP地址。 @@ -6906,6 +7073,10 @@ swipe action Relay test failed! No comment provided by engineer. + + Reliability: many relays per channel. + No comment provided by engineer. + Remove 移除 @@ -7188,6 +7359,10 @@ swipe action SOCKS代理 No comment provided by engineer. + + Safe web links + No comment provided by engineer. + Safely receive files 安全接收文件 @@ -7233,6 +7408,10 @@ chat item action 保存并通知群组成员 No comment provided by engineer. + + Save and notify subscribers + No comment provided by engineer. + Save and reconnect 保存并重新连接 @@ -7431,6 +7610,10 @@ chat item action 安全码 No comment provided by engineer. + + Security: owners hold channel keys. + No comment provided by engineer. + Select 选择 @@ -7561,6 +7744,10 @@ chat item action 发送无消息请求 No comment provided by engineer. + + Send the link via any messenger - it's secure. Ask to paste into SimpleX. + No comment provided by engineer. + Send them from gallery or custom keyboards. 发送它们来自图库或自定义键盘。 @@ -7586,6 +7773,10 @@ chat item action 发送人可能已删除连接请求。 No comment provided by engineer. + + Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + alert message + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. 将对所有可见聊天配置文件中的所有联系人启用送达回执功能。 @@ -7881,11 +8072,14 @@ chat item action 公开分享地址 No comment provided by engineer. - - Share address with contacts? - 与联系人分享地址? + + Share address with SimpleX contacts? alert title + + Share channel + No comment provided by engineer. + Share from other apps. 从其他应用程序共享。 @@ -7925,9 +8119,12 @@ chat item action 分享到 SimpleX No comment provided by engineer. - - Share with contacts - 与联系人分享 + + Share via chat + No comment provided by engineer. + + + Share with SimpleX contacts No comment provided by engineer. @@ -8373,6 +8570,10 @@ Relay address was used to set up this relay for the channel. 拍照 No comment provided by engineer. + + Talk to someone + No comment provided by engineer. + Tap Connect to chat 轻按连接进行聊天 @@ -8388,11 +8589,6 @@ Relay address was used to set up this relay for the channel. 轻按“连接”使用机器人 No comment provided by engineer. - - Tap Create SimpleX address in the menu to create it later. - 要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址” - No comment provided by engineer. - Tap Join channel No comment provided by engineer. @@ -8427,6 +8623,10 @@ Relay address was used to set up this relay for the channel. 点击以加入隐身聊天 No comment provided by engineer. + + Tap to open + No comment provided by engineer. + Tap to paste link 轻按粘贴链接 @@ -8528,6 +8728,10 @@ It can happen because of some bug or when the connection is compromised.您扫描的码不是 SimpleX 链接的二维码。 No comment provided by engineer. + + The connection reached the limit of undelivered messages + conn error description + The connection reached the limit of undelivered messages, your contact may be offline. 连接达到了未送达消息上限,你的联系人可能处于离线状态。 @@ -8759,6 +8963,10 @@ It can happen because of some bug or when the connection is compromised.隐藏不需要的信息。 No comment provided by engineer. + + To make SimpleX Network last. + No comment provided by engineer. + To make a new connection 建立新连接 @@ -9034,7 +9242,7 @@ To connect, please ask your contact to create another connection link and check Unsupported connection link 不支持的连接链接 - No comment provided by engineer. + conn error description Up to 100 last messages are sent to new members. @@ -9254,6 +9462,10 @@ To connect, please ask your contact to create another connection link and check 用一只手使用应用程序。 No comment provided by engineer. + + Use this address in your social media profile, website, or email signature. + No comment provided by engineer. + Use web port 使用 web 端口 @@ -9406,6 +9618,10 @@ To connect, please ask your contact to create another connection link and check Wait response relay test step + + Waiting for channel owner to add relays. + No comment provided by engineer. + Waiting for desktop... 正在等待桌面... @@ -9446,6 +9662,10 @@ To connect, please ask your contact to create another connection link and check 警告:您可能会丢失部分数据! No comment provided by engineer. + + We made connecting simpler for new users. + No comment provided by engineer. + WebRTC ICE servers WebRTC ICE 服务器 @@ -10033,6 +10253,10 @@ Relays can access channel messages. 您的个人资料已修改。如果进行保存,更新后的个人资料将发送到所有联系人。 alert message + + Your public address + No comment provided by engineer. + Your random profile 您的随机资料 @@ -10229,6 +10453,10 @@ marked deleted chat item preview text 呼叫中…… call status + + can't broadcast + No comment provided by engineer. + can't send messages 无法发送消息 @@ -10883,6 +11111,10 @@ time to disappear removed (%d attempts) receive error chat item + + removed by operator + No comment provided by engineer. + removed contact address 删除了联系地址 @@ -11204,6 +11436,10 @@ last received msg: %2$@ \~删去~ No comment provided by engineer. + + ⚠️ Signature verification failed: %@. + owner verification + From a3fde4daa17e7d1e5ea1f8eb908de3a2dab61f4d Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:00:13 +0000 Subject: [PATCH 11/77] simplex-chat-nodejs: add PostgreSQL backend build support (#6845) * simplex-chat-nodejs: add PostgreSQL backend build support * simplex-chat-nodejs: fix postgres install command in README --- .github/workflows/build.yml | 114 +++++++++++++++++- packages/simplex-chat-nodejs/README.md | 26 ++++ .../simplex-chat-nodejs/src/download-libs.js | 27 ++++- scripts/desktop/build-lib-linux.sh | 9 +- 4 files changed, 168 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7c6cb7a3a7..6c588f1f7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -377,6 +377,100 @@ jobs: exit 1 fi +# ================================= +# Linux PostgreSQL Library Build +# ================================= + + build-linux-postgres: + name: "ubuntu-22.04-x86_64 (Postgres lib), GHC: ${{ needs.variables.outputs.GHC_VER }}" + needs: [maybe-release, variables] + runs-on: ubuntu-22.04 + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Get UID and GID + id: ids + run: | + echo "uid=$(id -u)" >> $GITHUB_OUTPUT + echo "gid=$(id -g)" >> $GITHUB_OUTPUT + + - name: Free disk space + shell: bash + run: ./scripts/ci/linux_util_free_space.sh + + - name: Restore cached build + uses: actions/cache@v4 + with: + path: | + ~/.cabal/store + dist-newstyle + key: ubuntu-22.04-x86_64-postgres-ghc${{ needs.variables.outputs.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }} + + - name: Set up Docker Buildx + uses: simplex-chat/docker-setup-buildx-action@v3 + + - name: Build and cache Docker image + uses: simplex-chat/docker-build-push-action@v6 + with: + context: . + load: true + file: Dockerfile.build + tags: build/22.04:latest + build-args: | + TAG=22.04 + HASH=sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5 + GHC=${{ needs.variables.outputs.GHC_VER }} + USER_UID=${{ steps.ids.outputs.uid }} + USER_GID=${{ steps.ids.outputs.gid }} + + - name: Start container + shell: bash + run: | + docker run -t -d \ + --name builder \ + -v ~/.cabal:/root/.cabal \ + -v /home/runner/work/_temp:/home/runner/work/_temp \ + -v ${{ github.workspace }}:/project \ + build/22.04:latest + + - name: Prepare cabal.project.local + shell: bash + run: | + echo "ignore-project: False" >> cabal.project.local + echo "package direct-sqlcipher" >> cabal.project.local + echo " flags: +openssl" >> cabal.project.local + + - name: Build postgres library + shell: docker exec -t builder sh -eu {0} + run: | + cabal clean + cabal update + scripts/desktop/build-lib-linux.sh postgres + + - name: Copy libs from container + shell: bash + run: | + ARCH=x86_64 + GHC_VER=${{ needs.variables.outputs.GHC_VER }} + BUILD_DIR=$(echo dist-newstyle/build/${ARCH}-linux/ghc-${GHC_VER}/simplex-chat-*) + mkdir -p postgres-libs + cp ${BUILD_DIR}/build/libsimplex.so postgres-libs/ + cp ${BUILD_DIR}/build/deps/* postgres-libs/ + + - name: Upload postgres libs artifact + uses: actions/upload-artifact@v4 + with: + name: simplex-libs-linux-postgres-x86_64 + path: postgres-libs/ + + - name: Fix permissions for cache + shell: bash + run: | + sudo chmod -R 777 dist-newstyle ~/.cabal + sudo chown -R $(id -u):$(id -g) dist-newstyle ~/.cabal + # ========================= # MacOS Build # ========================= @@ -613,7 +707,7 @@ jobs: release-nodejs-libs: runs-on: ubuntu-latest - needs: [build-linux, build-macos] + needs: [build-linux, build-linux-postgres, build-macos] if: startsWith(github.ref, 'refs/tags/v') && (!cancelled()) steps: - name: Checkout current repository @@ -622,6 +716,13 @@ jobs: - name: Install packages for archiving run: sudo apt install -y msitools gcc-mingw-w64 + - name: Download postgres libs artifact + if: needs.build-linux-postgres.result == 'success' + uses: actions/download-artifact@v4 + with: + name: simplex-libs-linux-postgres-x86_64 + path: ${{ runner.temp }}/postgres-libs + - name: Build archives run: | INIT_DIR='${{ runner.temp }}/artifacts' @@ -678,6 +779,17 @@ jobs: zip -r "${PREFIX}-windows-x86_64.zip" libs mv "${PREFIX}-windows-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR" + # Linux PostgreSQL (only if postgres build succeeded) + # ------------------------------------------------- + POSTGRES_LIBS='${{ runner.temp }}/postgres-libs' + if [ -d "$POSTGRES_LIBS" ]; then + mkdir -p linux-postgres/libs + cp "${POSTGRES_LIBS}"/*.so linux-postgres/libs/ + cd linux-postgres + zip -r "${PREFIX}-linux-x86_64-postgres.zip" libs + mv "${PREFIX}-linux-x86_64-postgres.zip" "$RELEASE_DIR" && cd "$INIT_DIR" + fi + - name: Create release in libs repo and upload artifacts uses: softprops/action-gh-release@v2 with: diff --git a/packages/simplex-chat-nodejs/README.md b/packages/simplex-chat-nodejs/README.md index e75dab3f96..ba5a758e0f 100644 --- a/packages/simplex-chat-nodejs/README.md +++ b/packages/simplex-chat-nodejs/README.md @@ -62,6 +62,32 @@ There is an example with more options in [./examples/squaring-bot.ts](./examples You can run it with: `npx ts-node ./examples/squaring-bot.ts` +## PostgreSQL backend + +By default, the package uses SQLite. To use PostgreSQL instead: + +```bash +npm install simplex-chat --simplex_backend=postgres +``` + +Or persist the setting in `.npmrc`: + +```ini +simplex_backend=postgres +``` + +### Prerequisites (PostgreSQL) + +- `libpq5` must be installed on the host system (`apt install libpq5` on Debian/Ubuntu) +- PostgreSQL backend is only available for Linux x86_64 +- A PostgreSQL server accessible via connection string + +### API difference + +With the PostgreSQL backend, `chatMigrateInit` arguments change meaning: +- First argument: schema prefix (instead of DB file prefix) +- Second argument: connection string (instead of encryption key) + ## Documentation The library docs are [here](./docs/README.md). diff --git a/packages/simplex-chat-nodejs/src/download-libs.js b/packages/simplex-chat-nodejs/src/download-libs.js index 25d4127ea4..bd30e8f56d 100644 --- a/packages/simplex-chat-nodejs/src/download-libs.js +++ b/packages/simplex-chat-nodejs/src/download-libs.js @@ -5,6 +5,18 @@ const extract = require('extract-zip'); const GITHUB_REPO = 'simplex-chat/simplex-chat-libs'; const RELEASE_TAG = 'v6.5.0-beta.9'; +const BACKEND = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || 'sqlite').toLowerCase(); + +if (BACKEND !== 'sqlite' && BACKEND !== 'postgres') { + console.error(`✗ Invalid SIMPLEX_BACKEND: "${BACKEND}". Must be "sqlite" or "postgres".`); + process.exit(1); +} + +if (BACKEND === 'postgres' && (process.platform !== 'linux' || process.arch !== 'x64')) { + console.error(`✗ SIMPLEX_BACKEND=postgres is only supported on Linux x86_64.`); + process.exit(1); +} + const ROOT_DIR = process.cwd(); // Root of the package being installed const LIBS_DIR = path.join(ROOT_DIR, 'libs') const INSTALLED_FILE = path.join(LIBS_DIR, 'installed.txt'); @@ -56,11 +68,12 @@ function isAlreadyInstalled() { try { const installedVersion = fs.readFileSync(INSTALLED_FILE, 'utf-8').trim(); - if (installedVersion === RELEASE_TAG) { - console.log(`✓ Libraries version ${RELEASE_TAG} already installed`); + const expectedVersion = `${RELEASE_TAG}:${BACKEND}`; + if (installedVersion === expectedVersion) { + console.log(`✓ Libraries version ${RELEASE_TAG}:${BACKEND} already installed`); return true; } else { - console.log(`Version mismatch: installed ${installedVersion}, need ${RELEASE_TAG}`); + console.log(`Version mismatch: installed ${installedVersion}, need ${expectedVersion}`); cleanLibsDirectory(); return false; } @@ -79,12 +92,14 @@ async function install() { const { platformName, archName } = getPlatformInfo(); const repoName = GITHUB_REPO.split('/')[1]; - const zipFilename = `${repoName}-${platformName}-${archName}.zip`; + const backendSuffix = BACKEND === 'postgres' ? '-postgres' : ''; + const zipFilename = `${repoName}-${platformName}-${archName}${backendSuffix}.zip`; const ZIP_URL = `https://github.com/${GITHUB_REPO}/releases/download/${RELEASE_TAG}/${zipFilename}`; const ZIP_PATH = path.join(ROOT_DIR, zipFilename); const TEMP_EXTRACT_DIR = path.join(ROOT_DIR, '.temp-extract'); console.log(`Detected: ${platformName} ${archName}`); + console.log(`Backend: ${BACKEND}`); console.log(`Downloading: ${zipFilename}`); // Create libs directory @@ -124,8 +139,8 @@ async function install() { } // Write installed.txt with version - fs.writeFileSync(INSTALLED_FILE, RELEASE_TAG, 'utf-8'); - console.log(`✓ Wrote version ${RELEASE_TAG} to installed.txt`); + fs.writeFileSync(INSTALLED_FILE, `${RELEASE_TAG}:${BACKEND}`, 'utf-8'); + console.log(`✓ Wrote version ${RELEASE_TAG}:${BACKEND} to installed.txt`); // Cleanup fs.rmSync(TEMP_EXTRACT_DIR, { recursive: true, force: true }); diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index 7868a125b6..a2684b87d2 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -8,6 +8,7 @@ function readlink() { OS=linux ARCH="$(uname -m)" +DATABASE_BACKEND="${1:-sqlite}" GHC_VERSION=9.6.3 if [ "$ARCH" == "aarch64" ]; then @@ -25,7 +26,13 @@ for elem in "${exports[@]}"; do count=$(grep -R "$elem$" libsimplex.dll.def | wc for elem in "${exports[@]}"; do count=$(grep -R "\"$elem\"" flake.nix | wc -l); if [ $count -ne 2 ]; then echo Wrong exports in flake.nix. Add \"$elem\" in two places of the file; exit 1; fi ; done #rm -rf $BUILD_DIR -cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -optl-Wl,-soname,libsimplex.so -flink-rts -threaded' --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' +if [[ "$DATABASE_BACKEND" == "postgres" ]]; then + echo "Building with postgres backend..." + cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -optl-Wl,-soname,libsimplex.so -flink-rts -threaded' --constraint 'simplexmq +client_library +client_postgres' --constraint 'simplex-chat +client_library +client_postgres' +else + echo "Building with sqlite backend..." + cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -optl-Wl,-soname,libsimplex.so -flink-rts -threaded' --constraint 'simplexmq +client_library' --constraint 'simplex-chat +client_library' +fi cd $BUILD_DIR/build mv libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so libsimplex.so 2> /dev/null || true #patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libsimplex.so From f3547878cce8e5db155e5927fbae4e31435343e0 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Thu, 23 Apr 2026 13:30:26 +0100 Subject: [PATCH 12/77] directory: support public channels and relay-based groups (#6840) * directory: support public channels and relay-based groups (plan) * types * amend types * directory types, resolve known link * implementation, test fails * fix test * fix test * more test * minimal test * more test * debug test * clean up * remove debug logs * refactor * use group/channel terms correctly * remove unsupported commands * manage profile update * owner left the channel * more tests, correct response to sent link * re-registration * /help and /link commands * correct listing for channels * fix test * fix bot api * refactor * do not include link data in GLPKnown * refactor * diff * undo refactor * simplify * remove harness test * remove flip * add v6.5 app requirement for channels * add website support * update bot api types * correct member count, fix test * members -> subscribers * add link to channel description * fix css * move version note --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../src/Directory/Events.hs | 23 +- .../src/Directory/Listing.hs | 43 +- .../src/Directory/Service.hs | 493 +++++++++++++----- bots/api/COMMANDS.md | 1 + bots/api/TYPES.md | 3 + bots/src/API/Docs/Types.hs | 2 +- .../types/typescript/src/commands.ts | 1 + .../types/typescript/src/types.ts | 3 + plans/2026-04-19-directory-public-groups.md | 324 ++++++++++++ src/Simplex/Chat/Controller.hs | 12 +- src/Simplex/Chat/Library/Commands.hs | 48 +- src/Simplex/Chat/Library/Subscriber.hs | 5 +- src/Simplex/Chat/Store/Groups.hs | 6 +- src/Simplex/Chat/Types.hs | 3 + src/Simplex/Chat/View.hs | 2 +- tests/Bots/DirectoryTests.hs | 207 +++++++- tests/ChatTests/Files.hs | 4 +- website/src/directory.html | 4 +- website/src/js/directory.js | 13 +- 19 files changed, 999 insertions(+), 198 deletions(-) create mode 100644 plans/2026-04-19-directory-public-groups.md diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 45c0b84cc6..ef6056fbc4 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -33,10 +33,10 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Directory.Store import Simplex.Chat.Controller -import Simplex.Chat.Markdown (displayNameTextP) +import Simplex.Chat.Markdown (MarkdownList, displayNameTextP) import Simplex.Chat.Messages import Simplex.Chat.Messages.CIContent -import Simplex.Chat.Protocol (MsgContent (..)) +import Simplex.Chat.Protocol (LinkOwnerSig, MsgChatLink, MsgContent (..)) import Simplex.Chat.Types import Simplex.Chat.Types.Shared import Simplex.Messaging.Agent.Protocol (AgentErrorType (..)) @@ -57,6 +57,8 @@ data DirectoryEvent | DEContactLeftGroup ContactId GroupInfo | DEServiceRemovedFromGroup GroupInfo | DEGroupDeleted GroupInfo + | DEChatLinkReceived {contact :: Contact, chatItemId :: ChatItemId, chatLink :: MsgChatLink, ownerSig :: Maybe LinkOwnerSig} + | DEMemberUpdated {groupInfo :: GroupInfo, fromMember :: GroupMember, toMember :: GroupMember} | DEUnsupportedMessage Contact ChatItemId | DEItemEditIgnored Contact | DEItemDeleteIgnored Contact @@ -91,11 +93,14 @@ crDirectoryEvent_ = \case CEvtLeftMember {groupInfo, member} -> (`DEContactLeftGroup` groupInfo) <$> memberContactId member CEvtDeletedMemberUser {groupInfo} -> Just $ DEServiceRemovedFromGroup groupInfo CEvtGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo + CEvtUnknownMemberAnnounced {groupInfo, unknownMember, announcedMember} -> Just $ DEMemberUpdated {groupInfo, fromMember = unknownMember, toMember = announcedMember} + CEvtGroupMemberUpdated {groupInfo, fromMember, toMember} -> Just $ DEMemberUpdated {groupInfo, fromMember, toMember} CEvtChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct CEvtChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct - CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}) : _} -> + CEvtNewChatItems {chatItems = (AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, formattedText = ft, meta = CIMeta {itemLive}}) : _} -> Just $ case (mc, itemLive) of - (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t + (MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP ft <* A.endOfInput) $ T.dropWhileEnd isSpace t + (MCChat {chatLink, ownerSig}, Nothing) -> DEChatLinkReceived {contact = ct, chatItemId = ciId, chatLink, ownerSig} _ -> DEUnsupportedMessage ct ciId where ciId = chatItemId' ci @@ -149,7 +154,7 @@ data DirectoryHelpSection = DHSRegistration | DHSCommands data DirectoryCmd (r :: DirectoryRole) where DCHelp :: DirectoryHelpSection -> DirectoryCmd 'DRUser - DCSearchGroup :: Text -> DirectoryCmd 'DRUser + DCSearchGroup :: Text -> Maybe MarkdownList -> DirectoryCmd 'DRUser DCSearchNext :: DirectoryCmd 'DRUser DCAllGroups :: DirectoryCmd 'DRUser DCRecentGroups :: DirectoryCmd 'DRUser @@ -181,11 +186,11 @@ data ADirectoryCmd = forall r. ADC (SDirectoryRole r) (DirectoryCmd r) deriving instance Show ADirectoryCmd -directoryCmdP :: Parser ADirectoryCmd -directoryCmdP = +directoryCmdP :: Maybe MarkdownList -> Parser ADirectoryCmd +directoryCmdP ft = (A.char '/' *> cmdStrP) <|> (A.char '.' $> ADC SDRUser DCSearchNext) - <|> (ADC SDRUser . DCSearchGroup <$> A.takeText) + <|> (ADC SDRUser . (`DCSearchGroup` ft) <$> A.takeText) where cmdStrP = (tagP >>= \(ADCT u t) -> ADC u <$> (cmdP t <|> pure (DCCommandError t))) @@ -304,7 +309,7 @@ directoryCmdP = directoryCmdTag :: DirectoryCmd r -> Text directoryCmdTag = \case DCHelp _ -> "help" - DCSearchGroup _ -> "search" + DCSearchGroup {} -> "search" DCSearchNext -> "next" DCAllGroups -> "all" DCRecentGroups -> "new" diff --git a/apps/simplex-directory-service/src/Directory/Listing.hs b/apps/simplex-directory-service/src/Directory/Listing.hs index 0d4e8d351c..ef093020bb 100644 --- a/apps/simplex-directory-service/src/Directory/Listing.hs +++ b/apps/simplex-directory-service/src/Directory/Listing.hs @@ -27,7 +27,7 @@ import Data.List (isPrefixOf) import Data.Maybe (catMaybes, fromMaybe) import Data.Text (Text) import qualified Data.Text as T -import Data.Text.Encoding (encodeUtf8) +import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Data.Time.Clock import Data.Time.Clock.System import Data.Time.Format.ISO8601 (iso8601Show) @@ -53,16 +53,24 @@ listingImageFolder :: String listingImageFolder = "images" data DirectoryEntryType = DETGroup - { admission :: Maybe GroupMemberAdmission, + { groupType :: Maybe GroupType, + admission :: Maybe GroupMemberAdmission, summary :: GroupSummary } $(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "DET") ''DirectoryEntryType) +data PublicLink = PublicLink + { connFullLink :: Maybe ConnReqContact, + connShortLink :: Maybe ShortLinkContact + } + +$(JQ.deriveJSON defaultJSON ''PublicLink) + data DirectoryEntry = DirectoryEntry { entryType :: DirectoryEntryType, displayName :: Text, - groupLink :: CreatedLinkContact, + groupLink :: PublicLink, shortDescr :: Maybe MarkdownList, welcomeMessage :: Maybe MarkdownList, imageFile :: Maybe String, @@ -90,8 +98,15 @@ recentRoundedTime roundTo now t groupDirectoryEntry :: UTCTime -> GroupInfo -> Maybe GroupLink -> Maybe (DirectoryEntry, Maybe (FilePath, ImageFileData)) groupDirectoryEntry now GroupInfo {groupProfile, chatTs, createdAt, groupSummary} gLink_ = - let GroupProfile {displayName, shortDescr, description, image, memberAdmission} = groupProfile - entryType = DETGroup memberAdmission groupSummary + let GroupProfile {displayName, shortDescr, description, image, memberAdmission, publicGroup} = groupProfile + gt = (\PublicGroupProfile {groupType} -> groupType) <$> publicGroup + entryType = DETGroup gt memberAdmission groupSummary + description' = case publicGroup of + Just PublicGroupProfile {groupType = gt', groupLink = sLnk} -> + let gtStr = case gt' of GTChannel -> "channel"; _ -> "group" + linkLine = "Link to join the " <> gtStr <> " " <> displayName <> ": " <> decodeUtf8 (strEncode sLnk) + in Just $ maybe linkLine (<> "\n\n" <> linkLine) description + Nothing -> description entry groupLink = let de = DirectoryEntry @@ -99,22 +114,30 @@ groupDirectoryEntry now GroupInfo {groupProfile, chatTs, createdAt, groupSummary displayName, groupLink, shortDescr = toFormattedText <$> shortDescr, - welcomeMessage = toFormattedText <$> description, + welcomeMessage = toFormattedText <$> description', imageFile = fst <$> imgData, activeAt = recentRoundedTime 900 now $ fromMaybe createdAt chatTs, createdAt = recentRoundedTime 86400 now createdAt } imgData = imgFileData groupLink =<< image in (de, imgData) - in (entry . connLinkContact) <$> gLink_ + in case publicGroup of + Just PublicGroupProfile {groupLink = sLnk} -> + Just $ entry $ PublicLink Nothing (Just sLnk) + Nothing -> + entry . toPublicLink . connLinkContact <$> gLink_ where - imgFileData :: CreatedConnLink 'CMContact -> ImageData -> Maybe (FilePath, ByteString) - imgFileData groupLink (ImageData img) = + toPublicLink (CCLink fullLink shortLink) = PublicLink (Just fullLink) shortLink + imgFileData :: PublicLink -> ImageData -> Maybe (FilePath, ByteString) + imgFileData PublicLink {connFullLink, connShortLink} (ImageData img) = let (img', imgExt) = fromMaybe (img, ".jpg") $ (,".jpg") <$> T.stripPrefix "data:image/jpg;base64," img <|> (,".png") <$> T.stripPrefix "data:image/png;base64," img - imgName = B.unpack $ B64URL.encodeUnpadded $ BA.convert $ (CH.hash :: ByteString -> Digest MD5) $ strEncode (connFullLink groupLink) + linkHash = case connFullLink of + Just fl -> strEncode fl + Nothing -> maybe "" strEncode connShortLink + imgName = B.unpack $ B64URL.encodeUnpadded $ BA.convert $ (CH.hash :: ByteString -> Digest MD5) linkHash imgFile = listingImageFolder imgName <> imgExt in case B64.decode $ encodeUtf8 img' of Right img'' -> Just (imgFile, img'') diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index 34b63ff06a..c1cf61f5a1 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -19,6 +19,7 @@ module Directory.Service where import Control.Concurrent (forkIO) +import Control.Concurrent.Async (race_) import Control.Concurrent.STM import Control.Exception (SomeException, try) import Control.Logger.Simple @@ -31,7 +32,7 @@ import Data.Either (fromRight) import Data.List (find, intercalate) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.Map.Strict as M -import Data.Maybe (fromMaybe, isJust, isNothing) +import Data.Maybe (fromMaybe, isJust, isNothing, maybeToList) import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T @@ -51,12 +52,12 @@ import Simplex.Chat.Bot import Simplex.Chat.Bot.KnownContacts import Simplex.Chat.Controller import Simplex.Chat.Core -import Simplex.Chat.Markdown (Format (..), FormattedText (..), parseMaybeMarkdownList, viewName) +import Simplex.Chat.Markdown (Format (..), FormattedText (..), SimplexLinkType (..), parseMaybeMarkdownList, viewName) import Simplex.Chat.Messages import Simplex.Chat.Options -import Simplex.Chat.Protocol (MsgContent (..), memberSupportVoiceVersion) +import Simplex.Chat.Protocol (GroupShortLinkData (..), LinkOwnerSig (..), MsgChatLink (..), MsgContent (..), memberSupportVoiceVersion) import Simplex.Chat.Store.Direct (getContact) -import Simplex.Chat.Store.Groups (getGroupLink, getGroupMember, setGroupCustomData) -- TODO remove setGroupCustomData +import Simplex.Chat.Store.Groups (getGroupLink, getGroupMember, getGroupMemberByMemberId, setGroupCustomData) -- TODO remove setGroupCustomData import Simplex.Chat.Store.Profiles (GroupLinkInfo (..), getGroupLinkInfo) import Simplex.Chat.Store.Shared (StoreError (..)) import Simplex.Chat.Terminal (terminalChatConfig) @@ -65,7 +66,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.View (serializeChatError, serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) -import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ConnectionLink (..), CreatedConnLink (..), SConnectionMode (..), sameConnReqContact, sameShortLinkContact) +import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ACreatedConnLink (..), ConnectionLink (..), CreatedConnLink (..), SConnectionMode (..), sameConnReqContact, sameShortLinkContact) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String import Simplex.Messaging.TMap (TMap) @@ -164,7 +165,7 @@ directoryServiceCLI st opts = do [ simplexChatCLI' terminalChatConfig {chatHooks} (mkChatOpts opts) Nothing, processEvents eventQ env ] - <> updateListingsThread_ opts env + <> maybeToList (updateListingsThread_ opts env) where processEvents eventQ env = forever $ do (cc, resp) <- atomically $ readTQueue eventQ @@ -174,8 +175,8 @@ directoryServiceCLI st opts = do updateListingDelay :: Int updateListingDelay = 5 * 60 * 1000000 -- update every 5 minutes -updateListingsThread_ :: DirectoryOpts -> ServiceState -> [IO ()] -updateListingsThread_ opts env = maybe [] (\f -> [updateListingsThread f]) $ webFolder opts +updateListingsThread_ :: DirectoryOpts -> ServiceState -> Maybe (IO ()) +updateListingsThread_ opts env = updateListingsThread <$> webFolder opts where updateListingsThread f = do cc <- atomically $ takeTMVar $ updateListingsJob env @@ -234,13 +235,10 @@ directoryService st opts cfg = do acceptMember = Just $ acceptMemberHook opts env } simplexChatCore cfg {chatHooks} (mkChatOpts opts) $ \user cc -> - raceAny_ $ - [ forever $ void getLine, - forever $ do - (_, resp) <- atomically . readTBQueue $ outputQ cc - directoryServiceEvent st opts env user cc resp - ] - <> updateListingsThread_ opts env + maybe id race_ (updateListingsThread_ opts env) $ + forever $ do + (_, resp) <- atomically . readTBQueue $ outputQ cc + directoryServiceEvent st opts env user cc resp acceptMemberHook :: DirectoryOpts -> ServiceState -> GroupInfo -> GroupLinkInfo -> Profile -> IO (Either GroupRejectionReason (GroupAcceptance, GroupMemberRole)) acceptMemberHook @@ -298,6 +296,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName DEContactLeftGroup ctId g -> deContactLeftGroup ctId g DEServiceRemovedFromGroup g -> deServiceRemovedFromGroup g DEGroupDeleted g -> deGroupDeleted g + DEChatLinkReceived {contact = ct, chatLink, ownerSig} -> deChatLinkReceived ct chatLink ownerSig + DEMemberUpdated {groupInfo = g, fromMember, toMember} -> deMemberUpdated g fromMember toMember DEUnsupportedMessage _ct _ciId -> pure () DEItemEditIgnored _ct -> pure () DEItemDeleteIgnored _ct -> pure () @@ -325,7 +325,19 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let msg = "Error: " <> err <> ", group: " <> tshow groupId <> " " <> localDisplayName <> ", " <> T.pack e notifyAdminUsers msg logError msg - groupInfoText p@GroupProfile {description = d} = groupNameDescr p <> maybe "" ("\nWelcome message:\n" <>) d + groupInfoText p@GroupProfile {description = d, publicGroup} = groupNameDescr p <> maybe "" ("\nWelcome message:\n" <>) d <> linkToJoin + where + linkToJoin = case publicGroup of + Just pg@PublicGroupProfile {groupLink} -> + "\nLink to join " <> groupTypeStr' pg <> ": " <> strEncodeTxt groupLink + <> "\nYou need SimpleX Chat app v6.5 to join." + Nothing -> "" + membersCountStr GroupProfile {publicGroup} GroupSummary {currentMembers, publicMemberCount} = + let count = fromMaybe currentMembers publicMemberCount + label = case publicGroup of + Just PublicGroupProfile {groupType = GTChannel} -> " subscribers" + _ -> " members" + in tshow count <> label knockingStr :: Maybe GroupMemberAdmission -> [Text] knockingStr = \case Just GroupMemberAdmission {review = Just MCAll} -> ["New members are reviewed by admins"] @@ -342,6 +354,9 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName groupReference' groupId displayName = "ID " <> tshow groupId <> " (" <> displayName <> ")" groupAlreadyListed GroupInfo {groupProfile = p} = "The group " <> groupNameDescr p <> " is already listed in the directory, please choose another name." + ifPublicGroup :: GroupInfo -> IO () -> IO () -> IO () + ifPublicGroup GroupInfo {groupProfile = GroupProfile {publicGroup}} reject action = + if isJust publicGroup then reject else action getDuplicateGroup :: GroupInfo -> IO (Either String DuplicateGroup) getDuplicateGroup GroupInfo {groupId, groupProfile = GroupProfile {displayName}} = @@ -375,7 +390,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName sendMessage cc ct $ ("Welcome to " <> serviceName <> "!\n\n") <> "🔍 Send search string to find groups - try _security_.\n\ - \/help - how to submit your group.\n\ + \/help - how to submit your group or channel.\n\ \/new - recent groups.\n\n\ \[Directory rules](https://simplex.chat/docs/directory.html)." @@ -461,37 +476,68 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName byMember = case memberContactId m of Just ctId | ctId `isOwner` gr -> "" -- group registration owner, not any group owner. _ -> " by " <> mName -- owner notification from directory will include the name. - case groupRegStatus of - GRSPendingConfirmation -> pure () - GRSProposed -> pure () - GRSPendingUpdate -> - groupProfileUpdate >>= \case - GPNoServiceLink -> - notifyOwner gr $ "The profile updated for " <> userGroupRef <> byMember <> ", but the group link is not added to the welcome message." - GPServiceLinkAdded _ -> groupLinkAdded gr byMember - GPServiceLinkRemoved -> - notifyOwner gr $ - "The group link of " <> userGroupRef <> " is removed from the welcome message" <> byMember <> ", please add it." - GPHasServiceLink {} -> groupLinkAdded gr byMember - GPServiceLinkError -> do - notifyOwner gr $ - ("Error: " <> serviceName <> " has no group link for " <> userGroupRef) - <> " after profile was updated" - <> byMember - <> ". Please report the error to the developers." - logError $ "Error: no group link for " <> userGroupRef - GRSPendingApproval n -> processProfileChange gr byMember False $ n + 1 - GRSActive -> processProfileChange gr byMember True 1 - GRSSuspended -> processProfileChange gr byMember False 1 - GRSSuspendedBadRoles -> processProfileChange gr byMember False 1 - GRSRemoved -> pure () + case publicGroup p' of + Just pg -> case groupRegStatus of + GRSPendingApproval n -> publicGroupProfileChange pg gr byMember $ n + 1 + GRSActive -> publicGroupProfileChange pg gr byMember 1 + _ -> pure () + Nothing -> case groupRegStatus of + GRSPendingConfirmation -> pure () + GRSProposed -> pure () + GRSPendingUpdate -> + groupProfileUpdate >>= \case + GPNoServiceLink -> + notifyOwner gr $ "The profile updated for " <> userGroupRef <> byMember <> ", but the group link is not added to the welcome message." + GPServiceLinkAdded _ -> groupLinkAdded gr byMember + GPServiceLinkRemoved -> + notifyOwner gr $ + "The group link of " <> userGroupRef <> " is removed from the welcome message" <> byMember <> ", please add it." + GPHasServiceLink {} -> groupLinkAdded gr byMember + GPServiceLinkError -> do + notifyOwner gr $ + ("Error: " <> serviceName <> " has no group link for " <> userGroupRef) + <> " after profile was updated" + <> byMember + <> ". Please report the error to the developers." + logError $ "Error: no group link for " <> userGroupRef + GRSPendingApproval n -> processProfileChange gr byMember False $ n + 1 + GRSActive -> processProfileChange gr byMember True 1 + GRSSuspended -> processProfileChange gr byMember False 1 + GRSSuspendedBadRoles -> processProfileChange gr byMember False 1 + GRSRemoved -> pure () where GroupInfo {groupId, groupProfile = p} = fromGroup GroupInfo {groupProfile = p'} = toGroup sameProfile - GroupProfile {displayName = n, fullName = fn, shortDescr = sd, image = i, description = d, memberAdmission = ma} - GroupProfile {displayName = n', fullName = fn', shortDescr = sd', image = i', description = d', memberAdmission = ma'} = - n == n' && fn == fn' && i == i' && sd == sd' && (T.words <$> d) == (T.words <$> d') && ma == ma' + GroupProfile {displayName = n, fullName = fn, shortDescr = sd, image = i, description = d, memberAdmission = ma, publicGroup = pg} + GroupProfile {displayName = n', fullName = fn', shortDescr = sd', image = i', description = d', memberAdmission = ma', publicGroup = pg'} = + n == n' && fn == fn' && i == i' && sd == sd' && (T.words <$> d) == (T.words <$> d') && ma == ma' && pg == pg' + publicGroupProfileChange pg@PublicGroupProfile {groupLink} gr byMember n' = do + let gt = groupTypeStr' pg + userGroupRef = userGroupReference gr toGroup + groupRef = groupReference toGroup + link = ACL SCMContact $ CLShort groupLink + updatedNotification gr' g' = do + notifyOwner gr' $ + ("The " <> gt <> " " <> userGroupRef <> " is updated" <> byMember) + <> ".\nIt is hidden from the directory until approved." + notifyAdminUsers $ "The " <> gt <> " " <> groupRef <> " is updated" <> byMember <> "." + sendToApprove g' gr' n' + sendChatCmd cc (APIConnectPlan userId (Just link) True Nothing) >>= \case + Right (CRConnectionPlan _ _ (CPGroupLink (GLPKnown {groupInfo = g'}))) -> + case dbOwnerMemberId gr of + Just ownerGMId -> + withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMember db (vr cc) user groupId ownerGMId) >>= \case + Right ownerMember + | let GroupMember {memberRole = role} = ownerMember, role >= GROwner -> + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval n') (`updatedNotification` g') + | otherwise -> do + setGroupStatus notifyAdminUsers st env cc groupId GRSSuspendedBadRoles $ \_ -> pure () + notifyOwner gr $ "The registration owner is no longer an owner. Registration suspended." + Left _ -> logError $ "could not find owner member for " <> groupRef + Nothing -> logError $ "no owner member set for " <> groupRef + _ -> + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval n') (`updatedNotification` toGroup) groupLinkAdded gr byMember = getDuplicateGroup toGroup >>= \case Left e -> notifyOwner gr $ "Error: getDuplicateGroup. Please notify the developers.\n" <> T.pack e @@ -644,7 +690,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName -- /audio is matched as text, not as DirectoryCmd, because it is only valid -- in group context at captcha stage, while DirectoryCmd is for DM commands. isAudioCmd = T.strip msgText == "/audio" - cmd = fromRight (ADC SDRUser DCUnknownCommand) $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.strip msgText + cmd = fromRight (ADC SDRUser DCUnknownCommand) $ A.parseOnly (directoryCmdP Nothing <* A.endOfInput) $ T.strip msgText atomically (TM.lookup gmId $ pendingCaptchas env) >>= \case Nothing | isAudioCmd && canSendVoiceCaptcha g m -> sendMemberCaptcha g m (Just ciId) noCaptcha 0 CMAudio @@ -661,7 +707,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName sendComposedMessages_ cc sendRef [(Just ciId, MCText audioAlreadyEnabled)] else sendComposedMessages_ cc sendRef [(Just ciId, MCText voiceCaptchaUnavailable)] | otherwise -> case cmd of - ADC SDRUser (DCSearchGroup _) -> do + ADC SDRUser (DCSearchGroup {}) -> do ts <- getCurrentTime if | ts `diffUTCTime` sentAt > captchaTTL -> sendMemberCaptcha g m (Just ciId) captchaExpired (attempts - 1) captchaMode @@ -704,11 +750,12 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName useMemberFilter image $ passCaptcha a sendToApprove :: GroupInfo -> GroupReg -> GroupApprovalId -> IO () - sendToApprove GroupInfo {groupId, groupProfile = p@GroupProfile {displayName, image = image'}, groupSummary} GroupReg {dbContactId, promoted} gaId = do + sendToApprove GroupInfo {groupId, groupProfile = p@GroupProfile {displayName, image = image', publicGroup = pg_}, groupSummary} GroupReg {dbContactId, promoted} gaId = do ct_ <- getContact' cc user dbContactId - let membersStr = "_" <> tshow (currentMembers groupSummary) <> " members_\n" + let gt = maybe "group" groupTypeStr' pg_ + membersStr = "_" <> membersCountStr p groupSummary <> "_\n" text = - either (\_ -> "The group ID " <> tshow groupId <> " submitted: ") (\c -> localDisplayName' c <> " submitted the group ID " <> tshow groupId <> ": ") ct_ + either (\_ -> "The " <> gt <> " ID " <> tshow groupId <> " submitted: ") (\c -> localDisplayName' c <> " submitted the " <> gt <> " ID " <> tshow groupId <> ": ") ct_ <> ("\n" <> groupInfoText p <> "\n" <> membersStr <> "\nTo approve send:") msg = maybe (MCText text) (\image -> MCImage {text, image}) image' withAdminUsers $ \cId -> do @@ -771,63 +818,205 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName >>= mapM_ (\cm@GroupMember {memberRole} -> when (memberRole == GROwner && memberActive cm) action) deContactRemovedFromGroup :: ContactId -> GroupInfo -> IO () - deContactRemovedFromGroup ctId g@GroupInfo {groupId} = do + deContactRemovedFromGroup ctId g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "contact ID " <> tshow ctId <> " removed from group " <> viewGroupName g - withGroupReg g "contact removed" $ \gr -> do + withGroupReg g "contact removed" $ \gr -> when (ctId `isOwner` gr) $ setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr' -> do - notifyOwner gr' $ "You are removed from the group " <> userGroupReference gr' g <> ".\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner is removed)." + notifyOwner gr' $ "You are removed from the " <> gt <> " " <> userGroupReference gr' g <> ".\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (" <> gt <> " owner is removed)." + when (isJust pg_) $ leavePublicGroup g deContactLeftGroup :: ContactId -> GroupInfo -> IO () - deContactLeftGroup ctId g@GroupInfo {groupId} = do + deContactLeftGroup ctId g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "contact ID " <> tshow ctId <> " left group " <> viewGroupName g - -- TODO combine withGroupReg g "contact left" $ \gr -> when (ctId `isOwner` gr) $ setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr' -> do - notifyOwner gr' $ "You left the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group owner left)." + notifyOwner gr' $ "You left the " <> gt <> " " <> userGroupReference gr' g <> ".\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (" <> gt <> " owner left)." + when (isJust pg_) $ leavePublicGroup g deServiceRemovedFromGroup :: GroupInfo -> IO () - deServiceRemovedFromGroup g@GroupInfo {groupId} = do + deServiceRemovedFromGroup g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "service removed from group " <> viewGroupName g setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr -> do - notifyOwner gr $ serviceName <> " is removed from the group " <> userGroupReference gr g <> ".\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (directory service is removed)." + notifyOwner gr $ serviceName <> " is removed from the " <> gt <> " " <> userGroupReference gr g <> ".\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (directory service is removed)." deGroupDeleted :: GroupInfo -> IO () - deGroupDeleted g@GroupInfo {groupId} = do + deGroupDeleted g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} = do + let gt = maybe "group" groupTypeStr' pg_ logInfo $ "group removed " <> viewGroupName g setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \gr -> do - notifyOwner gr $ "The group " <> userGroupReference gr g <> " is deleted.\n\nThe group is no longer listed in the directory." - notifyAdminUsers $ "The group " <> groupReference g <> " is de-listed (group is deleted)." + notifyOwner gr $ "The " <> gt <> " " <> userGroupReference gr g <> " is deleted.\n\nThe " <> gt <> " is no longer listed in the directory." + notifyAdminUsers $ "The " <> gt <> " " <> groupReference g <> " is de-listed (" <> gt <> " is deleted)." + + deChatLinkReceived :: Contact -> MsgChatLink -> Maybe LinkOwnerSig -> IO () + deChatLinkReceived ct (MCLGroup {connLink, groupProfile = GroupProfile {publicGroup = Just PublicGroupProfile {groupType}}}) (Just ownerSig@LinkOwnerSig {ownerId = Just (B64UrlByteString oIdBytes)}) = + case groupType of + GTUnknown tag -> sendMessage cc ct $ "Unsupported group type: " <> T.pack (show tag) + gt -> do + let link = ACL SCMContact $ CLShort connLink + mId = MemberId oIdBytes + gt' = groupTypeStr gt + sendChatCmd cc (APIConnectPlan userId (Just link) True (Just ownerSig)) >>= \case + Right (CRConnectionPlan _ (ACCL SCMContact ccLink) plan) -> + handleGroupLinkPlan ct ccLink mId ownerSig gt' plan + _ -> sendMessage cc ct "Error: could not connect. Please report it to directory admins." + deChatLinkReceived ct (MCLGroup {groupProfile = GroupProfile {publicGroup = Just pg}}) _ = + sendMessage cc ct $ "To add a " <> groupTypeStr' pg <> " to directory you must be the owner." + deChatLinkReceived ct _ _ = + sendMessage cc ct "Only channels can be added to directory via link." + + groupTypeStr :: GroupType -> Text + groupTypeStr = \case + GTChannel -> "channel" + GTGroup -> "group" + GTUnknown _ -> "group" + + groupTypeStr' :: PublicGroupProfile -> Text + groupTypeStr' PublicGroupProfile {groupType} = groupTypeStr groupType + + leavePublicGroup :: GroupInfo -> IO () + leavePublicGroup GroupInfo {groupId} = + void $ sendChatCmd cc (APILeaveGroup groupId) + + handleGroupLinkPlan :: Contact -> CreatedLinkContact -> MemberId -> LinkOwnerSig -> Text -> ConnectionPlan -> IO () + handleGroupLinkPlan ct ccLink mId ownerSig gt = \case + CPGroupLink glp -> case glp of + GLPOk {groupSLinkData_, ownerVerification} -> case (groupSLinkData_, ownerVerification) of + (Just groupSLinkData, Just OVVerified) -> joinAndRegisterPublicGroup ct ccLink mId gt groupSLinkData + (_, Just (OVFailed reason)) -> sendMessage cc ct $ "Link signature verification failed: " <> reason <> ".\nYou must be the " <> gt <> " owner to register it." + (Nothing, _) -> sendMessage cc ct $ "Error: no " <> gt <> " information available via the link." + _ -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership. Please report it to directory admins." + GLPKnown {groupInfo = g, groupUpdated, ownerVerification} -> case ownerVerification of + Just OVVerified -> deReregistration ct g groupUpdated ownerSig + Just (OVFailed reason) -> sendMessage cc ct $ "Link signature verification failed: " <> reason <> ".\nYou must be the " <> gt <> " owner to register it." + Nothing -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership." + GLPConnectingProhibit _ -> sendMessage cc ct $ "Already connecting to this " <> gt <> "." + GLPConnectingConfirmReconnect -> sendMessage cc ct $ "Already connecting to this " <> gt <> "." + GLPNoRelays _ -> sendMessage cc ct $ T.toTitle gt <> " has no active relays. Please try again later." + GLPOwnLink _ -> sendMessage cc ct "Unexpected error. Please report it to directory admins." + _ -> sendMessage cc ct "Unexpected error. Please report it to directory admins." + + joinAndRegisterPublicGroup :: Contact -> CreatedLinkContact -> MemberId -> Text -> GroupShortLinkData -> IO () + joinAndRegisterPublicGroup ct ccLink mId gt groupSLinkData = do + let GroupShortLinkData {groupProfile = GroupProfile {displayName}} = groupSLinkData + ownerContact = GroupOwnerContact {contactId = contactId' ct, memberId = mId} + sendMessage cc ct $ "Joining the " <> gt <> " " <> displayName <> "…" + sendChatCmd cc (APIPrepareGroup userId ccLink False groupSLinkData) >>= \case + Right (CRNewPreparedChat _ (AChat SCTGroup (Chat (GroupChat gInfo _) _ _))) -> do + let gId = groupId' gInfo + addGroupReg notifyAdminUsers st cc ct gInfo GRSProposed $ \_ -> pure () + sendChatCmd cc (APIConnectPreparedGroup gId False (Just ownerContact) Nothing) >>= \case + Right CRStartedConnectionToGroup {groupInfo = gInfo'} -> + withDB "getGroupMember" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (vr cc) user gInfo' mId) >>= \case + Right ownerMember -> + void $ setGroupRegOwner cc gId ownerMember + Left e -> do + logError $ "could not find owner member: " <> T.pack e + sendMessage cc ct "Error: could not find owner member after joining. Please report it to directory admins." + _ -> sendMessage cc ct $ "Error joining " <> gt <> " " <> displayName <> ", please re-send the link!" + _ -> sendMessage cc ct $ "Error joining " <> gt <> " " <> displayName <> ", please re-send the link!" + + deReregistration :: Contact -> GroupInfo -> Bool -> LinkOwnerSig -> IO () + deReregistration ct g@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}} profileChanged LinkOwnerSig {ownerId = Just (B64UrlByteString oIdBytes)} = do + let mId = MemberId oIdBytes + gt = maybe "group" groupTypeStr' pg_ + withDB "getGroupMemberByMemberId" cc (\db -> withExceptT show $ getGroupMemberByMemberId db (vr cc) user g mId) >>= \case + Right ownerMember@GroupMember {memberRole = role, memberStatus} -> + if + | role >= GROwner && memberStatus /= GSMemUnknown -> + getGroupReg cc groupId >>= \case + Right gr + | contactId' ct `isOwner` gr -> sameOwnerReregistration gr gt + | otherwise -> sendMessage cc ct $ "This " <> gt <> " is registered by another owner." + Left _ -> + addGroupReg notifyAdminUsers st cc ct g (GRSPendingApproval 1) $ \gr -> do + void $ setGroupRegOwner cc groupId ownerMember + sendToApprove g gr 1 + | role < GROwner -> sendMessage cc ct $ "You must be the " <> gt <> " owner to register it." + | otherwise -> sendMessage cc ct $ "Waiting for the owner member to be connected to the " <> gt <> "." + Left _ -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership. Please report it to directory admins." + where + sameOwnerReregistration gr gt = case groupRegStatus gr of + GRSProposed -> sendMessage cc ct $ "Registration is in progress, waiting for the owner member to be connected to the " <> gt <> "." + GRSPendingConfirmation -> pendingApprovalTransition gr gt 1 + GRSPendingUpdate -> pendingApprovalTransition gr gt 1 + GRSPendingApproval n + | profileChanged -> pendingApprovalTransition gr gt $ n + 1 + | otherwise -> sendMessage cc ct $ T.toTitle gt <> " is already pending approval." + GRSActive + | profileChanged -> pendingApprovalTransition gr gt 1 + | otherwise -> sendMessage cc ct $ T.toTitle gt <> " is already listed in the directory." + GRSSuspended -> sendMessage cc ct $ T.toTitle gt <> " is suspended by admin. Please contact support." + GRSSuspendedBadRoles -> pendingApprovalTransition gr gt 1 + GRSRemoved -> pendingApprovalTransition gr gt 1 + pendingApprovalTransition gr gt n = do + let userGroupRef = userGroupReference gr g + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval n) $ \gr' -> do + notifyOwner gr' $ + "The " <> gt <> " " <> userGroupRef <> " is submitted for approval.\nIt is hidden from the directory until approved." + sendToApprove g gr' n + deReregistration ct _ _ _ = + sendMessage cc ct "Error: could not verify ownership. Please report it to directory admins." + + deMemberUpdated :: GroupInfo -> GroupMember -> GroupMember -> IO () + deMemberUpdated g@GroupInfo {groupId, groupProfile = GroupProfile {displayName, publicGroup}} fromMember toMember = + withGroupReg g "owner member announced" $ \gr@GroupReg {groupRegStatus, dbOwnerMemberId} -> + when (groupRegStatus == GRSProposed && (dbOwnerMemberId == Just (groupMemberId' fromMember) || dbOwnerMemberId == Just (groupMemberId' toMember))) $ + let GroupMember {memberRole = role} = toMember + gt = maybe "group" groupTypeStr' publicGroup + in if role >= GROwner + then setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval 1) $ \gr' -> do + notifyOwner gr' $ "Joined the " <> gt <> " " <> displayName <> ". Registration is pending approval — it may take up to 48 hours." + sendToApprove g gr' 1 + else do + setGroupStatus notifyAdminUsers st env cc groupId GRSRemoved $ \_ -> pure () + sendMessage' cc (dbContactId gr) "The signing key does not belong to a current owner. Registration cancelled." deUserCommand :: Contact -> ChatItemId -> DirectoryCmd 'DRUser -> IO () deUserCommand ct ciId = \case DCHelp DHSRegistration -> sendMessage cc ct $ - "You must be the group owner to add it to the directory:\n\n\ - \1️⃣ *Invite* " + "You must be the group or channel owner to add it to the directory.\n\n\ + \*To register a channel*, use _Share via chat_ to send its link to " + <> serviceName + <> " bot.\n\n\ + \*To register a group*:\n\ + \1️⃣ *Invite* " <> serviceName <> " bot to your group as *admin* - it will create a link for new members to join.\n\ - \2️⃣ *Add* this link to the group's welcome message.\n\ - \3️⃣ We *review* your group. Once *approved*, anybody can find it.\n\n\ - \_We usually approve within a day, except holidays_. [More details](https://simplex.chat/docs/directory.html#adding-groups-to-the-directory)." + \2️⃣ *Add* this link to the group's welcome message.\n\n\ + \Once your group or channel *approved*, it can be found here or at [simplex.chat/directory](https://simplex.chat/directory).\n\n\ + \_We usually review within a day, except holidays_. [More details](https://simplex.chat/docs/directory.html#adding-groups-to-the-directory)." DCHelp DHSCommands -> sendMessage cc ct $ "/'help commands' - receive this help message.\n\ - \/help - how to register your group to be added to directory.\n\ + \/help - how to register your group or channel to be added to directory.\n\ \/list - list the groups you registered.\n\ \`/role ` - view and set default member role for your group.\n\ \`/filter ` - view and set spam filter settings for group.\n\ \`/link ` - view and upgrade group link.\n\ \`/delete :` - remove the group you submitted from directory, with _ID_ and _name_ as shown by /list command.\n\n\ \To search for groups, send the search text." - DCSearchGroup s -> - sendFoundListedGroups (STSearch s) Nothing "No groups found" $ \gs n -> -- $ sendSearchResults s + DCSearchGroup s ft -> + sendFoundListedGroups (STSearch s) Nothing notFound $ \gs n -> let more = if n > length gs then ", sending top " <> tshow (length gs) else "" in "Found " <> tshow n <> " group(s)" <> more <> "." + where + notFound + | hasSimplexGroupLink ft = "No groups found.\nTo register a group or a channel, please use \"Share via chat\" feature." + | otherwise = "No groups found" + hasSimplexGroupLink = \case + Just fts -> any isGroupLink fts + Nothing -> False + isGroupLink (FormattedText (Just SimplexLink {linkType}) _) = linkType == XLGroup || linkType == XLChannel + isGroupLink _ = False DCSearchNext -> atomically (TM.lookup (contactId' ct) searchRequests) >>= \case Just SearchRequest {searchType, searchTime, lastGroup} -> do @@ -858,14 +1047,17 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName Left e -> sendReply $ "Error reading groups: " <> T.pack e Right gs -> sendGroupsInfo ct ciId isAdmin (gs, length gs) DCDeleteGroup gId gName -> - (if isAdmin then withGroupAndReg sendReply else withUserGroupReg) gId gName $ \GroupInfo {groupProfile = GroupProfile {displayName}} GroupReg {dbGroupId} -> do + (if isAdmin then withGroupAndReg sendReply else withUserGroupReg) gId gName $ \g@GroupInfo {groupProfile = GroupProfile {displayName, publicGroup = pg_}} GroupReg {dbGroupId} -> do + let gt = maybe "group" groupTypeStr' pg_ delGroupReg cc dbGroupId >>= \case Right () -> do logGDelete st dbGroupId - sendReply $ (if isAdmin then "The group " else "Your group ") <> displayName <> " is deleted from the directory" - Left e -> sendReply $ "Error deleting group " <> displayName <> ": " <> T.pack e + sendReply $ (if isAdmin then "The " <> gt <> " " else "Your " <> gt <> " ") <> displayName <> " is deleted from the directory" + when (isJust pg_) $ leavePublicGroup g + Left e -> sendReply $ "Error deleting " <> gt <> " " <> displayName <> ": " <> T.pack e DCMemberRole gId gName_ mRole_ -> - (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> do + (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> + ifPublicGroup g (sendReply "This command is not available for public groups.") $ do let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g case mRole_ of Nothing -> @@ -885,7 +1077,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName initialRole n mRole = "The initial member role for the group " <> n <> " is set to *" <> textEncode mRole <> "*\n" onlyViaLink gLink = "*Please note*: it applies only to members joining via this link: " <> groupLinkText gLink DCGroupFilter gId gName_ acceptance_ -> - (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> do + (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \g _gr -> + ifPublicGroup g (sendReply "This command is not available for public groups.") $ do let GroupInfo {groupProfile = GroupProfile {displayName = n}} = g a = groupMemberAcceptance g case acceptance_ of @@ -916,39 +1109,42 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName Just PCAll -> "_enabled_" Just PCNoImage -> "_enabled for profiles without image_" DCShowUpgradeGroupLink gId gName_ -> - (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \GroupInfo {groupId, localDisplayName = gName} _ -> do - let groupRef = groupReference' gId gName - withGroupLinkResult groupRef (sendChatCmd cc $ APIGetGroupLink groupId) $ - \GroupLink {connLinkContact = gLink@(CCLink _ sLnk_), acceptMemberRole, shortLinkDataSet, shortLinkLargeDataSet = BoolDef slLargeDataSet} -> do - let shouldBeUpgraded = isNothing sLnk_ || not shortLinkDataSet || not slLargeDataSet - sendReply $ - T.unlines $ - [ "The link to join the group " <> groupRef <> ":", - groupLinkText gLink, - "New member role: " <> textEncode acceptMemberRole - ] - <> ["The link is being upgraded..." | shouldBeUpgraded] - when shouldBeUpgraded $ do - let send = sendComposedMessage cc ct Nothing . MCText . T.unlines - withGroupLinkResult groupRef (sendChatCmd cc $ APIAddGroupShortLink groupId) $ - \GroupLink {connLinkContact = CCLink _ sLnk_'} -> case (sLnk_, sLnk_') of - (Just _, Just _) -> - send ["The group link is upgraded for: " <> groupRef, "No changes to group needed."] - (Nothing, Just sLnk) -> - sendComposedMessages - cc - (SRDirect $ contactId' ct) - [ MCText $ - T.unlines - [ "Please replace the old link in welcome message of your group " <> groupRef, - "If this is the only change, the group will remain listed in directory without re-approval.", - "", - "The new link:" - ], - MCText $ strEncodeTxt sLnk - ] - (_, Nothing) -> - send ["The short link is not created for " <> groupRef, "Please report it to the developers."] + (if isAdmin then withGroupAndReg_ sendReply else withUserGroupReg_) gId gName_ $ \GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}, localDisplayName = gName} _ -> case pg_ of + Just pg@PublicGroupProfile {groupLink} -> + sendReply $ "The link to join the " <> groupTypeStr' pg <> " " <> groupReference' gId gName <> ":\n" <> strEncodeTxt groupLink + Nothing -> do + let groupRef = groupReference' gId gName + withGroupLinkResult groupRef (sendChatCmd cc $ APIGetGroupLink groupId) $ + \GroupLink {connLinkContact = gLink@(CCLink _ sLnk_), acceptMemberRole, shortLinkDataSet, shortLinkLargeDataSet = BoolDef slLargeDataSet} -> do + let shouldBeUpgraded = isNothing sLnk_ || not shortLinkDataSet || not slLargeDataSet + sendReply $ + T.unlines $ + [ "The link to join the group " <> groupRef <> ":", + groupLinkText gLink, + "New member role: " <> textEncode acceptMemberRole + ] + <> ["The link is being upgraded..." | shouldBeUpgraded] + when shouldBeUpgraded $ do + let send = sendComposedMessage cc ct Nothing . MCText . T.unlines + withGroupLinkResult groupRef (sendChatCmd cc $ APIAddGroupShortLink groupId) $ + \GroupLink {connLinkContact = CCLink _ sLnk_'} -> case (sLnk_, sLnk_') of + (Just _, Just _) -> + send ["The group link is upgraded for: " <> groupRef, "No changes to group needed."] + (Nothing, Just sLnk) -> + sendComposedMessages + cc + (SRDirect $ contactId' ct) + [ MCText $ + T.unlines + [ "Please replace the old link in welcome message of your group " <> groupRef, + "If this is the only change, the group will remain listed in directory without re-approval.", + "", + "The new link:" + ], + MCText $ strEncodeTxt sLnk + ] + (_, Nothing) -> + send ["The short link is not created for " <> groupRef, "Please report it to the developers."] where withGroupLinkResult groupRef a cb = a >>= \case @@ -1000,8 +1196,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName where msgs = replyMsg :| map foundGroup gs <> [moreMsg | moreGroups > 0] replyMsg = (Just ciId, MCText reply) - foundGroup (GroupInfo {groupId, groupProfile = p@GroupProfile {image = image_, memberAdmission}, groupSummary = GroupSummary {currentMembers}}, _) = - let membersStr = "_" <> tshow currentMembers <> " members_" + foundGroup (GroupInfo {groupId, groupProfile = p@GroupProfile {image = image_, memberAdmission}, groupSummary}, _) = + let membersStr = "_" <> membersCountStr p groupSummary <> "_" showId = if isAdmin then tshow groupId <> ". " else "" text = T.unlines $ [showId <> groupInfoText p, membersStr] ++ knockingStr memberAdmission in (Nothing, maybe (MCText text) (\image -> MCImage {text, image}) image_) @@ -1014,40 +1210,49 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName withGroupAndReg sendReply groupId n $ \g gr@GroupReg {userGroupRegId = ugrId, promoted} -> case groupRegStatus gr of GRSPendingApproval gaId - | gaId == groupApprovalId -> + | gaId == groupApprovalId -> do + let GroupInfo {groupProfile = GroupProfile {publicGroup = pg_}} = g + isPublicGroup_ = isJust pg_ + gt = maybe "group" groupTypeStr' pg_ getDuplicateGroup g >>= \case Left e -> sendReply $ "Error: getDuplicateGroup. Please notify the developers.\n" <> T.pack e - Right DGReserved -> sendReply $ "The group " <> groupRef <> " is already listed in the directory." - _ -> getGroupRolesStatus g gr >>= \case - Right GRSOk -> do - let grPromoted' - | promoted || knownCt `elem` superUsers = fromMaybe promoted promote - | otherwise = False - setGroupStatusPromo sendReply st env cc gr GRSActive grPromoted' $ do - let approved = "The group " <> userGroupReference' gr n <> " is approved" - notifyOwner gr $ - (approved <> " and listed in directory - please moderate it!\n") - <> "_Please note_: if you change the group profile it will be hidden from directory until it is re-approved.\n\n" - <> "Supported commands:\n" - <> ("/'filter " <> tshow ugrId <> "' - to configure anti-spam filter.\n") - <> ("/'role " <> tshow ugrId <> "' - to set default member role.\n") - <> ("/'link " <> tshow ugrId <> "' - to view/upgrade group link.") - invited <- - forM ownersGroup $ \og@KnownGroup {localDisplayName = ogName} -> do - inviteToOwnersGroup og gr $ \case - Right () -> do - owner <- groupOwnerInfo groupRef $ dbContactId gr - pure $ "Invited " <> owner <> " to owners' group " <> viewName ogName - Left err -> pure err - sendReply $ "Group approved" <> (if grPromoted' then " (promoted)" else "") <> "!" <> maybe "" ("\n" <>) invited - notifyOtherSuperUsers $ approved <> " by " <> viewName (localDisplayName' ct) <> maybe "" ("\n" <>) invited - Right GRSServiceNotAdmin -> replyNotApproved serviceNotAdmin - Right GRSContactNotOwner -> replyNotApproved "user is not an owner." - Right GRSBadRoles -> replyNotApproved $ "user is not an owner, " <> serviceNotAdmin - Left e -> sendReply $ "Error: getGroupRolesStatus. Please notify the developers.\n" <> T.pack e - where - replyNotApproved reason = sendReply $ "Group is not approved: " <> reason - serviceNotAdmin = serviceName <> " is not an admin." + Right DGReserved -> sendReply $ "The " <> gt <> " " <> groupRef <> " is already listed in the directory." + _ -> do + rolesOk <- if isPublicGroup_ then pure (Right GRSOk) else getGroupRolesStatus g gr + case rolesOk of + Right GRSOk -> do + let grPromoted' + | promoted || knownCt `elem` superUsers = fromMaybe promoted promote + | otherwise = False + setGroupStatusPromo sendReply st env cc gr GRSActive grPromoted' $ do + let approved = "The " <> gt <> " " <> userGroupReference' gr n <> " is approved" + let commands + | isPublicGroup_ = "" + | otherwise = + "\n\nSupported commands:\n" + <> ("/'filter " <> tshow ugrId <> "' - to configure anti-spam filter.\n") + <> ("/'role " <> tshow ugrId <> "' - to set default member role.\n") + <> ("/'link " <> tshow ugrId <> "' - to view/upgrade group link.") + notifyOwner gr $ + (approved <> " and listed in directory - please moderate it!\n") + <> "_Please note_: if you change the " <> gt <> " profile it will be hidden from directory until it is re-approved." + <> commands + invited <- + forM ownersGroup $ \og@KnownGroup {localDisplayName = ogName} -> do + inviteToOwnersGroup og gr $ \case + Right () -> do + owner <- groupOwnerInfo groupRef $ dbContactId gr + pure $ "Invited " <> owner <> " to owners' group " <> viewName ogName + Left err -> pure err + sendReply $ T.toTitle gt <> " approved" <> (if grPromoted' then " (promoted)" else "") <> "!" <> maybe "" ("\n" <>) invited + notifyOtherSuperUsers $ approved <> " by " <> viewName (localDisplayName' ct) <> maybe "" ("\n" <>) invited + Right GRSServiceNotAdmin -> replyNotApproved serviceNotAdmin + Right GRSContactNotOwner -> replyNotApproved "user is not an owner." + Right GRSBadRoles -> replyNotApproved $ "user is not an owner, " <> serviceNotAdmin + Left e -> sendReply $ "Error: getGroupRolesStatus. Please notify the developers.\n" <> T.pack e + where + replyNotApproved reason = sendReply $ "Group is not approved: " <> reason + serviceNotAdmin = serviceName <> " is not an admin." | otherwise -> sendReply "Incorrect approval code" _ -> sendReply $ "Error: the group " <> groupRef <> " is not pending approval." where @@ -1189,7 +1394,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName GroupReg {userGroupRegId, groupRegStatus} = gr useGroupId = if isAdmin then groupId else userGroupRegId statusStr = "Status: " <> groupRegStatusText groupRegStatus - membersStr = "_" <> tshow (currentMembers groupSummary) <> " members_" + membersStr = "_" <> membersCountStr p groupSummary <> "_" cmds = "/'role " <> tshow useGroupId <> "', /'filter " <> tshow useGroupId <> "'" ownerStr = maybe "" (("Owner: " <>) . either (("getContact error: " <>) . T.pack) localDisplayName') ct_ text = T.unlines $ [tshow useGroupId <> ". " <> groupInfoText p] ++ [ownerStr | isAdmin] ++ [membersStr, statusStr] ++ knockingStr memberAdmission ++ [cmds] diff --git a/bots/api/COMMANDS.md b/bots/api/COMMANDS.md index ab3ec3d241..5ca2c4260a 100644 --- a/bots/api/COMMANDS.md +++ b/bots/api/COMMANDS.md @@ -1285,6 +1285,7 @@ Determine SimpleX link type and if the bot is already connected via this link. **Parameters**: - userId: int64 - connectionLink: string? +- resolveKnown: bool - linkOwnerSig: [LinkOwnerSig](./TYPES.md#linkownersig)? **Syntax**: diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 23fc79b634..2e4f64dcdd 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -2290,6 +2290,8 @@ ConnectingProhibit: Known: - type: "known" - groupInfo: [GroupInfo](#groupinfo) +- groupUpdated: bool +- ownerVerification: [OwnerVerification](#ownerverification)? NoRelays: - type: "noRelays" @@ -2513,6 +2515,7 @@ Public: **Enum type**: - "channel" +- "group" --- diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index e2f67c88c6..de5b721b2d 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -294,7 +294,7 @@ chatTypesDocsData = (sti @GroupShortLinkInfo, STRecord, "", [], "", ""), (sti @GroupSummary, STRecord, "", [], "", ""), (sti @GroupSupportChat, STRecord, "", [], "", ""), - (sti @GroupType, STEnum1, "GT", ["GTUnknown"], "", ""), + (sti @GroupType, STEnum, "GT", ["GTUnknown"], "", ""), (sti @HandshakeError, STEnum, "", [], "", ""), (sti @InlineFileMode, STEnum, "IFM", [], "", ""), (sti @InvitationLinkPlan, STUnion, "ILP", [], "", ""), diff --git a/packages/simplex-chat-client/types/typescript/src/commands.ts b/packages/simplex-chat-client/types/typescript/src/commands.ts index 36692739dd..9c5c31ceb2 100644 --- a/packages/simplex-chat-client/types/typescript/src/commands.ts +++ b/packages/simplex-chat-client/types/typescript/src/commands.ts @@ -471,6 +471,7 @@ export namespace APIAddContact { export interface APIConnectPlan { userId: number // int64 connectionLink?: string + resolveKnown: boolean linkOwnerSig?: T.LinkOwnerSig } diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 6f4f0b6525..08cb225cbc 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -2610,6 +2610,8 @@ export namespace GroupLinkPlan { export interface Known extends Interface { type: "known" groupInfo: GroupInfo + groupUpdated: boolean + ownerVerification?: OwnerVerification } export interface NoRelays extends Interface { @@ -2776,6 +2778,7 @@ export interface GroupSupportChat { export enum GroupType { Channel = "channel", + Group = "group", } export enum HandshakeError { diff --git a/plans/2026-04-19-directory-public-groups.md b/plans/2026-04-19-directory-public-groups.md new file mode 100644 index 0000000000..1b23234d14 --- /dev/null +++ b/plans/2026-04-19-directory-public-groups.md @@ -0,0 +1,324 @@ +# Directory Service — Public Group Registration via Chat Cards + +## Goal + +Enable directory registration of public groups (channels and future group types) via MCChat cards shared in DM with the bot. Replaces the admin-invitation flow with a signature-verified card flow. + +## Background + +### Current group registration flow +1. Owner invites bot as admin member +2. Bot joins, creates group link, asks owner to add link to welcome message +3. Owner updates profile with link → bot sends for admin approval +4. Admin approves → group listed + +This requires the bot to be admin. Public groups don't need this — they already have a public link, and ownership is proven via `ownerSig` on the MCChat card. + +### Public group identity +- `PublicGroupProfile {groupType :: GroupType, groupLink :: ShortLinkContact, publicGroupId :: B64UrlByteString}` +- `publicGroupId = sha256(rootKey)` — immutable identity +- `GroupType`: currently `GTChannel`, adding `GTGroup` for forward compatibility +- `GroupKeys {publicGroupId, groupRootKey, memberPrivKey}` — owner's signing keys +- `ownerId` in `LinkOwnerSig` = `B64UrlByteString (unMemberId memberId)` — the owner's MemberId bytes + +### ownerId-to-member mapping +- `LinkOwnerSig.ownerId = Just (B64UrlByteString unMemberId)` — same raw bytes as `MemberId` +- `createLinkOwnerMember` (called during `APIConnectPreparedGroup`, Commands.hs:2129) creates a member record with `memberRole = GROwner`, `memberStatus = GSMemUnknown`, `memberContactId = Nothing` +- `GroupMemberId` is available immediately after `APIConnectPreparedGroup` +- `getGroupMemberIdViaMemberId db user gInfo (MemberId ownerId)` looks up `GroupMemberId` from `MemberId` + +### Owner member activation +When a relay announces the pre-created `GSMemUnknown` member, `CEvtUnknownMemberAnnounced` fires (Subscriber.hs:2872, via `xGrpMemNew`). The member's profile and role are updated from the announcement's `MemberInfo` (via `updateUnknownMemberAnnounced`, Groups.hs:3010) — the role reflects the member's actual current role, not the pre-created `GROwner`. This event is not currently handled in directory Events.hs. + +### connectPlan and known groups +`apiConnectPlan` with `linkOwnerSig` returns: +- `GLPOk {groupSLinkData_, ownerVerification}` — new group +- `GLPKnown {groupInfo}` — bot already a member +- `GLPOwnLink` / `GLPConnectingProhibit` / `GLPConnectingConfirmReconnect` / `GLPNoRelays` + +**Gap**: For `GLPKnown`, `groupShortLinkPlan` short-circuits via `knownLinkPlans` — never resolves link data, never verifies signature. + +**Fix**: Add an optional parameter to `APIConnectPlan` (before `sig=`, since JSON must be last) that forces link data re-resolution even for known groups. With this parameter, `GLPKnown` includes `ownerVerification` and freshly loaded `groupSLinkData`. The loaded profile may differ from stored — the bot treats the server's current data as authoritative and updates its stored profile accordingly. + +**Future**: Add a signed version counter to link data to detect rollback attacks (malicious server serving old signed profiles). The bot would store the highest version seen and reject/flag version reductions. For now, the server is treated as authoritative. + +### Owner-contact association via APIConnectPreparedGroup +`createLinkOwnerMember` (called during `APIConnectPreparedGroup`) currently creates owner members with `memberContactId = Nothing`. Add an optional `(contactId, ownerId)` paired parameter to `APIConnectPreparedGroup`: when the link was received in a DM, pass the sender's `contactId` and the `ownerId` from `LinkOwnerSig`. The core sets `memberContactId` on the specific owner member whose `memberId` matches `ownerId`. + +This makes ALL existing directory event routing work: `DEContactRoleChanged`, `DEContactRemovedFromGroup`, `DEContactLeftGroup` all resolve via `memberContactId` — no new event types needed for owner tracking. + +Also benefits regular UI: when a user taps an owner's link in a DM, the contact association is created, improving the experience (e.g., showing the contact in the group member list). + +## Registration flow for public groups + +1. Owner taps "Share via chat" on their public group → sends MCChat card to bot in DM +2. Bot receives `CEvtNewChatItems` with `MCChat` content in direct chat → `DEChatLinkReceived` +3. Bot validates card (see validation matrix) +4. Bot calls `apiConnectPlan` with `connLink`, `linkOwnerSig`, and force-resolve flag +5. On `GLPOk` + `Verified`: bot replies "Joining {channel/group} {name}..." and joins via `APIPrepareGroup` then `APIConnectPreparedGroup` (passing owner's `contactId` and `ownerId`). On error: replies "Error joining {channel/group} {name}, please re-send the link!" (same pattern as existing group flow, Service.hs:368-370). +6. After `APIConnectPreparedGroup`, bot stores `dbOwnerMemberId` (via `getGroupMemberIdViaMemberId` — `createLinkOwnerMember` created the record during connect). Registration status: `GRSProposed`. +7. When `CEvtUnknownMemberAnnounced` fires for the owner member → `DEOwnerMemberAnnounced` → bot transitions to `GRSPendingApproval`, replies "Joined {channel/group} {name}. Registration is pending approval — it may take up to 48 hours.", sends to admins for approval +8. Admin approves → `GRSActive` + +## Scenario matrix: card received in DM + +### Event + +One event: `DEChatLinkReceived { contact :: Contact, chatItemId :: ChatItemId, chatLink :: MsgChatLink, ownerSig :: Maybe LinkOwnerSig }`. + +Handler validates and replies based on content. + +### Card validation (handler level) + +| Condition | Action | +|---|---| +| `chatLink` is not MCLGroup, or MCLGroup but no `publicGroup` in profile | Reply: "Only channels can be added to directory via link." | +| MCLGroup + publicGroup but `ownerSig` is `Nothing` | Reply: "To add a {channel/group} to directory you must be the owner." | +| MCLGroup + publicGroup + `ownerSig` is `Just` | Proceed to connectPlan | + +### connectPlan results + +| Plan result | ownerVerification | Action | +|---|---|---| +| `GLPOk` + sLinkData | `Verified` | Reply "Joining {channel/group} {name}...", join (with contactId + ownerId), register as `GRSProposed` | +| `GLPOk` + sLinkData | `Failed reason` | Reply: "Link signature verification failed: {reason}.\nYou must be the {channel/group} owner to register it." | +| `GLPOk` + sLinkData | `Nothing` | Reply: "Error: could not verify {channel/group} ownership. Please report it to directory admins." | +| `GLPOk` no sLinkData | — | Reply: "Error: no {channel/group} information available via the link." | +| `GLPKnown` | `Verified` | Bot already member — handle as re-registration (see below) | +| `GLPKnown` | `Failed reason` | Reply: "Link signature verification failed: {reason}.\nYou must be the {channel/group} owner to register it." | +| `GLPKnown` | `Nothing` | Reply: "Error: could not verify ownership." | +| `GLPConnectingProhibit` | — | Reply: "Already connecting to this {channel/group}." | +| `GLPConnectingConfirmReconnect` | — | Reply: "Already connecting to this {channel/group}." | +| `GLPOwnLink` | — | Log error. Reply: "Unexpected error. Please report it to directory admins." | +| `GLPNoRelays` | — | Reply: "{Channel/Group} has no active relays. Please try again later." | + +### Owner member activation after joining + +Bot is in `GRSProposed`. The pre-created owner member has `GSMemUnknown` status. When the relay announces this member, `CEvtUnknownMemberAnnounced` fires → mapped to `DEOwnerMemberAnnounced` in directory events. + +| Condition | Action | +|---|---| +| `CEvtUnknownMemberAnnounced` for member matching `dbOwnerMemberId`, announced role is `GROwner` | Transition to `GRSPendingApproval`, notify submitting contact, send for admin approval | +| `CEvtUnknownMemberAnnounced` for member matching `dbOwnerMemberId`, announced role < `GROwner` | Reply: "The signing key does not belong to a current owner. Registration cancelled." Set `GRSRemoved`. | +| Owner member never announced | Registration stays in `GRSProposed`. No timeout — manual cleanup via admin. | + +### Re-registration (GLPKnown — bot already member, signature verified at plan) + +With the `connectPlan` fix, `GLPKnown` now includes `ownerVerification` and fresh `groupSLinkData`. Only proceed if `Verified`. + +Bot extracts `ownerId`, looks up member via `getGroupMemberIdViaMemberId`, confirms `memberRole >= GROwner` AND `memberStatus` is active (not `GSMemUnknown`). The pre-created member has `GROwner` role from creation, so role alone is insufficient — the member must have been announced by a relay to confirm actual presence in the group. + +Look up existing `GroupReg` by `groupId`: + +| Existing registration | Ownership verified | Action | +|---|---|---| +| No GroupReg found | Yes | Create new registration as `GRSPendingApproval` | +| GroupReg exists, same owner contact | Yes | Handle based on current status (see status matrix) | +| GroupReg exists, different contact | Sender is verified owner AND previous registrant no longer owner (check `dbOwnerMemberId` member's current role) | Transfer: update `dbContactId` and `dbOwnerMemberId`, proceed as same-owner case | +| GroupReg exists, different contact | Sender is verified owner BUT previous registrant still owner | Reply: "This {channel/group} is registered by another owner." | +| GroupReg exists, different contact | Sender NOT verified owner | Reply: "You must be the {channel/group} owner to register it." Additionally: check if previous registrant (via `dbOwnerMemberId`) is still owner. If not → suspend (`GRSSuspendedBadRoles`). | + +### Re-registration by same owner — status matrix + +| Current status | Action | +|---|---| +| `GRSProposed` | Only if owner member is active (not `GSMemUnknown`): transition to `GRSPendingApproval`, send for approval. If still `GSMemUnknown`: reply "Waiting for owner to connect to the {channel/group}." | +| `GRSPendingConfirmation` | Transition to `GRSPendingApproval`, send for approval (only if previously registered via admin-invitation flow) | +| `GRSPendingUpdate` | Transition to `GRSPendingApproval`, send for approval (only if previously registered via admin-invitation flow) | +| `GRSPendingApproval n` | Check if profile changed (fresh profile from connectPlan vs bot's current DB). If yes: increment approval ID, re-send. If no: reply "Already pending approval." | +| `GRSActive` | Check if profile changed. If yes: transition to `GRSPendingApproval`, re-send. If no: reply "Already listed in the directory." | +| `GRSSuspended` | Reply: "{Channel/Group} is suspended by admin. Contact support." | +| `GRSSuspendedBadRoles` | Ownership re-verified at plan. Transition to `GRSPendingApproval`, send for approval. | +| `GRSRemoved` | Re-register as `GRSPendingApproval` | + +### Profile change detection + +For re-registration: compare the freshly loaded profile (from connectPlan's re-resolved `groupSLinkData`) against the group's current profile in the bot's database. + +For XGrpInfo updates: re-resolve the link via `apiConnectPlan` with `resolve=on`, compare freshly loaded link profile against bot's stored profile. + +Uses the same `sameProfile` comparison as existing group flow (Service.hs:491-494), extended with `publicGroup` field: `displayName`, `fullName`, `shortDescr`, `image`, `description`, `memberAdmission`, `publicGroup` — any difference triggers re-approval. The `publicGroup` field includes `groupLink` (ShortLinkContact), so link regeneration by the owner also triggers re-approval. + +## Profile updates via XGrpInfo (bot is subscriber) + +Bot receives `DEGroupUpdated` when any member updates the group profile. Works for subscribers. + +For public groups: skip "link in welcome message" check. First check if the profile actually changed using the same `sameProfile` comparison as for regular groups (`displayName`, `fullName`, `shortDescr`, `image`, `description`, `memberAdmission`). Only if changed, call `apiConnectPlan` with `resolve=on` to re-resolve the link data. Compare the resolved link profile against the bot's stored profile. + +Note: `xGrpInfo` (Subscriber.hs:3172) prevents `publicGroup` removal and `publicGroupId` changes for channels — these cases can never occur. The `groupLink` (ShortLinkContact) CAN change if the owner regenerates the link; the bot's DB is updated via XGrpInfo and subsequent re-resolution uses the current link. + +| Current status | Profile changed (link data vs stored) | Action | +|---|---|---| +| `GRSProposed` | Any | No action (waiting for owner activation) | +| `GRSPendingApproval n` | Yes | Increment approval ID, re-send for approval | +| `GRSPendingApproval n` | No | No action | +| `GRSActive` | Yes | Transition to `GRSPendingApproval`, notify owner, re-send | +| `GRSActive` | No | No action | +| `GRSSuspended` | Any | No action | +| `GRSSuspendedBadRoles` | Any | No action | +| `GRSRemoved` | Any | No action | + +## Owner tracking + +### Owner-contact association + +When the bot connects via `APIConnectPreparedGroup` with the submitting contact's `contactId` and `ownerId`, the core sets `memberContactId` on the specific pre-created owner member whose `memberId` matches `ownerId`. This makes all existing event routing work: `DEContactRoleChanged`, `DEContactRemovedFromGroup`, `DEContactLeftGroup` resolve via `memberContactId`. + +### Owner changes + +| Event | Detection | Action | +|---|---|---| +| Owner loses owner role | `DEContactRoleChanged` (works via `memberContactId` set at connect time) | Transition to `GRSSuspendedBadRoles`, notify | +| Owner leaves group | `DEContactLeftGroup` | Transition to `GRSRemoved`, notify, leave group | +| Owner removed from group | `DEContactRemovedFromGroup` | Transition to `GRSRemoved`, notify, leave group | +| Non-owner sends card, current registrant no longer owner | Re-registration flow detects stale ownership | Suspend (`GRSSuspendedBadRoles`). Non-owner's card also checked: if their `ownerId` resolves to a non-owner member, and the current registrant is also not owner → suspend. | +| New owner sends card, current registrant no longer owner | Re-registration flow, verified | Transfer registration | + +## Commands for public group registrations + +Bot is subscriber (not admin): +- `/filter` — Reply: "This command is not available for public groups." +- `/role` — Reply: "This command is not available for public groups." +- `/link` — Show `PublicGroupProfile.groupLink` with appropriate message. +- `/delete` — Remove registration, bot leaves group (`APILeaveGroup`). +- `/list` — Works as before, includes public group registrations. + +## De-registration + +| Event | Action | +|---|---| +| Owner sends `/delete ID:NAME` | Delete registration, reply confirmation, leave group | +| Bot removed (`DEServiceRemovedFromGroup`) | Set `GRSRemoved`, notify | +| Group deleted (`DEGroupDeleted`) | Set `GRSRemoved`, notify | +| Owner leaves (`DEContactLeftGroup`) | Set `GRSRemoved`, notify, leave group | +| Owner removed (`DEContactRemovedFromGroup`) | Set `GRSRemoved`, notify, leave group | +| Admin sends `/suspend ID:NAME` | Set `GRSSuspended`, notify, do NOT leave group | + +Bot leaves group only for public group registrations (regular groups preserve existing behavior). + +## Code changes + +### 1. GroupType — add GTGroup + +`Types.hs`: +```haskell +data GroupType = GTChannel | GTGroup | GTUnknown Text +``` + +### 2. connectPlan — force-resolve parameter + +Add optional parameter to `APIConnectPlan` (before `sig=`): `resolve=on`. When present, `groupShortLinkPlan` skips the `knownLinkPlans` shortcut and always resolves link data. `GLPKnown` extended with `ownerVerification` and `groupSLinkData_`: +```haskell +GLPKnown {groupInfo :: GroupInfo, ownerVerification :: Maybe OwnerVerification, groupSLinkData_ :: Maybe GroupShortLinkData} +``` + +Parser: `/_connect plan [resolve=on] [sig=]` + +### 3. APIConnectPreparedGroup — optional (contactId, ownerId) + +Add optional paired `(contactId, ownerId)` parameter to `APIConnectPreparedGroup`. When present, `createLinkOwnerMember` (called during connect, Commands.hs:2129) sets `memberContactId` on the specific owner member whose `memberId` matches the provided `ownerId`. + +Current parser (Commands.hs:5045): `/_connect group # [incognito=on] []` +New parser: `/_connect group # [contact= owner=] [incognito=on] []` + +`contact` and `owner` are paired — both required together. `ownerId` identifies which pre-created owner member gets the `memberContactId` set (multiple owners possible via OwnerAuth chain). + +Current type (Controller.hs:479): `APIConnectPreparedGroup GroupId IncognitoEnabled (Maybe MsgContent)` +New type: `APIConnectPreparedGroup GroupId (Maybe (ContactId, B64UrlByteString)) IncognitoEnabled (Maybe MsgContent)` + +This also benefits the UI: when tapping an owner's link in a DM, the contactId is threaded through the connect alert to `APIConnectPreparedGroup`, creating the association. + +### 4. Events.hs — new events + +`DEChatLinkReceived` — fires for ALL MCChat messages in DM (any `MsgChatLink` variant, signed or unsigned): +```haskell +| DEChatLinkReceived + { contact :: Contact, + chatItemId :: ChatItemId, + chatLink :: MsgChatLink, + ownerSig :: Maybe LinkOwnerSig + } +``` + +`DEOwnerMemberAnnounced` (from `CEvtUnknownMemberAnnounced`): +```haskell +| DEOwnerMemberAnnounced GroupInfo GroupMember GroupMember + -- ^ groupInfo, unknownMember, announcedMember +``` + +In `crDirectoryEvent_`, extend `CEvtNewChatItems` for direct chat: +```haskell +(MCChat {chatLink, ownerSig}, Nothing) -> DEChatLinkReceived ct ciId chatLink ownerSig +``` + +Add `CEvtUnknownMemberAnnounced` handler: +```haskell +CEvtUnknownMemberAnnounced {groupInfo, unknownMember, announcedMember} -> + Just $ DEOwnerMemberAnnounced groupInfo unknownMember announcedMember +``` + +### 5. Service.hs — public group link handler + +`deChatLinkReceived`: validates card, calls `apiConnectPlan` (with `resolve=on`), handles per scenario matrix. The link string comes from `MCLGroup.connLink` (`ShortLinkContact`) formatted as URI — passed via command string, parsed inside the handler. For `GLPOk` + `Verified`: joins (with contactId + ownerId), stores `dbOwnerMemberId`, registers as `GRSProposed`. On join error: replies to owner (same pattern as Service.hs:368-370). For `GLPKnown` + `Verified`: re-registration flow. + +### 6. Service.hs — owner member announced handler + +`deOwnerMemberAnnounced`: checks if the announced member's `GroupMemberId` matches `dbOwnerMemberId` of any `GRSProposed` registration. If yes and role is `GROwner`: transition to `GRSPendingApproval`, notify, send for approval. If role < `GROwner`: cancel. + +### 7. Service.hs — deGroupUpdated changes + +For public groups (`groupProfile.publicGroup` present), skip "link in welcome message" check. On profile change, call `apiConnectPlan` with `resolve=on` to get authoritative link data. Compare resolved profile against stored. If different, trigger re-approval. + +### 8. Service.hs — command restrictions and de-registration + +Check `groupProfile.publicGroup` for `/filter`, `/role`. On `/delete` for public groups, call `APILeaveGroup`. Same for owner departure/removal events. + +### 9. Help message update + +``` +To register a channel, share its link with this bot using the "Share via chat" button. +To register a group, invite this bot as admin. +``` + +### 10. Approval message for admins + +Include: group name, description, image, member count, "Registered via link sharing (signed by owner)", publicGroupId. + +### 11. Tests + +**Registration:** +- Share signed card → bot joins, owner announced, pending approval +- Share unsigned card → "must be owner" reply +- Share non-MCLGroup / non-public-group card → "only channels" reply +- Share card with invalid signature → rejection with reason +- Share card, owner never announced → stays GRSProposed +- Share card, owner announced but role < GROwner → cancelled + +**Re-registration (GLPKnown, verified):** +- Same owner re-shares, active → "already listed" +- Same owner re-shares, pending → "already pending" +- Same owner re-shares with changed profile → re-approval +- Different contact, verified owner, previous no longer owner → transfer +- Different contact, verified owner, previous still owner → "registered by another owner" +- Different contact, not owner → rejection + stale ownership check +- Same owner re-shares while GRSProposed, owner still GSMemUnknown → "waiting for owner" + +**Profile updates:** +- XGrpInfo on active public group → re-approval +- XGrpInfo on pending public group → increment approval ID +- XGrpInfo on public group skips link-in-welcome check + +**Owner tracking (via contactId association):** +- Owner role changed → suspension +- Owner leaves → removal, bot leaves +- Owner removed → removal, bot leaves + +**De-registration:** +- `/delete` by owner → removal, bot leaves +- Bot removed → removal +- Admin `/suspend` → suspension, bot stays + +**Commands:** +- `/filter` on public group → disabled +- `/role` on public group → disabled +- `/link` on public group → shows public link diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index e989e520a5..a7f4ceced0 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -470,13 +470,13 @@ data ChatCommand | AddContact IncognitoEnabled | APISetConnectionIncognito Int64 IncognitoEnabled | APIChangeConnectionUser Int64 UserId -- new user id to switch connection to - | APIConnectPlan {userId :: UserId, connectionLink :: Maybe AConnectionLink, linkOwnerSig :: Maybe LinkOwnerSig} -- Maybe AConnectionLink is used to report link parsing failure as special error + | APIConnectPlan {userId :: UserId, connectionLink :: Maybe AConnectionLink, resolveKnown :: Bool, linkOwnerSig :: Maybe LinkOwnerSig} -- Maybe AConnectionLink is used to report link parsing failure as special error | APIPrepareContact UserId ACreatedConnLink ContactShortLinkData | APIPrepareGroup UserId CreatedLinkContact DirectLink GroupShortLinkData | APIChangePreparedContactUser ContactId UserId | APIChangePreparedGroupUser GroupId UserId | APIConnectPreparedContact {contactId :: ContactId, incognito :: IncognitoEnabled, msgContent_ :: Maybe MsgContent} - | APIConnectPreparedGroup GroupId IncognitoEnabled (Maybe MsgContent) + | APIConnectPreparedGroup {groupId :: GroupId, incognito :: IncognitoEnabled, ownerContact :: Maybe GroupOwnerContact, msgContent_ :: Maybe MsgContent} | APIConnect {userId :: UserId, incognito :: IncognitoEnabled, preparedLink_ :: Maybe ACreatedConnLink} -- Maybe is used to report link parsing failure as special error | Connect {incognito :: IncognitoEnabled, connLink_ :: Maybe AConnectionLink} | APIConnectContactViaAddress UserId IncognitoEnabled ContactId @@ -1037,7 +1037,7 @@ data GroupLinkPlan | GLPOwnLink {groupInfo :: GroupInfo} | GLPConnectingConfirmReconnect | GLPConnectingProhibit {groupInfo_ :: Maybe GroupInfo} - | GLPKnown {groupInfo :: GroupInfo} + | GLPKnown {groupInfo :: GroupInfo, groupUpdated :: Bool, ownerVerification :: Maybe OwnerVerification} | GLPNoRelays {groupSLinkData_ :: Maybe GroupShortLinkData} deriving (Show) @@ -1046,6 +1046,12 @@ data OwnerVerification | OVFailed {reason :: Text} deriving (Show) +data GroupOwnerContact = GroupOwnerContact + { contactId :: ContactId, + memberId :: MemberId + } + deriving (Show) + type DirectLink = Bool data GroupShortLinkInfo = GroupShortLinkInfo diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 042280e062..7a5309164b 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1978,9 +1978,9 @@ processChatCommand vr nm = \case createDirectConnection db newUser agConnId ccLink' Nothing ConnNew Nothing subMode initialChatVersion PQSupportOn deleteAgentConnectionAsync (aConnId' conn) pure conn' - APIConnectPlan userId (Just cLink) linkOwnerSig_ -> withUserId userId $ \user -> - uncurry (CRConnectionPlan user) <$> connectPlan user cLink linkOwnerSig_ - APIConnectPlan _ Nothing _ -> throwChatError CEInvalidConnReq + APIConnectPlan userId (Just cLink) resolveKnown linkOwnerSig_ -> withUserId userId $ \user -> + uncurry (CRConnectionPlan user) <$> connectPlan user cLink resolveKnown linkOwnerSig_ + APIConnectPlan _ Nothing _ _ -> throwChatError CEInvalidConnReq APIPrepareContact userId accLink contactSLinkData -> withUserId userId $ \user -> do let ContactShortLinkData {profile, message, business} = contactSLinkData welcomeSharedMsgId <- forM message $ \_ -> getSharedMsgId @@ -2100,7 +2100,7 @@ processChatCommand vr nm = \case toView $ CEvtNewChatItems user [ci] pure $ CRStartedConnectionToContact user ct' customUserProfile CVRConnectedContact ct' -> pure $ CRContactAlreadyExists user ct' - APIConnectPreparedGroup groupId incognito msgContent_ -> withUser $ \user -> do + APIConnectPreparedGroup {groupId, incognito, ownerContact, msgContent_} -> withUser $ \user -> do gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId case gInfo of GroupInfo {preparedGroup = Nothing} -> throwCmdError "group doesn't have link to connect" @@ -2126,8 +2126,12 @@ processChatCommand vr nm = \case gInfo' <- withFastStore $ \db -> do gInfo' <- updatePreparedRelayedGroup db vr user gInfo mainCReq cReqHash incognitoProfile rootKey memberPrivKey publicMemberCount_ -- Pre-emptively create owner members with trusted keys from link data - forM_ owners $ \OwnerAuth {ownerId, ownerKey} -> - void $ createLinkOwnerMember db vr user gInfo' (MemberId ownerId) ownerKey + forM_ owners $ \OwnerAuth {ownerId, ownerKey} -> do + let ctId_ = case ownerContact of + Just GroupOwnerContact {contactId, memberId} + | memberId == MemberId ownerId -> Just contactId + _ -> Nothing + void $ createLinkOwnerMember db vr user gInfo' ctId_ (MemberId ownerId) ownerKey pure gInfo' rs <- mapConcurrently (connectToRelay gInfo') relays let relayFailed = \case (_, _, Left _) -> True; _ -> False @@ -2221,7 +2225,7 @@ processChatCommand vr nm = \case Connect incognito (Just cLink@(ACL m cLink')) -> withUser $ \user -> do -- TODO [relays] member: /c api to support groups with relays -- TODO - possibly by going through APIPrepareGroup -> APIConnectPreparedGroup - (ccLink, plan) <- connectPlan user cLink Nothing `catchAllErrors` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink (ILPOk Nothing Nothing)); _ -> throwError e + (ccLink, plan) <- connectPlan user cLink False Nothing `catchAllErrors` \e -> case cLink' of CLFull cReq -> pure (ACCL m (CCLink cReq Nothing), CPInvitationLink (ILPOk Nothing Nothing)); _ -> throwError e connectWithPlan user incognito ccLink plan Connect _ Nothing -> throwChatError CEInvalidConnReq APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do @@ -3978,8 +3982,8 @@ processChatCommand vr nm = \case pure (gId, chatSettings) _ -> throwCmdError "not supported" processChatCommand vr nm $ APISetChatSettings (ChatRef cType chatId Nothing) $ updateSettings chatSettings - connectPlan :: User -> AConnectionLink -> Maybe LinkOwnerSig -> CM (ACreatedConnLink, ConnectionPlan) - connectPlan user (ACL SCMInvitation cLink) sig_ = case cLink of + connectPlan :: User -> AConnectionLink -> Bool -> Maybe LinkOwnerSig -> CM (ACreatedConnLink, ConnectionPlan) + connectPlan user (ACL SCMInvitation cLink) _ sig_ = case cLink of CLFull cReq -> invitationReqAndPlan cReq Nothing Nothing Nothing CLShort l -> do let l' = serverShortLink l @@ -4000,7 +4004,7 @@ processChatCommand vr nm = \case invitationReqAndPlan cReq sLnk_ cld ov = do plan <- invitationRequestPlan user cReq cld ov `catchAllErrors` (pure . CPError) pure (ACCL SCMInvitation (CCLink cReq sLnk_), plan) - connectPlan user (ACL SCMContact cLink) sig_ = case cLink of + connectPlan user (ACL SCMContact cLink) resolveKnown sig_ = case cLink of CLFull cReq -> do plan <- contactOrGroupRequestPlan user cReq `catchAllErrors` (pure . CPError) pure (ACCL SCMContact $ CCLink cReq Nothing, plan) @@ -4033,9 +4037,11 @@ processChatCommand vr nm = \case where l' = serverShortLink l con cReq = ACCL SCMContact $ CCLink cReq (Just l') - gPlan (cReq, g) = if memberRemoved (membership g) then Nothing else Just (con cReq, CPGroupLink (GLPKnown g)) + gPlan (cReq, g) = if memberRemoved (membership g) then Nothing else Just (con cReq, CPGroupLink (GLPKnown g False Nothing)) groupShortLinkPlan = knownLinkPlans >>= \case + Just (_, CPGroupLink (GLPKnown g _ _)) + | resolveKnown -> resolveKnownGroup g Just r -> pure r Nothing -> do (fd, cData@(ContactLinkData _ UserContactData {direct, owners, relays})) <- getShortLinkConnReq' nm user l' @@ -4045,8 +4051,6 @@ processChatCommand vr nm = \case else do let FixedLinkData {linkConnReq = cReq, linkEntityId, rootKey} = fd linkInfo = GroupShortLinkInfo {direct, groupRelays = relays, publicGroupId = B64UrlByteString <$> linkEntityId} - -- Cross-validate linkEntityId and publicGroupId from profile: - -- for channels both must be present and match, for p2p groups both must be absent let profilePGId = groupSLinkData_ >>= \GroupShortLinkData {groupProfile = GroupProfile {publicGroup}} -> fmap (\PublicGroupProfile {publicGroupId} -> publicGroupId) publicGroup case (B64UrlByteString <$> linkEntityId, profilePGId) of @@ -4061,6 +4065,15 @@ processChatCommand vr nm = \case liftIO (getGroupInfoViaUserShortLink db vr user l') >>= \case Just (cReq, g) -> pure $ Just (con cReq, CPGroupLink (GLPOwnLink g)) Nothing -> (gPlan =<<) <$> getGroupViaShortLinkToConnect db vr user l' + resolveKnownGroup g@GroupInfo {groupProfile = p} = do + (fd@FixedLinkData {rootKey = rk}, cData@(ContactLinkData _ UserContactData {owners})) <- getShortLinkConnReq' nm user l' + groupSLinkData_ <- liftIO $ decodeLinkUserData cData + let ov = verifyLinkOwner rk owners l' sig_ + (g', updated) <- case groupSLinkData_ of + Just GroupShortLinkData {groupProfile} + | p /= groupProfile -> (,True) <$> withStore (\db -> updateGroupProfile db user g groupProfile) + _ -> pure (g, False) + pure (con (linkConnReq fd), CPGroupLink (GLPKnown g' updated ov)) connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse connectWithPlan user@User {userId} incognito ccLink plan | connectionPlanProceed plan = do @@ -4140,10 +4153,10 @@ processChatCommand vr nm = \case (Just gInfo, _) -> groupPlan gInfo linkInfo gld ov groupPlan :: GroupInfo -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan groupPlan gInfo@GroupInfo {membership} linkInfo gld ov - | memberStatus membership == GSMemRejected = pure $ CPGroupLink (GLPKnown gInfo) + | memberStatus membership == GSMemRejected = pure $ CPGroupLink (GLPKnown gInfo False ov) | not (memberActive membership) && not (memberRemoved membership) = pure $ CPGroupLink (GLPConnectingProhibit $ Just gInfo) - | memberActive membership = pure $ CPGroupLink (GLPKnown gInfo) + | memberActive membership = pure $ CPGroupLink (GLPKnown gInfo False ov) | otherwise = pure $ CPGroupLink (GLPOk linkInfo gld ov) contactCReqSchemas :: ConnReqUriData -> (ConnReqContact, ConnReqContact) contactCReqSchemas crData = @@ -5051,13 +5064,13 @@ chatCommandP = (">#" <|> "> #") *> (SendGroupMessageQuote <$> displayNameP <* A.space <* char_ '@' <*> (Just <$> displayNameP) <* A.space <*> quotedMsg <*> msgTextP), "/_contacts " *> (APIListContacts <$> A.decimal), "/contacts" $> ListContacts, - "/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> ((Just <$> strP) <|> A.takeTill (== ' ') $> Nothing) <*> optional (" sig=" *> jsonP)), + "/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> ((Just <$> strP) <|> A.takeTill (== ' ') $> Nothing) <*> ((" resolve=" *> onOffP) <|> pure False) <*> optional (" sig=" *> jsonP)), "/_prepare contact " *> (APIPrepareContact <$> A.decimal <* A.space <*> connLinkP <* A.space <*> jsonP), "/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP' <*> (" direct=" *> onOffP <|> pure True) <* A.space <*> jsonP), "/_set contact user @" *> (APIChangePreparedContactUser <$> A.decimal <* A.space <*> A.decimal), "/_set group user #" *> (APIChangePreparedGroupUser <$> A.decimal <* A.space <*> A.decimal), "/_connect contact @" *> (APIConnectPreparedContact <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)), - "/_connect group #" *> (APIConnectPreparedGroup <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)), + "/_connect group #" *> (APIConnectPreparedGroup <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> ownerContactP) <*> optional (A.space *> msgContentP)), "/_connect " *> (APIAddContact <$> A.decimal <*> incognitoOnOffP), "/_connect " *> (APIConnect <$> A.decimal <*> incognitoOnOffP <* A.space <*> connLinkP_), "/_set incognito :" *> (APISetConnectionIncognito <$> A.decimal <* A.space <*> onOffP), @@ -5187,6 +5200,7 @@ chatCommandP = ((Just <$> connLinkP) <|> A.takeTill (== ' ') $> Nothing) incognitoP = (A.space *> ("incognito" <|> "i")) $> True <|> pure False incognitoOnOffP = (A.space *> "incognito=" *> onOffP) <|> pure False + ownerContactP = "contact=" *> (GroupOwnerContact <$> A.decimal <* " owner=" <*> strP) imagePrefix = (<>) <$> "data:" <*> ("image/png;base64," <|> "image/jpg;base64,") imageP = safeDecodeUtf8 <$> ((<>) <$> imagePrefix <*> (B64.encode <$> base64P)) chatTypeP = A.char '@' $> CTDirect <|> A.char '#' $> CTGroup <|> A.char '*' $> CTLocal <|> A.char ':' $> CTContactConnection diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index e92622b60c..5d99491b01 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2917,8 +2917,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = GCHostMember -> withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Right existingMember - | useRelays' gInfo -> - void $ withStore $ \db -> updatePreparedChannelMember db vr user existingMember memInfo + | useRelays' gInfo -> do + updatedMember <- withStore $ \db -> updatePreparedChannelMember db vr user existingMember memInfo + toView $ CEvtGroupMemberUpdated user gInfo existingMember updatedMember | otherwise -> messageError "x.grp.mem.intro ignored: member already exists" Left _ diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index c4e7210637..3f5414a1d8 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -2966,8 +2966,8 @@ createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {g where VersionRange minV maxV = vr -createLinkOwnerMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember -createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId ownerKey = do +createLinkOwnerMember :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe ContactId -> MemberId -> C.PublicKeyEd25519 -> ExceptT StoreError IO GroupMember +createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId} contactId_ memberId ownerKey = do currentTs <- liftIO getCurrentTime let memberProfile = profileFromName $ nameFromMemberId memberId (localDisplayName, profileId) <- createNewMemberProfile_ db user memberProfile currentTs @@ -2983,7 +2983,7 @@ createLinkOwnerMember db vr user@User {userId, userContactId} GroupInfo {groupId VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (groupId, indexInGroup, memberId, GROwner, GCPreMember, GSMemUnknown, Binary B.empty, fromInvitedBy userContactId IBUnknown) - :. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, ownerKey, currentTs, currentTs) + :. (userId, localDisplayName, contactId_, profileId, ownerKey, currentTs, currentTs) :. (minV, maxV) ) groupMemberId <- liftIO $ insertedRowId db diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 6ddc411fff..6fb55c84ce 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -769,15 +769,18 @@ fromLocalProfile LocalProfile {displayName, fullName, shortDescr, image, contact data GroupType = GTChannel + | GTGroup | GTUnknown Text deriving (Eq, Show) instance TextEncoding GroupType where textEncode = \case GTChannel -> "channel" + GTGroup -> "group" GTUnknown tag -> tag textDecode s = Just $ case s of "channel" -> GTChannel + "group" -> GTGroup tag -> GTUnknown tag instance FromField GroupType where fromField = fromTextField_ textDecode diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index ce0f55cf01..2bd48d297a 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2103,7 +2103,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case GLPConnectingConfirmReconnect -> [grpLink "connecting, allowed to reconnect"] GLPConnectingProhibit Nothing -> [grpLink "connecting"] GLPConnectingProhibit (Just g) -> connecting g - GLPKnown g@GroupInfo {preparedGroup, membership = m} -> case preparedGroup of + GLPKnown g@GroupInfo {preparedGroup, membership = m} _ _ -> case preparedGroup of Just PreparedGroup {connLinkStartedConnection} -> case memberStatus m of GSMemUnknown | connLinkStartedConnection -> connecting g diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 852e4cf4c4..bf2f1c8128 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -7,7 +7,9 @@ module Bots.DirectoryTests where import ChatClient +import ChatTests.ChatRelays (withRelay) import ChatTests.DBUtils +import ChatTests.Groups (memberJoinChannel, prepareChannel1Relay) import ChatTests.Utils import Control.Concurrent (forkIO, killThread, threadDelay) import Control.Exception (finally) @@ -85,6 +87,12 @@ directoryServiceTests = do describe "help commands" $ do it "should not list audio command" testHelpNoAudio it "should reject audio command in DM" testAudioCommandInDM + describe "public group registration" $ do + it "should register channel via shared link card" testRegisterChannelViaCard + it "should suggest share via chat when link sent as text" testLinkAsTextSearch + it "should reject card shared by non-owner" testNonOwnerSharesCard + it "should delete channel registration and leave" testDeleteChannelRegistration + it "should handle re-registration when already listed" testReregistrationAlreadyListed directoryProfile :: Profile directoryProfile = Profile {displayName = "SimpleX Directory", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing} @@ -1771,7 +1779,7 @@ u `connectVia` dsLink = do u .<# "> Welcome to SimpleX Directory!" u <## "" u <## "🔍 Send search string to find groups - try security." - u <## "/help - how to submit your group." + u <## "/help - how to submit your group or channel." u <## "/new - recent groups." u <## "" u <## "[Directory rules](https://simplex.chat/docs/directory.html)." @@ -1922,7 +1930,7 @@ testHelpNoAudio ps = -- commands help should not mention /audio bob #> "@'SimpleX Directory' /help commands" bob <# "'SimpleX Directory'> /'help commands' - receive this help message." - bob <## "/help - how to register your group to be added to directory." + bob <## "/help - how to register your group or channel to be added to directory." bob <## "/list - list the groups you registered." bob <## "`/role ` - view and set default member role for your group." bob <## "`/filter ` - view and set spam filter settings for group." @@ -1940,6 +1948,201 @@ testAudioCommandInDM ps = bob <# "'SimpleX Directory'> > /audio" bob <## " Unknown command" +testRegisterChannelViaCard :: HasCallStack => TestParams -> IO () +testRegisterChannelViaCard ps = + withDirectoryServiceCfg ps testCfg $ \superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + -- bob connects to directory service first + bob `connectVia` dsLink + -- bob creates a channel with a relay + (_shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + -- bob shares the channel card with directory bot + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + -- directory bot validates and joins via relay + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + -- owner sends a message to trigger member introduction + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "2 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + -- superuser approves + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- owner updates channel profile, triggering re-approval + bob ##> "/gp news news News and Updates" + bob <## "description changed to: News and Updates" + bob <# "'SimpleX Directory'> The channel ID 1 (news) is updated." + bob <## "It is hidden from the directory until approved." + relay <## "bob updated group #news: (signed)" + relay <## "description changed to: News and Updates" + superUser <# "'SimpleX Directory'> The channel ID 1 (news) is updated." + superUser <# ("'SimpleX Directory'> bob submitted the channel ID 1:") + superUser <## "news (News and Updates)" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "3 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + -- re-approve after profile update + let approve2 = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve2) + superUser <# ("'SimpleX Directory'> > " <> approve2) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- owner leaves channel, triggering de-listing and bot leaving + bob ##> "/leave #news" + concurrentlyN_ + [ do + bob <## "#news: you left the group" + bob <## "use /d #news to delete the group", + relay <## "#news: bob left the group (signed)" + ] + bob <# "'SimpleX Directory'> You left the channel ID 1 (news)." + bob <## "" + bob <## "The channel is no longer listed in the directory." + superUser <# "'SimpleX Directory'> The channel ID 1 (news) is de-listed (channel owner left)." + relay <## "#news: 'SimpleX Directory' left the group (signed)" + +testLinkAsTextSearch :: HasCallStack => TestParams -> IO () +testLinkAsTextSearch ps = + withDirectoryServiceCfg ps testCfg $ \_superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + bob `connectVia` dsLink + (shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + bob #> ("@'SimpleX Directory' " <> shortLink) + bob <# ("'SimpleX Directory'> > " <> shortLink) + bob <## " No groups found." + bob <## "To register a group or a channel, please use \"Share via chat\" feature." + +testNonOwnerSharesCard :: HasCallStack => TestParams -> IO () +testNonOwnerSharesCard ps = + withDirectoryServiceCfg ps testCfg $ \_superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> + withNewTestChatCfg ps testCfg "cath" cathProfile $ \cath -> do + bob `connectVia` dsLink + cath `connectVia` dsLink + (shortLink, fullLink) <- prepareChannel1Relay "news" bob relay + memberJoinChannel "news" [relay] [bob] shortLink fullLink cath + cath ##> "/share chat #news @'SimpleX Directory'" + cath <# "@'SimpleX Directory' link to join channel #news:" + _ <- getTermLine cath -- short link + cath <# "'SimpleX Directory'> To add a channel to directory you must be the owner." + +testDeleteChannelRegistration :: HasCallStack => TestParams -> IO () +testDeleteChannelRegistration ps = + withDirectoryServiceCfg ps testCfg $ \superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + bob `connectVia` dsLink + (_shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "2 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- owner deletes registration + bob #> "@'SimpleX Directory' /delete 1:news" + bob + <### + [ WithTime "'SimpleX Directory'> > /delete 1:news", + " Your channel news is deleted from the directory", + "#news: 'SimpleX Directory_1' left the group (signed)" + ] + relay <## "#news: 'SimpleX Directory' left the group (signed)" + +testReregistrationAlreadyListed :: HasCallStack => TestParams -> IO () +testReregistrationAlreadyListed ps = + withDirectoryServiceCfg ps testCfg $ \superUser dsLink -> + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> do + bob `connectVia` dsLink + (_shortLink, _fullLink) <- prepareChannel1Relay "news" bob relay + -- register and approve + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "2 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- search finds the channel with its link + bob #> "@'SimpleX Directory' news" + bob <# "'SimpleX Directory'> > news" + bob <## " Found 1 group(s)." + bob <# "'SimpleX Directory'> news" + bob <##. "Link to join channel: " + bob <## "You need SimpleX Chat app v6.5 to join." + bob <## "3 subscribers" + -- owner re-shares card while already listed + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Channel is already listed in the directory." + testGetCaptchaStr :: HasCallStack => TestParams -> IO () testGetCaptchaStr _ps = do s0 <- getCaptchaStr 0 "" diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 880c1373e9..fb94194561 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -19,7 +19,7 @@ import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Library.Internal (roundedFDCount) import Simplex.Chat.Mobile.File import Simplex.Chat.Options (ChatOpts (..)) -import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..)) +import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), XFTPStoreConfig (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Encoding.String import System.Directory (copyFile, createDirectoryIfMissing, doesFileExist, getFileSize) @@ -940,7 +940,7 @@ testXFTPRcvError ps = do alice <## "completed uploading file 1 (test.pdf) for bob" -- server is up w/t store log - file reception should fail - withXFTPServer' xftpServerConfig {storeLogFile = Nothing} $ do + withXFTPServer' xftpServerConfig {serverStoreCfg = XSCMemory Nothing, storeLogFile = Nothing} $ do withTestChat ps "bob" $ \bob -> do bob <## "subscribed 1 connections on server localhost" bob ##> "/fr 1 ./tests/tmp" diff --git a/website/src/directory.html b/website/src/directory.html index 4ea42f0c3b..f6fc7991f0 100644 --- a/website/src/directory.html +++ b/website/src/directory.html @@ -20,14 +20,14 @@ active_directory: true word-break: break-word; } - #directory .entry a { + #directory .entry a.img-link { order: -1; object-fit: cover; margin-right: 16px; margin-bottom: 16px; } - #directory .entry a img { + #directory .entry a.img-link img { min-width: 104px; min-height: 104px; width: 104px; diff --git a/website/src/js/directory.js b/website/src/js/directory.js index d008370342..ca3ed796e6 100644 --- a/website/src/js/directory.js +++ b/website/src/js/directory.js @@ -165,7 +165,7 @@ function entrySortPriority(entry) { function entryMemberCount(entry) { return entry.entryType.type == 'group' - ? (entry.entryType.summary?.currentMembers ?? 0) + ? (entry.entryType.summary?.publicMemberCount ?? entry.entryType.summary?.currentMembers ?? 0) : 0 } @@ -263,6 +263,13 @@ function displayEntries(entries) { }, 0); } + if (entryType?.groupType) { + const noteElement = document.createElement('p'); + noteElement.innerHTML = 'You need SimpleX Chat app v6.5 to join.'; + noteElement.className = 'text-sm'; + textContainer.appendChild(noteElement); + } + const entryTimestamp = currentSortMode === 'new' && entry.createdAt ? showCreatedOn(entry.createdAt) : entry.activeAt @@ -278,7 +285,8 @@ function displayEntries(entries) { const memberCount = entryMemberCount(entry); if (typeof memberCount == 'number' && memberCount > 0) { const memberCountElement = document.createElement('p'); - memberCountElement.textContent = `${memberCount} members`; + const isChannel = entryType?.groupType === 'channel'; + memberCountElement.textContent = `${memberCount} ${isChannel ? 'subscribers' : 'members'}`; memberCountElement.className = 'text-sm'; textContainer.appendChild(memberCountElement); } @@ -291,6 +299,7 @@ function displayEntries(entries) { } const imgLinkElement = document.createElement('a'); + imgLinkElement.className = 'img-link'; const groupLinkUri = groupLink.connShortLink ?? groupLink.connFullLink try { imgLinkElement.href = platformSimplexUri(groupLinkUri); From ac0176fa0bc8c9dcf146e873c9085e2fce0f1e32 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:58:27 +0000 Subject: [PATCH 13/77] simplex-chat-nodejs: fix resolveKnown type error in apiConnectPlan (#6871) --- packages/simplex-chat-nodejs/src/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index 8bc56db41c..72a7fa93b4 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -654,7 +654,7 @@ export class ChatApi { * Network usage: interactive. */ async apiConnectPlan(userId: number, connectionLink: string): Promise<[T.ConnectionPlan, T.CreatedConnLink]> { - const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectionLink})) + const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectionLink, resolveKnown: false})) if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink] throw new ChatCommandError("error getting connect plan", r) } From 42dd36bf0999d8aa18b1d209b4b1111fb4291d17 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:43:43 +0000 Subject: [PATCH 14/77] simplex-chat-nodejs: typed DbConfig (#6875) * simplex-chat-nodejs: typed DbConfig for ChatApi.init and BotDbOpts * simplex-chat-nodejs: regenerate typedoc docs for DbConfig * simplex-chat-nodejs: rename DbConfig.kind to .type --- packages/simplex-chat-nodejs/README.md | 22 +- .../simplex-chat-nodejs/docs/Namespace.api.md | 1 + .../simplex-chat-nodejs/docs/Namespace.bot.md | 5 +- .../docs/api.Class.ChatApi.md | 350 +++++++++++++----- .../docs/api.Class.ChatCommandError.md | 2 +- .../docs/api.Interface.BotAddressSettings.md | 6 +- .../docs/api.TypeAlias.DbConfig.md | 64 ++++ .../docs/api.TypeAlias.EventSubscriberFunc.md | 4 +- .../docs/bot.Function.run.md | 2 +- .../docs/bot.Interface.BotConfig.md | 26 +- .../docs/bot.Interface.BotDbOpts.md | 33 -- .../docs/bot.Interface.BotOptions.md | 108 +++--- .../docs/bot.TypeAlias.BotDbOpts.md | 17 + .../docs/core.Class.ChatAPIError.md | 8 +- .../docs/core.Class.ChatInitError.md | 2 +- .../docs/core.Interface.APIResult.md | 4 +- .../examples/squaring-bot-readme.js | 2 +- .../examples/squaring-bot.ts | 2 +- packages/simplex-chat-nodejs/src/api.ts | 46 ++- packages/simplex-chat-nodejs/src/bot.ts | 6 +- .../simplex-chat-nodejs/tests/api.test.ts | 10 +- .../simplex-chat-nodejs/tests/bot.test.ts | 4 +- 22 files changed, 485 insertions(+), 239 deletions(-) create mode 100644 packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md delete mode 100644 packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md create mode 100644 packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md diff --git a/packages/simplex-chat-nodejs/README.md b/packages/simplex-chat-nodejs/README.md index ba5a758e0f..e3363df789 100644 --- a/packages/simplex-chat-nodejs/README.md +++ b/packages/simplex-chat-nodejs/README.md @@ -26,7 +26,7 @@ Simple bot that replies with squares of numbers you send to it: // const {bot} = await import("../dist/index.js") const [chat, _user, _address] = await bot.run({ profile: {displayName: "Squaring bot example", fullName: ""}, - dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: "./squaring_bot"}, options: { addressSettings: {welcomeMessage: "Send a number, I will square it.", }, @@ -82,11 +82,23 @@ simplex_backend=postgres - PostgreSQL backend is only available for Linux x86_64 - A PostgreSQL server accessible via connection string -### API difference +### Passing PostgreSQL connection -With the PostgreSQL backend, `chatMigrateInit` arguments change meaning: -- First argument: schema prefix (instead of DB file prefix) -- Second argument: connection string (instead of encryption key) +The `DbConfig` type is a discriminated union — pick the variant that matches +the backend you installed: + +```ts +// SQLite (default) +dbOpts: {type: "sqlite", filePrefix: "./data/bot"} +// optional: encryptionKey: "" + +// PostgreSQL +dbOpts: { + type: "postgres", + connectionString: "postgres://user:pass@host/db", + // schemaPrefix: "bot", // optional — defaults to "simplex_v1" +} +``` ## Documentation diff --git a/packages/simplex-chat-nodejs/docs/Namespace.api.md b/packages/simplex-chat-nodejs/docs/Namespace.api.md index a1b3d2ca5a..5771b8450e 100644 --- a/packages/simplex-chat-nodejs/docs/Namespace.api.md +++ b/packages/simplex-chat-nodejs/docs/Namespace.api.md @@ -24,6 +24,7 @@ You need to use it in bot event handlers, and for any other use cases. ## Type Aliases +- [DbConfig](api.TypeAlias.DbConfig.md) - [EventSubscriberFunc](api.TypeAlias.EventSubscriberFunc.md) - [EventSubscribers](api.TypeAlias.EventSubscribers.md) diff --git a/packages/simplex-chat-nodejs/docs/Namespace.bot.md b/packages/simplex-chat-nodejs/docs/Namespace.bot.md index 447b0b6f68..4b9171d0f7 100644 --- a/packages/simplex-chat-nodejs/docs/Namespace.bot.md +++ b/packages/simplex-chat-nodejs/docs/Namespace.bot.md @@ -12,9 +12,12 @@ It automates creating and updating of the bot profile, address and bot commands ## Interfaces - [BotConfig](bot.Interface.BotConfig.md) -- [BotDbOpts](bot.Interface.BotDbOpts.md) - [BotOptions](bot.Interface.BotOptions.md) +## Type Aliases + +- [BotDbOpts](bot.TypeAlias.BotDbOpts.md) + ## Functions - [run](bot.Function.run.md) diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md index 6812740aa2..b81677b976 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md @@ -6,7 +6,7 @@ # Class: ChatApi -Defined in: [src/api.ts:62](../src/api.ts#L62) +Defined in: [src/api.ts:97](../src/api.ts#L97) Main API class for interacting with the chat core library. @@ -16,7 +16,7 @@ Main API class for interacting with the chat core library. > `protected` **ctrl\_**: `bigint` \| `undefined` -Defined in: [src/api.ts:68](../src/api.ts#L68) +Defined in: [src/api.ts:103](../src/api.ts#L103) ## Accessors @@ -26,7 +26,7 @@ Defined in: [src/api.ts:68](../src/api.ts#L68) > **get** **ctrl**(): `bigint` -Defined in: [src/api.ts:295](../src/api.ts#L295) +Defined in: [src/api.ts:329](../src/api.ts#L329) Chat controller reference @@ -42,7 +42,7 @@ Chat controller reference > **get** **initialized**(): `boolean` -Defined in: [src/api.ts:281](../src/api.ts#L281) +Defined in: [src/api.ts:315](../src/api.ts#L315) Chat controller is initialized @@ -58,7 +58,7 @@ Chat controller is initialized > **get** **started**(): `boolean` -Defined in: [src/api.ts:288](../src/api.ts#L288) +Defined in: [src/api.ts:322](../src/api.ts#L322) Chat controller is started @@ -72,7 +72,7 @@ Chat controller is started > **apiAcceptContactRequest**(`contactReqId`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:697](../src/api.ts#L697) +Defined in: [src/api.ts:731](../src/api.ts#L731) Accept contact request. Network usage: interactive. @@ -93,7 +93,7 @@ Network usage: interactive. > **apiAcceptMember**(`groupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:517](../src/api.ts#L517) +Defined in: [src/api.ts:551](../src/api.ts#L551) Accept group member. Requires Admin role. Network usage: background. @@ -122,7 +122,7 @@ Network usage: background. > **apiAddMember**(`groupId`, `contactId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:497](../src/api.ts#L497) +Defined in: [src/api.ts:531](../src/api.ts#L531) Add contact to group. Requires bot to have Admin role. Network usage: interactive. @@ -151,7 +151,7 @@ Network usage: interactive. > **apiBlockMembersForAll**(`groupId`, `groupMemberIds`, `blocked`): `Promise`\<`void`\> -Defined in: [src/api.ts:537](../src/api.ts#L537) +Defined in: [src/api.ts:571](../src/api.ts#L571) Block members. Requires Moderator role. Network usage: background. @@ -180,7 +180,7 @@ Network usage: background. > **apiCancelFile**(`fileId`): `Promise`\<`void`\> -Defined in: [src/api.ts:487](../src/api.ts#L487) +Defined in: [src/api.ts:521](../src/api.ts#L521) Cancel file. Network usage: background. @@ -201,7 +201,7 @@ Network usage: background. > **apiChatItemReaction**(`chatType`, `chatId`, `chatItemId`, `add`, `reaction`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:461](../src/api.ts#L461) +Defined in: [src/api.ts:495](../src/api.ts#L495) Add/remove message reaction. Network usage: background. @@ -238,7 +238,7 @@ Network usage: background. > **apiConnect**(`userId`, `incognito`, `preparedLink?`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:666](../src/api.ts#L666) +Defined in: [src/api.ts:700](../src/api.ts#L700) Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link Network usage: interactive. @@ -267,7 +267,7 @@ Network usage: interactive. > **apiConnectActiveUser**(`connLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:675](../src/api.ts#L675) +Defined in: [src/api.ts:709](../src/api.ts#L709) Connect via SimpleX link as string in the active user profile. Network usage: interactive. @@ -288,7 +288,7 @@ Network usage: interactive. > **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\> -Defined in: [src/api.ts:656](../src/api.ts#L656) +Defined in: [src/api.ts:690](../src/api.ts#L690) Determine SimpleX link type and if the bot is already connected via this link. Network usage: interactive. @@ -313,7 +313,7 @@ Network usage: interactive. > **apiCreateActiveUser**(`profile?`): `Promise`\<`User`\> -Defined in: [src/api.ts:774](../src/api.ts#L774) +Defined in: [src/api.ts:849](../src/api.ts#L849) Create new user profile Network usage: no. @@ -334,7 +334,7 @@ Network usage: no. > **apiCreateGroupLink**(`groupId`, `memberRole`): `Promise`\<`string`\> -Defined in: [src/api.ts:597](../src/api.ts#L597) +Defined in: [src/api.ts:631](../src/api.ts#L631) Create group link. Network usage: interactive. @@ -359,7 +359,7 @@ Network usage: interactive. > **apiCreateLink**(`userId`): `Promise`\<`string`\> -Defined in: [src/api.ts:643](../src/api.ts#L643) +Defined in: [src/api.ts:677](../src/api.ts#L677) Create 1-time invitation link. Network usage: interactive. @@ -376,11 +376,37 @@ Network usage: interactive. *** +### apiCreateMemberContact() + +> **apiCreateMemberContact**(`groupId`, `groupMemberId`): `Promise`\<`Contact`\> + +Defined in: [src/api.ts:915](../src/api.ts#L915) + +Create a direct message contact with a group member. +Returns the created contact. +Network usage: interactive. + +#### Parameters + +##### groupId + +`number` + +##### groupMemberId + +`number` + +#### Returns + +`Promise`\<`Contact`\> + +*** + ### apiCreateUserAddress() > **apiCreateUserAddress**(`userId`): `Promise`\<`CreatedConnLink`\> -Defined in: [src/api.ts:312](../src/api.ts#L312) +Defined in: [src/api.ts:346](../src/api.ts#L346) Create bot address. Network usage: interactive. @@ -399,9 +425,9 @@ Network usage: interactive. ### apiDeleteChat() -> **apiDeleteChat**(`chatType`, `chatId`, `deleteMode`): `Promise`\<`void`\> +> **apiDeleteChat**(`chatType`, `chatId`, `deleteMode?`): `Promise`\<`void`\> -Defined in: [src/api.ts:737](../src/api.ts#L737) +Defined in: [src/api.ts:771](../src/api.ts#L771) Delete chat. Network usage: background. @@ -416,7 +442,7 @@ Network usage: background. `number` -##### deleteMode +##### deleteMode? `ChatDeleteMode` = `...` @@ -430,7 +456,7 @@ Network usage: background. > **apiDeleteChatItems**(`chatType`, `chatId`, `chatItemIds`, `deleteMode`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:436](../src/api.ts#L436) +Defined in: [src/api.ts:470](../src/api.ts#L470) Delete message. Network usage: background. @@ -463,7 +489,7 @@ Network usage: background. > **apiDeleteGroupLink**(`groupId`): `Promise`\<`void`\> -Defined in: [src/api.ts:619](../src/api.ts#L619) +Defined in: [src/api.ts:653](../src/api.ts#L653) Delete group link. Network usage: background. @@ -484,7 +510,7 @@ Network usage: background. > **apiDeleteMemberChatItem**(`groupId`, `chatItemIds`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:451](../src/api.ts#L451) +Defined in: [src/api.ts:485](../src/api.ts#L485) Moderate message. Requires Moderator role (and higher than message author's). Network usage: background. @@ -509,7 +535,7 @@ Network usage: background. > **apiDeleteUser**(`userId`, `delSMPQueues`, `viewPwd?`): `Promise`\<`void`\> -Defined in: [src/api.ts:804](../src/api.ts#L804) +Defined in: [src/api.ts:879](../src/api.ts#L879) Delete user profile. Network usage: background. @@ -538,7 +564,7 @@ Network usage: background. > **apiDeleteUserAddress**(`userId`): `Promise`\<`void`\> -Defined in: [src/api.ts:322](../src/api.ts#L322) +Defined in: [src/api.ts:356](../src/api.ts#L356) Deletes a user address. Network usage: background. @@ -559,7 +585,7 @@ Network usage: background. > **apiGetActiveUser**(): `Promise`\<`User` \| `undefined`\> -Defined in: [src/api.ts:754](../src/api.ts#L754) +Defined in: [src/api.ts:829](../src/api.ts#L829) Get active user profile Network usage: no. @@ -570,11 +596,40 @@ Network usage: no. *** +### apiGetChat() + +> **apiGetChat**(`chatType`, `chatId`, `count`): `Promise`\<`any`\> + +Defined in: [src/api.ts:819](../src/api.ts#L819) + +Get chat items. +Network usage: no. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### count + +`number` + +#### Returns + +`Promise`\<`any`\> + +*** + ### apiGetGroupLink() > **apiGetGroupLink**(`groupId`): `Promise`\<`GroupLink`\> -Defined in: [src/api.ts:628](../src/api.ts#L628) +Defined in: [src/api.ts:662](../src/api.ts#L662) Get group link. Network usage: no. @@ -595,7 +650,7 @@ Network usage: no. > **apiGetGroupLinkStr**(`groupId`): `Promise`\<`string`\> -Defined in: [src/api.ts:634](../src/api.ts#L634) +Defined in: [src/api.ts:668](../src/api.ts#L668) #### Parameters @@ -613,7 +668,7 @@ Defined in: [src/api.ts:634](../src/api.ts#L634) > **apiGetUserAddress**(`userId`): `Promise`\<`UserContactLink` \| `undefined`\> -Defined in: [src/api.ts:332](../src/api.ts#L332) +Defined in: [src/api.ts:366](../src/api.ts#L366) Get bot address and settings. Network usage: no. @@ -634,7 +689,7 @@ Network usage: no. > **apiJoinGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:507](../src/api.ts#L507) +Defined in: [src/api.ts:541](../src/api.ts#L541) Join group. Network usage: interactive. @@ -655,7 +710,7 @@ Network usage: interactive. > **apiLeaveGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:557](../src/api.ts#L557) +Defined in: [src/api.ts:591](../src/api.ts#L591) Leave group. Network usage: background. @@ -676,7 +731,7 @@ Network usage: background. > **apiListContacts**(`userId`): `Promise`\<`Contact`[]\> -Defined in: [src/api.ts:717](../src/api.ts#L717) +Defined in: [src/api.ts:751](../src/api.ts#L751) Get contacts. Network usage: no. @@ -697,7 +752,7 @@ Network usage: no. > **apiListGroups**(`userId`, `contactId?`, `search?`): `Promise`\<`GroupInfo`[]\> -Defined in: [src/api.ts:727](../src/api.ts#L727) +Defined in: [src/api.ts:761](../src/api.ts#L761) Get groups. Network usage: no. @@ -726,7 +781,7 @@ Network usage: no. > **apiListMembers**(`groupId`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:567](../src/api.ts#L567) +Defined in: [src/api.ts:601](../src/api.ts#L601) Get group members. Network usage: no. @@ -747,7 +802,7 @@ Network usage: no. > **apiListUsers**(): `Promise`\<`UserInfo`[]\> -Defined in: [src/api.ts:784](../src/api.ts#L784) +Defined in: [src/api.ts:859](../src/api.ts#L859) Get all user profiles Network usage: no. @@ -762,7 +817,7 @@ Network usage: no. > **apiNewGroup**(`userId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:577](../src/api.ts#L577) +Defined in: [src/api.ts:611](../src/api.ts#L611) Create group. Network usage: no. @@ -787,7 +842,7 @@ Network usage: no. > **apiReceiveFile**(`fileId`): `Promise`\<`AChatItem`\> -Defined in: [src/api.ts:477](../src/api.ts#L477) +Defined in: [src/api.ts:511](../src/api.ts#L511) Receive file. Network usage: no. @@ -808,7 +863,7 @@ Network usage: no. > **apiRejectContactRequest**(`contactReqId`): `Promise`\<`void`\> -Defined in: [src/api.ts:707](../src/api.ts#L707) +Defined in: [src/api.ts:741](../src/api.ts#L741) Reject contact request. The user who sent the request is **not notified**. Network usage: no. @@ -827,9 +882,9 @@ Network usage: no. ### apiRemoveMembers() -> **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages`): `Promise`\<`GroupMember`[]\> +> **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages?`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:547](../src/api.ts#L547) +Defined in: [src/api.ts:581](../src/api.ts#L581) Remove members. Requires Admin role. Network usage: background. @@ -844,7 +899,7 @@ Network usage: background. `number`[] -##### withMessages +##### withMessages? `boolean` = `false` @@ -854,11 +909,37 @@ Network usage: background. *** +### apiSendMemberContactInvitation() + +> **apiSendMemberContactInvitation**(`contactId`, `message?`): `Promise`\<`Contact`\> + +Defined in: [src/api.ts:926](../src/api.ts#L926) + +Send a direct message invitation to a group member contact. +The contact must have been created with [apiCreateMemberContact](#apicreatemembercontact). +Network usage: interactive. + +#### Parameters + +##### contactId + +`number` + +##### message? + +`string` \| `MsgContent` + +#### Returns + +`Promise`\<`Contact`\> + +*** + ### apiSendMessages() -> **apiSendMessages**(`chat`, `messages`, `liveMessage`): `Promise`\<`AChatItem`[]\> +> **apiSendMessages**(`chat`, `messages`, `liveMessage?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:381](../src/api.ts#L381) +Defined in: [src/api.ts:415](../src/api.ts#L415) Send messages. Network usage: background. @@ -867,13 +948,13 @@ Network usage: background. ##### chat -`ChatInfo` | `ChatRef` | \[`ChatType`, `number`\] +`ChatInfo` \| `ChatRef` \| \[`ChatType`, `number`\] ##### messages `ComposedMessage`[] -##### liveMessage +##### liveMessage? `boolean` = `false` @@ -887,7 +968,7 @@ Network usage: background. > **apiSendTextMessage**(`chat`, `text`, `inReplyTo?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:403](../src/api.ts#L403) +Defined in: [src/api.ts:437](../src/api.ts#L437) Send text message. Network usage: background. @@ -896,7 +977,7 @@ Network usage: background. ##### chat -`ChatInfo` | `ChatRef` | \[`ChatType`, `number`\] +`ChatInfo` \| `ChatRef` \| \[`ChatType`, `number`\] ##### text @@ -916,7 +997,7 @@ Network usage: background. > **apiSendTextReply**(`chatItem`, `text`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:411](../src/api.ts#L411) +Defined in: [src/api.ts:445](../src/api.ts#L445) Send text message in reply to received message. Network usage: background. @@ -941,7 +1022,7 @@ Network usage: background. > **apiSetActiveUser**(`userId`, `viewPwd?`): `Promise`\<`User`\> -Defined in: [src/api.ts:794](../src/api.ts#L794) +Defined in: [src/api.ts:869](../src/api.ts#L869) Set active user profile Network usage: no. @@ -966,7 +1047,7 @@ Network usage: no. > **apiSetAddressSettings**(`userId`, `__namedParameters`): `Promise`\<`void`\> -Defined in: [src/api.ts:364](../src/api.ts#L364) +Defined in: [src/api.ts:398](../src/api.ts#L398) Set bot address settings. Network usage: interactive. @@ -987,11 +1068,61 @@ Network usage: interactive. *** +### apiSetAutoAcceptMemberContacts() + +> **apiSetAutoAcceptMemberContacts**(`userId`, `onOff`): `Promise`\<`void`\> + +Defined in: [src/api.ts:808](../src/api.ts#L808) + +Set auto-accept member contacts. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +##### onOff + +`boolean` + +#### Returns + +`Promise`\<`void`\> + +*** + +### apiSetContactCustomData() + +> **apiSetContactCustomData**(`contactId`, `customData?`): `Promise`\<`void`\> + +Defined in: [src/api.ts:798](../src/api.ts#L798) + +Set contact custom data. +Network usage: no. + +#### Parameters + +##### contactId + +`number` + +##### customData? + +`object` + +#### Returns + +`Promise`\<`void`\> + +*** + ### apiSetContactPrefs() > **apiSetContactPrefs**(`contactId`, `preferences`): `Promise`\<`void`\> -Defined in: [src/api.ts:830](../src/api.ts#L830) +Defined in: [src/api.ts:905](../src/api.ts#L905) Configure chat preference overrides for the contact. Network usage: background. @@ -1012,11 +1143,36 @@ Network usage: background. *** +### apiSetGroupCustomData() + +> **apiSetGroupCustomData**(`groupId`, `customData?`): `Promise`\<`void`\> + +Defined in: [src/api.ts:788](../src/api.ts#L788) + +Set group custom data. +Network usage: no. + +#### Parameters + +##### groupId + +`number` + +##### customData? + +`object` + +#### Returns + +`Promise`\<`void`\> + +*** + ### apiSetGroupLinkMemberRole() > **apiSetGroupLinkMemberRole**(`groupId`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:610](../src/api.ts#L610) +Defined in: [src/api.ts:644](../src/api.ts#L644) Set member role for group link. Network usage: no. @@ -1041,7 +1197,7 @@ Network usage: no. > **apiSetMembersRole**(`groupId`, `groupMemberIds`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:527](../src/api.ts#L527) +Defined in: [src/api.ts:561](../src/api.ts#L561) Set members role. Requires Admin role. Network usage: background. @@ -1070,7 +1226,7 @@ Network usage: background. > **apiSetProfileAddress**(`userId`, `enable`): `Promise`\<`UserProfileUpdateSummary`\> -Defined in: [src/api.ts:350](../src/api.ts#L350) +Defined in: [src/api.ts:384](../src/api.ts#L384) Add address to bot profile. Network usage: interactive. @@ -1095,7 +1251,7 @@ Network usage: interactive. > **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage`): `Promise`\<`ChatItem`\> -Defined in: [src/api.ts:419](../src/api.ts#L419) +Defined in: [src/api.ts:453](../src/api.ts#L453) Update message. Network usage: background. @@ -1132,7 +1288,7 @@ Network usage: background. > **apiUpdateGroupProfile**(`groupId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:587](../src/api.ts#L587) +Defined in: [src/api.ts:621](../src/api.ts#L621) Update group profile. Network usage: background. @@ -1157,7 +1313,7 @@ Network usage: background. > **apiUpdateProfile**(`userId`, `profile`): `Promise`\<`UserProfileUpdateSummary` \| `undefined`\> -Defined in: [src/api.ts:814](../src/api.ts#L814) +Defined in: [src/api.ts:889](../src/api.ts#L889) Update user profile. Network usage: background. @@ -1182,7 +1338,7 @@ Network usage: background. > **close**(): `Promise`\<`void`\> -Defined in: [src/api.ts:114](../src/api.ts#L114) +Defined in: [src/api.ts:148](../src/api.ts#L148) Close chat database. Usually doesn't need to be called in chat bots. @@ -1195,9 +1351,9 @@ Usually doesn't need to be called in chat bots. ### off() -> **off**\<`K`\>(`event`, `subscriber`): `void` +> **off**\<`K`\>(`event`, `subscriber?`): `void` -Defined in: [src/api.ts:253](../src/api.ts#L253) +Defined in: [src/api.ts:287](../src/api.ts#L287) Unsubscribe all or a specific handler from a specific event. @@ -1215,12 +1371,12 @@ Unsubscribe all or a specific handler from a specific event. The event type to unsubscribe from. -##### subscriber +##### subscriber? + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> \| `undefined` An optional subscriber function for the event. -[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`K`\> | `undefined` - #### Returns `void` @@ -1229,20 +1385,20 @@ An optional subscriber function for the event. ### offAny() -> **offAny**(`receiver`): `void` +> **offAny**(`receiver?`): `void` -Defined in: [src/api.ts:269](../src/api.ts#L269) +Defined in: [src/api.ts:303](../src/api.ts#L303) Unsubscribe all or a specific handler from any events. #### Parameters -##### receiver +##### receiver? + +[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> \| `undefined` An optional subscriber function for the event. -[`EventSubscriberFunc`](api.TypeAlias.EventSubscriberFunc.md)\<`Tag`\> | `undefined` - #### Returns `void` @@ -1255,7 +1411,7 @@ An optional subscriber function for the event. > **on**\<`K`\>(`subscribers`): `void` -Defined in: [src/api.ts:163](../src/api.ts#L163) +Defined in: [src/api.ts:197](../src/api.ts#L197) Subscribe multiple event handlers at once. @@ -1285,7 +1441,7 @@ If the same function is subscribed to event. > **on**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:171](../src/api.ts#L171) +Defined in: [src/api.ts:205](../src/api.ts#L205) Subscribe a handler to a specific event. @@ -1323,7 +1479,7 @@ If the same function is subscribed to event. > **onAny**(`receiver`): `void` -Defined in: [src/api.ts:194](../src/api.ts#L194) +Defined in: [src/api.ts:228](../src/api.ts#L228) Subscribe a handler to any event. @@ -1349,7 +1505,7 @@ If the same function is subscribed to event. > **once**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:205](../src/api.ts#L205) +Defined in: [src/api.ts:239](../src/api.ts#L239) Subscribe a handler to a specific event to be delivered one time. @@ -1385,13 +1541,13 @@ If the same function is subscribed to event. ### recvChatEvent() -> **recvChatEvent**(`wait`): `Promise`\<`ChatEvent` \| `undefined`\> +> **recvChatEvent**(`wait?`): `Promise`\<`ChatEvent` \| `undefined`\> -Defined in: [src/api.ts:304](../src/api.ts#L304) +Defined in: [src/api.ts:338](../src/api.ts#L338) #### Parameters -##### wait +##### wait? `number` = `5_000_000` @@ -1405,7 +1561,7 @@ Defined in: [src/api.ts:304](../src/api.ts#L304) > **sendChatCmd**(`cmd`): `Promise`\<`ChatResponse`\> -Defined in: [src/api.ts:300](../src/api.ts#L300) +Defined in: [src/api.ts:334](../src/api.ts#L334) #### Parameters @@ -1423,7 +1579,7 @@ Defined in: [src/api.ts:300](../src/api.ts#L300) > **startChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:88](../src/api.ts#L88) +Defined in: [src/api.ts:122](../src/api.ts#L122) Start chat controller. Must be called with the existing user profile. @@ -1437,7 +1593,7 @@ Start chat controller. Must be called with the existing user profile. > **stopChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:102](../src/api.ts#L102) +Defined in: [src/api.ts:136](../src/api.ts#L136) Stop chat controller. Must be called before closing the database. @@ -1453,9 +1609,9 @@ Usually doesn't need to be called in chat bots. #### Call Signature -> **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +> **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:213](../src/api.ts#L213) +Defined in: [src/api.ts:247](../src/api.ts#L247) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1474,13 +1630,13 @@ Returns `undefined` on timeout if specified. ##### Returns -`Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +`Promise`\<`ChatEvent` & `object`\> #### Call Signature -> **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +> **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:214](../src/api.ts#L214) +Defined in: [src/api.ts:248](../src/api.ts#L248) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1499,17 +1655,17 @@ Returns `undefined` on timeout if specified. ###### predicate -(`event`) => `boolean` | `undefined` +((`event`) => `boolean`) \| `undefined` ##### Returns -`Promise`\<`ChatEvent` & \{ `type`: `K`; \}\> +`Promise`\<`ChatEvent` & `object`\> #### Call Signature > **wait**\<`K`\>(`event`, `timeout`): `Promise`\ -Defined in: [src/api.ts:215](../src/api.ts#L215) +Defined in: [src/api.ts:249](../src/api.ts#L249) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1538,7 +1694,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `predicate`, `timeout`): `Promise`\ -Defined in: [src/api.ts:216](../src/api.ts#L216) +Defined in: [src/api.ts:250](../src/api.ts#L250) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1557,7 +1713,7 @@ Returns `undefined` on timeout if specified. ###### predicate -(`event`) => `boolean` | `undefined` +((`event`) => `boolean`) \| `undefined` ###### timeout @@ -1571,25 +1727,19 @@ Returns `undefined` on timeout if specified. ### init() -> `static` **init**(`dbFilePrefix`, `dbKey?`, `confirm?`): `Promise`\<`ChatApi`\> +> `static` **init**(`db`, `confirm?`): `Promise`\<`ChatApi`\> -Defined in: [src/api.ts:76](../src/api.ts#L76) +Defined in: [src/api.ts:110](../src/api.ts#L110) Initializes the ChatApi. #### Parameters -##### dbFilePrefix +##### db -`string` +[`DbConfig`](api.TypeAlias.DbConfig.md) -File prefix for the database files. - -##### dbKey? - -`string` = `""` - -Database encryption key. +Database configuration (sqlite or postgres). ##### confirm? diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md index a4955cb3d9..5ec1d40540 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatCommandError.md @@ -74,7 +74,7 @@ Defined in: [src/api.ts:6](../src/api.ts#L6) ### stack? -> `optional` **stack**: `string` +> `optional` **stack?**: `string` Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) diff --git a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md index efb4a75e81..23c5e35326 100644 --- a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md +++ b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md @@ -14,7 +14,7 @@ Bot address settings. ### autoAccept? -> `optional` **autoAccept**: `boolean` +> `optional` **autoAccept?**: `boolean` Defined in: [src/api.ts:28](../src/api.ts#L28) @@ -30,7 +30,7 @@ true ### businessAddress? -> `optional` **businessAddress**: `boolean` +> `optional` **businessAddress?**: `boolean` Defined in: [src/api.ts:41](../src/api.ts#L41) @@ -47,7 +47,7 @@ false ### welcomeMessage? -> `optional` **welcomeMessage**: `string` \| `MsgContent` +> `optional` **welcomeMessage?**: `string` \| `MsgContent` Defined in: [src/api.ts:34](../src/api.ts#L34) diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md new file mode 100644 index 0000000000..7fe255327c --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md @@ -0,0 +1,64 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [api](Namespace.api.md) / DbConfig + +# Type Alias: DbConfig + +> **DbConfig** = \{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} \| \{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \} + +Defined in: [src/api.ts:65](../src/api.ts#L65) + +Database configuration. The native library is built against exactly one +backend (see `simplex_backend` / `SIMPLEX_BACKEND` at install time); this +type makes the caller state which one they are targeting so field names +can't lie about their meaning. + +## Union Members + +### Type Literal + +\{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} + +#### encryptionKey? + +> `optional` **encryptionKey?**: `string` + +Optional SQLCipher encryption key. Empty/omitted = unencrypted. + +#### filePrefix + +> **filePrefix**: `string` + +File prefix — two schema files are named `_chat.db` and `_agent.db`. + +#### type + +> **type**: `"sqlite"` + +SQLite backend (default). + +*** + +### Type Literal + +\{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \} + +#### connectionString + +> **connectionString**: `string` + +PostgreSQL connection string (e.g. `postgres://user:pass@host/db`). + +#### schemaPrefix? + +> `optional` **schemaPrefix?**: `string` + +Schema prefix used to namespace tables. Defaults to `"simplex_v1"` when omitted. + +#### type + +> **type**: `"postgres"` + +PostgreSQL backend (Linux x86_64 only, libpq5 required). diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md index 6197befc8a..bf40b3165c 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md @@ -4,7 +4,7 @@ [simplex-chat](README.md) / [api](Namespace.api.md) / EventSubscriberFunc -# Type Alias: EventSubscriberFunc()\ +# Type Alias: EventSubscriberFunc\ > **EventSubscriberFunc**\<`K`\> = (`event`) => `void` \| `Promise`\<`void`\> @@ -20,7 +20,7 @@ Defined in: [src/api.ts:50](../src/api.ts#L50) ### event -`ChatEvent` & \{ `type`: `K`; \} +`ChatEvent` & `object` ## Returns diff --git a/packages/simplex-chat-nodejs/docs/bot.Function.run.md b/packages/simplex-chat-nodejs/docs/bot.Function.run.md index bc31ad01a8..3c33e6c7d4 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Function.run.md +++ b/packages/simplex-chat-nodejs/docs/bot.Function.run.md @@ -8,7 +8,7 @@ > **run**(`__namedParameters`): `Promise`\<\[[`ChatApi`](api.Class.ChatApi.md), `User`, `UserContactLink` \| `undefined`\]\> -Defined in: [src/bot.ts:49](../src/bot.ts#L49) +Defined in: [src/bot.ts:47](../src/bot.ts#L47) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md index 0951c5a129..4624b1608b 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotConfig.md @@ -6,43 +6,43 @@ # Interface: BotConfig -Defined in: [src/bot.ts:37](../src/bot.ts#L37) +Defined in: [src/bot.ts:35](../src/bot.ts#L35) ## Properties ### dbOpts -> **dbOpts**: [`BotDbOpts`](bot.Interface.BotDbOpts.md) +> **dbOpts**: [`BotDbOpts`](bot.TypeAlias.BotDbOpts.md) -Defined in: [src/bot.ts:39](../src/bot.ts#L39) +Defined in: [src/bot.ts:37](../src/bot.ts#L37) *** ### events? -> `optional` **events**: [`EventSubscribers`](api.TypeAlias.EventSubscribers.md) +> `optional` **events?**: [`EventSubscribers`](api.TypeAlias.EventSubscribers.md) -Defined in: [src/bot.ts:46](../src/bot.ts#L46) +Defined in: [src/bot.ts:44](../src/bot.ts#L44) *** ### onCommands? -> `optional` **onCommands**: \{\[`key`: `string`\]: (`chatItem`, `command`) => `void` \| `Promise`\<`void`\> \| `undefined`; \} +> `optional` **onCommands?**: `object` -Defined in: [src/bot.ts:43](../src/bot.ts#L43) +Defined in: [src/bot.ts:41](../src/bot.ts#L41) #### Index Signature -\[`key`: `string`\]: (`chatItem`, `command`) => `void` \| `Promise`\<`void`\> \| `undefined` +\[`key`: `string`\]: ((`chatItem`, `command`) => `void` \| `Promise`\<`void`\>) \| `undefined` *** -### onMessage()? +### onMessage? -> `optional` **onMessage**: (`chatItem`, `content`) => `void` \| `Promise`\<`void`\> +> `optional` **onMessage?**: (`chatItem`, `content`) => `void` \| `Promise`\<`void`\> -Defined in: [src/bot.ts:41](../src/bot.ts#L41) +Defined in: [src/bot.ts:39](../src/bot.ts#L39) #### Parameters @@ -64,7 +64,7 @@ Defined in: [src/bot.ts:41](../src/bot.ts#L41) > **options**: [`BotOptions`](bot.Interface.BotOptions.md) -Defined in: [src/bot.ts:40](../src/bot.ts#L40) +Defined in: [src/bot.ts:38](../src/bot.ts#L38) *** @@ -72,4 +72,4 @@ Defined in: [src/bot.ts:40](../src/bot.ts#L40) > **profile**: `Profile` -Defined in: [src/bot.ts:38](../src/bot.ts#L38) +Defined in: [src/bot.ts:36](../src/bot.ts#L36) diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md deleted file mode 100644 index 7a9f113f6a..0000000000 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotDbOpts.md +++ /dev/null @@ -1,33 +0,0 @@ -[**simplex-chat**](README.md) - -*** - -[simplex-chat](README.md) / [bot](Namespace.bot.md) / BotDbOpts - -# Interface: BotDbOpts - -Defined in: [src/bot.ts:7](../src/bot.ts#L7) - -## Properties - -### confirmMigrations? - -> `optional` **confirmMigrations**: [`MigrationConfirmation`](core.Enumeration.MigrationConfirmation.md) - -Defined in: [src/bot.ts:10](../src/bot.ts#L10) - -*** - -### dbFilePrefix - -> **dbFilePrefix**: `string` - -Defined in: [src/bot.ts:8](../src/bot.ts#L8) - -*** - -### dbKey? - -> `optional` **dbKey**: `string` - -Defined in: [src/bot.ts:9](../src/bot.ts#L9) diff --git a/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md index 44d4380e5a..eee56b879a 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md +++ b/packages/simplex-chat-nodejs/docs/bot.Interface.BotOptions.md @@ -6,76 +6,76 @@ # Interface: BotOptions -Defined in: [src/bot.ts:13](../src/bot.ts#L13) +Defined in: [src/bot.ts:11](../src/bot.ts#L11) ## Properties ### addressSettings? -> `optional` **addressSettings**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) - -Defined in: [src/bot.ts:17](../src/bot.ts#L17) - -*** - -### allowFiles? - -> `optional` **allowFiles**: `boolean` - -Defined in: [src/bot.ts:18](../src/bot.ts#L18) - -*** - -### commands? - -> `optional` **commands**: `ChatBotCommand`[] - -Defined in: [src/bot.ts:19](../src/bot.ts#L19) - -*** - -### createAddress? - -> `optional` **createAddress**: `boolean` - -Defined in: [src/bot.ts:14](../src/bot.ts#L14) - -*** - -### logContacts? - -> `optional` **logContacts**: `boolean` - -Defined in: [src/bot.ts:21](../src/bot.ts#L21) - -*** - -### logNetwork? - -> `optional` **logNetwork**: `boolean` - -Defined in: [src/bot.ts:22](../src/bot.ts#L22) - -*** - -### updateAddress? - -> `optional` **updateAddress**: `boolean` +> `optional` **addressSettings?**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) Defined in: [src/bot.ts:15](../src/bot.ts#L15) *** -### updateProfile? +### allowFiles? -> `optional` **updateProfile**: `boolean` +> `optional` **allowFiles?**: `boolean` Defined in: [src/bot.ts:16](../src/bot.ts#L16) *** -### useBotProfile? +### commands? -> `optional` **useBotProfile**: `boolean` +> `optional` **commands?**: `ChatBotCommand`[] + +Defined in: [src/bot.ts:17](../src/bot.ts#L17) + +*** + +### createAddress? + +> `optional` **createAddress?**: `boolean` + +Defined in: [src/bot.ts:12](../src/bot.ts#L12) + +*** + +### logContacts? + +> `optional` **logContacts?**: `boolean` + +Defined in: [src/bot.ts:19](../src/bot.ts#L19) + +*** + +### logNetwork? + +> `optional` **logNetwork?**: `boolean` Defined in: [src/bot.ts:20](../src/bot.ts#L20) + +*** + +### updateAddress? + +> `optional` **updateAddress?**: `boolean` + +Defined in: [src/bot.ts:13](../src/bot.ts#L13) + +*** + +### updateProfile? + +> `optional` **updateProfile?**: `boolean` + +Defined in: [src/bot.ts:14](../src/bot.ts#L14) + +*** + +### useBotProfile? + +> `optional` **useBotProfile?**: `boolean` + +Defined in: [src/bot.ts:18](../src/bot.ts#L18) diff --git a/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md b/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md new file mode 100644 index 0000000000..b035f41355 --- /dev/null +++ b/packages/simplex-chat-nodejs/docs/bot.TypeAlias.BotDbOpts.md @@ -0,0 +1,17 @@ +[**simplex-chat**](README.md) + +*** + +[simplex-chat](README.md) / [bot](Namespace.bot.md) / BotDbOpts + +# Type Alias: BotDbOpts + +> **BotDbOpts** = [`DbConfig`](api.TypeAlias.DbConfig.md) & `object` + +Defined in: [src/bot.ts:7](../src/bot.ts#L7) + +## Type Declaration + +### confirmMigrations? + +> `optional` **confirmMigrations?**: [`MigrationConfirmation`](core.Enumeration.MigrationConfirmation.md) diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md index c6082f2985..5bd0722f0c 100644 --- a/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatAPIError.md @@ -16,7 +16,7 @@ Defined in: [src/core.ts:92](../src/core.ts#L92) ### Constructor -> **new ChatAPIError**(`message`, `chatError`): `ChatAPIError` +> **new ChatAPIError**(`message`, `chatError?`): `ChatAPIError` Defined in: [src/core.ts:93](../src/core.ts#L93) @@ -26,9 +26,9 @@ Defined in: [src/core.ts:93](../src/core.ts#L93) `string` -##### chatError +##### chatError? -`ChatError` | `undefined` +`ChatError` \| `undefined` #### Returns @@ -74,7 +74,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ ### stack? -> `optional` **stack**: `string` +> `optional` **stack?**: `string` Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) diff --git a/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md index eff5123fb0..0feceae4fd 100644 --- a/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md +++ b/packages/simplex-chat-nodejs/docs/core.Class.ChatInitError.md @@ -74,7 +74,7 @@ Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1076](../node_modules/typ ### stack? -> `optional` **stack**: `string` +> `optional` **stack?**: `string` Defined in: [node\_modules/typescript/lib/lib.es5.d.ts:1078](../node_modules/typescript/lib/lib.es5.d.ts#L1078) diff --git a/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md index 906ef3ec3e..8d18997ec4 100644 --- a/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md +++ b/packages/simplex-chat-nodejs/docs/core.Interface.APIResult.md @@ -18,7 +18,7 @@ Defined in: [src/core.ts:87](../src/core.ts#L87) ### error? -> `optional` **error**: `ChatError` +> `optional` **error?**: `ChatError` Defined in: [src/core.ts:89](../src/core.ts#L89) @@ -26,6 +26,6 @@ Defined in: [src/core.ts:89](../src/core.ts#L89) ### result? -> `optional` **result**: `R` +> `optional` **result?**: `R` Defined in: [src/core.ts:88](../src/core.ts#L88) diff --git a/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js index 16d0678b64..8899e9bf15 100644 --- a/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js +++ b/packages/simplex-chat-nodejs/examples/squaring-bot-readme.js @@ -2,7 +2,7 @@ const {bot} = await import("../dist/index.js") const [chat, _user, _address] = await bot.run({ profile: {displayName: "Squaring bot example", fullName: ""}, - dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: "./squaring_bot"}, options: { addressSettings: {welcomeMessage: "Send a number, I will square it."}, }, diff --git a/packages/simplex-chat-nodejs/examples/squaring-bot.ts b/packages/simplex-chat-nodejs/examples/squaring-bot.ts index 682e7b887a..dbb58d90dc 100644 --- a/packages/simplex-chat-nodejs/examples/squaring-bot.ts +++ b/packages/simplex-chat-nodejs/examples/squaring-bot.ts @@ -5,7 +5,7 @@ import {bot, util} from "../dist" const welcomeMessage = "Hello! I am a simple squaring bot.\n\nIf you send me a number, I will calculate its square." const [chat, _user, _address] = await bot.run({ profile: {displayName: "Squaring bot example", fullName: ""}, - dbOpts: {dbFilePrefix: "./squaring_bot", dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: "./squaring_bot"}, options: { addressSettings: {autoAccept: true, welcomeMessage, businessAddress: false}, commands: [ // commands to show in client UI diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index 72a7fa93b4..f1337e6753 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -56,6 +56,41 @@ interface EventSubscriber { once: boolean } +/** + * Database configuration. The native library is built against exactly one + * backend (see `simplex_backend` / `SIMPLEX_BACKEND` at install time); this + * type makes the caller state which one they are targeting so field names + * can't lie about their meaning. + */ +export type DbConfig = + | { + /** SQLite backend (default). */ + type: "sqlite" + /** File prefix — two schema files are named `_chat.db` and `_agent.db`. */ + filePrefix: string + /** Optional SQLCipher encryption key. Empty/omitted = unencrypted. */ + encryptionKey?: string + } + | { + /** PostgreSQL backend (Linux x86_64 only, libpq5 required). */ + type: "postgres" + /** Schema prefix used to namespace tables. Defaults to `"simplex_v1"` when omitted. */ + schemaPrefix?: string + /** PostgreSQL connection string (e.g. `postgres://user:pass@host/db`). */ + connectionString: string + } + +function dbConfigToMigrateArgs(db: DbConfig): [string, string] { + switch (db.type) { + case "sqlite": + return [db.filePrefix, db.encryptionKey ?? ""] + case "postgres": + return [db.schemaPrefix ?? "", db.connectionString] + default: + throw new Error(`Invalid DbConfig: ${JSON.stringify(db satisfies never)}`) + } +} + /** * Main API class for interacting with the chat core library. */ @@ -64,21 +99,20 @@ export class ChatApi { private eventsLoop: Promise | undefined = undefined private subscribers: {[K in CEvt.Tag]?: EventSubscriber[]} = {} private receivers: EventSubscriberFunc[] = [] - + private constructor(protected ctrl_: bigint | undefined) {} /** * Initializes the ChatApi. - * @param {string} dbFilePrefix - File prefix for the database files. - * @param {string} [dbKey=""] - Database encryption key. + * @param {DbConfig} db - Database configuration (sqlite or postgres). * @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode. */ static async init( - dbFilePrefix: string, - dbKey: string = "", + db: DbConfig, confirm = core.MigrationConfirmation.YesUp ): Promise { - const ctrl = await core.chatMigrateInit(dbFilePrefix, dbKey, confirm) + const [path, key] = dbConfigToMigrateArgs(db) + const ctrl = await core.chatMigrateInit(path, key, confirm) return new ChatApi(ctrl) } diff --git a/packages/simplex-chat-nodejs/src/bot.ts b/packages/simplex-chat-nodejs/src/bot.ts index 95a0c13d96..f6cb753d27 100644 --- a/packages/simplex-chat-nodejs/src/bot.ts +++ b/packages/simplex-chat-nodejs/src/bot.ts @@ -4,9 +4,7 @@ import * as core from "./core" import * as util from "./util" import equal = require("fast-deep-equal") -export interface BotDbOpts { - dbFilePrefix: string // two schema files will be named _chat.db and _agent.db - dbKey?: string +export type BotDbOpts = api.DbConfig & { confirmMigrations?: core.MigrationConfirmation } @@ -47,7 +45,7 @@ export interface BotConfig { } export async function run({profile, dbOpts, options = defaultOpts, onMessage, onCommands = {}, events = {}}: BotConfig): Promise<[api.ChatApi, T.User, T.UserContactLink | undefined]> { - const bot = await api.ChatApi.init(dbOpts.dbFilePrefix, dbOpts.dbKey || "", dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp) + const bot = await api.ChatApi.init(dbOpts, dbOpts.confirmMigrations || core.MigrationConfirmation.YesUp) const opts = fullOptions(options) if (onMessage) subscribeMessages(bot, onMessage) if (Object.keys(onCommands).length > 0) subscribeCommands(bot, onCommands) diff --git a/packages/simplex-chat-nodejs/tests/api.test.ts b/packages/simplex-chat-nodejs/tests/api.test.ts index 7bc1a89b86..99d511371c 100644 --- a/packages/simplex-chat-nodejs/tests/api.test.ts +++ b/packages/simplex-chat-nodejs/tests/api.test.ts @@ -15,8 +15,8 @@ describe("API tests (use preset servers)", () => { it("should send/receive message", async () => { // create users and start chat controllers - const alice = await api.ChatApi.init(alicePath) - const bob = await api.ChatApi.init(bobPath) + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + const bob = await api.ChatApi.init({type: "sqlite", filePrefix: bobPath}) const servers: string[] = [] let eventCount = 0 alice.on("hostConnected" as CEvt.Tag, async ({transportHost}: any) => { servers.push(transportHost) }) @@ -67,10 +67,10 @@ describe("API tests (use preset servers)", () => { it("should create member contact and send invitation", async () => { // create 3 users and start chat controllers - const alice = await api.ChatApi.init(alicePath) - const bob = await api.ChatApi.init(bobPath) + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + const bob = await api.ChatApi.init({type: "sqlite", filePrefix: bobPath}) const carolPath = path.join(tmpDir, "carol") - const carol = await api.ChatApi.init(carolPath) + const carol = await api.ChatApi.init({type: "sqlite", filePrefix: carolPath}) const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) await bob.apiCreateActiveUser({displayName: "bob", fullName: ""}) await carol.apiCreateActiveUser({displayName: "carol", fullName: ""}) diff --git a/packages/simplex-chat-nodejs/tests/bot.test.ts b/packages/simplex-chat-nodejs/tests/bot.test.ts index b1fd9d0186..5a7faa663f 100644 --- a/packages/simplex-chat-nodejs/tests/bot.test.ts +++ b/packages/simplex-chat-nodejs/tests/bot.test.ts @@ -18,7 +18,7 @@ describe("Bot tests (use preset servers)", () => { // run bot const [chat, botUser, botAddress] = await bot.run({ profile: {displayName: "Squaring bot", fullName: ""}, - dbOpts: {dbFilePrefix: botPath, dbKey: ""}, + dbOpts: {type: "sqlite", filePrefix: botPath}, options: { addressSettings: {welcomeMessage: "If you send me a number, I will calculate its square."}, }, @@ -30,7 +30,7 @@ describe("Bot tests (use preset servers)", () => { }) assert(typeof botAddress === "object") // create user - const alice = await api.ChatApi.init(alicePath) + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) await alice.startChat() // connect to bot From a845bfb89b6294aacca12c408144905503d65b11 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Sat, 25 Apr 2026 08:46:34 +0000 Subject: [PATCH 15/77] simplex-chat-nodejs: bump version (#6873) --- packages/simplex-chat-nodejs/package.json | 2 +- packages/simplex-chat-nodejs/src/download-libs.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/simplex-chat-nodejs/package.json b/packages/simplex-chat-nodejs/package.json index 21e00e7399..6b340dc1f9 100644 --- a/packages/simplex-chat-nodejs/package.json +++ b/packages/simplex-chat-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "simplex-chat", - "version": "6.5.0-beta.4.5", + "version": "6.5.0-beta.4.6", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ diff --git a/packages/simplex-chat-nodejs/src/download-libs.js b/packages/simplex-chat-nodejs/src/download-libs.js index bd30e8f56d..39eb545576 100644 --- a/packages/simplex-chat-nodejs/src/download-libs.js +++ b/packages/simplex-chat-nodejs/src/download-libs.js @@ -4,7 +4,7 @@ const path = require('path'); const extract = require('extract-zip'); const GITHUB_REPO = 'simplex-chat/simplex-chat-libs'; -const RELEASE_TAG = 'v6.5.0-beta.9'; +const RELEASE_TAG = 'v6.5.0-beta.10'; const BACKEND = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || 'sqlite').toLowerCase(); if (BACKEND !== 'sqlite' && BACKEND !== 'postgres') { From 53a225a0c9879fa45792361abc76c2f5990b28a4 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 25 Apr 2026 10:20:26 +0100 Subject: [PATCH 16/77] directory: periodically update subscriber counts for registered channels and re-verify links and ownership (#6879) * directory: update subscriber counts for registered channels and re-verify links and ownership * refactor * rename * mapM_ * refactor * refactor * refactor more * more * different approach * rename * test * bot api types --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../src/Directory/Events.hs | 1 + .../src/Directory/Options.hs | 10 ++ .../src/Directory/Service.hs | 128 ++++++++++++++---- bots/api/TYPES.md | 11 ++ bots/src/API/Docs/Types.hs | 2 + .../types/typescript/src/types.ts | 6 + src/Simplex/Chat/Controller.hs | 10 +- src/Simplex/Chat/Library/Commands.hs | 29 ++-- src/Simplex/Chat/Library/Internal.hs | 18 +++ src/Simplex/Chat/View.hs | 2 +- tests/Bots/DirectoryTests.hs | 85 +++++++++++- 11 files changed, 251 insertions(+), 51 deletions(-) diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index ef6056fbc4..bfbc025a49 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -49,6 +49,7 @@ data DirectoryEvent | DEGroupInvitation {contact :: Contact, groupInfo :: GroupInfo, fromMemberRole :: GroupMemberRole, memberRole :: GroupMemberRole} | DEServiceJoinedGroup {contactId :: ContactId, groupInfo :: GroupInfo, hostMember :: GroupMember} | DEGroupUpdated {member :: GroupMember, fromGroup :: GroupInfo, toGroup :: GroupInfo} + | DEGroupLinkCheck GroupInfo | DEPendingMember GroupInfo GroupMember | DEPendingMemberMsg GroupInfo GroupMember ChatItemId Text | DEContactRoleChanged GroupInfo ContactId GroupMemberRole -- contactId here is the contact whose role changed diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs index 94305abaa2..f566ed5ded 100644 --- a/apps/simplex-directory-service/src/Directory/Options.hs +++ b/apps/simplex-directory-service/src/Directory/Options.hs @@ -42,6 +42,7 @@ data DirectoryOpts = DirectoryOpts runCLI :: Bool, searchResults :: Int, webFolder :: Maybe FilePath, + linkCheckInterval :: Int, testing :: Bool } @@ -162,6 +163,14 @@ directoryOpts appDir defaultDbName = do <> metavar "WEB_FOLDER" <> help "Folder to store static web assets" ) + linkCheckInterval <- + option + auto + ( long "link-check-interval" + <> metavar "SECONDS" + <> help "Interval in seconds to check public group link data (default: 1800)" + <> value 1800 + ) pure DirectoryOpts { coreOptions, @@ -182,6 +191,7 @@ directoryOpts appDir defaultDbName = do runCLI, searchResults = 10, webFolder, + linkCheckInterval, testing = False } diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index c1cf61f5a1..400f250979 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -18,8 +18,7 @@ module Directory.Service ) where -import Control.Concurrent (forkIO) -import Control.Concurrent.Async (race_) +import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.STM import Control.Exception (SomeException, try) import Control.Logger.Simple @@ -66,9 +65,10 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.View (serializeChatError, serializeChatResponse, simplexChatContact, viewContactName, viewGroupName) -import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ACreatedConnLink (..), ConnectionLink (..), CreatedConnLink (..), SConnectionMode (..), sameConnReqContact, sameShortLinkContact) +import Simplex.Messaging.Agent.Protocol (AConnectionLink (..), ACreatedConnLink (..), AgentErrorType (..), ConnectionLink (..), CreatedConnLink (..), SConnectionMode (..), sameConnReqContact, sameShortLinkContact) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Protocol (ErrorType (..)) import Simplex.Messaging.TMap (TMap) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Util (eitherToMaybe, raceAny_, safeDecodeUtf8, tshow, unlessM, (<$$>)) @@ -100,7 +100,9 @@ data ServiceState = ServiceState { searchRequests :: TMap ContactId SearchRequest, blockedWordsCfg :: BlockedWordsConfig, pendingCaptchas :: TMap GroupMemberId PendingCaptcha, - updateListingsJob :: TMVar ChatController + serviceCC :: TMVar ChatController, + eventQ :: TQueue DirectoryEvent, + updateListingsJob :: TMVar () } data CaptchaMode = CMText | CMAudio @@ -126,8 +128,10 @@ newServiceState opts = do searchRequests <- TM.emptyIO blockedWordsCfg <- readBlockedWordsConfig opts pendingCaptchas <- TM.emptyIO + serviceCC <- newEmptyTMVarIO + eventQ <- newTQueueIO updateListingsJob <- newEmptyTMVarIO - pure ServiceState {searchRequests, blockedWordsCfg, pendingCaptchas, updateListingsJob} + pure ServiceState {searchRequests, blockedWordsCfg, pendingCaptchas, serviceCC, eventQ, updateListingsJob} welcomeGetOpts :: IO DirectoryOpts welcomeGetOpts = do @@ -151,9 +155,8 @@ welcomeGetOpts = do directoryServiceCLI :: DirectoryLog -> DirectoryOpts -> IO () directoryServiceCLI st opts = do - env <- newServiceState opts - eventQ <- newTQueueIO - let eventHook cc resp = atomically $ resp <$ writeTQueue eventQ (cc, resp) + env@ServiceState {eventQ} <- newServiceState opts + let eventHook _cc resp = atomically $ resp <$ mapM_ (writeTQueue eventQ) (crDirectoryEvent resp) chatHooks = defaultChatHooks { preStartHook = Just $ directoryPreStartHook opts, @@ -163,14 +166,18 @@ directoryServiceCLI st opts = do } raceAny_ $ [ simplexChatCLI' terminalChatConfig {chatHooks} (mkChatOpts opts) Nothing, - processEvents eventQ env + processEvents env ] <> maybeToList (updateListingsThread_ opts env) + <> maybeToList (linkCheckThread_ opts env) where - processEvents eventQ env = forever $ do - (cc, resp) <- atomically $ readTQueue eventQ + processEvents env@ServiceState {eventQ} = do + cc <- atomically $ readTMVar $ serviceCC env u_ <- readTVarIO (currentUser cc) - forM_ u_ $ \user -> directoryServiceEvent st opts env user cc resp + forM_ u_ $ \user -> + forever $ do + event <- atomically $ readTQueue eventQ + directoryServiceEvent st opts env user cc event updateListingDelay :: Int updateListingDelay = 5 * 60 * 1000000 -- update every 5 minutes @@ -179,15 +186,30 @@ updateListingsThread_ :: DirectoryOpts -> ServiceState -> Maybe (IO ()) updateListingsThread_ opts env = updateListingsThread <$> webFolder opts where updateListingsThread f = do - cc <- atomically $ takeTMVar $ updateListingsJob env + cc <- atomically $ readTMVar $ serviceCC env forever $ do u <- readTVarIO $ currentUser cc forM_ u $ \user -> updateGroupListingFiles cc user f delay <- registerDelay updateListingDelay atomically $ void (takeTMVar $ updateListingsJob env) `orElse` unlessM (readTVar delay) retry -listingsUpdated :: ServiceState -> ChatController -> IO () -listingsUpdated env = void . atomically . tryPutTMVar (updateListingsJob env) +listingsUpdated :: ServiceState -> IO () +listingsUpdated env = void $ atomically $ tryPutTMVar (updateListingsJob env) () + +linkCheckThread_ :: DirectoryOpts -> ServiceState -> Maybe (IO ()) +linkCheckThread_ opts env@ServiceState {eventQ} + | linkCheckInterval opts > 0 = Just $ do + cc <- atomically $ readTMVar $ serviceCC env + forever $ do + threadDelay $ linkCheckInterval opts * 1000000 + u <- readTVarIO $ currentUser cc + forM_ u $ \user -> + withDB' "linkCheckThread" cc (\db -> getAllGroupRegs_ db user) >>= \case + Left e -> logError $ "linkCheckThread error: " <> T.pack e + Right grs -> forM_ grs $ \(gInfo, gr) -> + unless (groupRemoved $ groupRegStatus gr) $ + atomically $ writeTQueue eventQ $ DEGroupLinkCheck gInfo + | otherwise = Nothing directoryPreStartHook :: DirectoryOpts -> ChatController -> IO () directoryPreStartHook opts ChatController {config, chatStore} = runDirectoryMigrations opts config chatStore @@ -198,7 +220,8 @@ directoryPostStartHook opts@DirectoryOpts {noAddress, testing} env cc = Nothing -> putStrLn "No current user" >> exitFailure Just User {userId, profile = p@LocalProfile {preferences}} -> do unless noAddress $ initializeBotAddress' (not testing) cc - listingsUpdated env cc + void $ atomically $ tryPutTMVar (serviceCC env) cc + listingsUpdated env let cmds = fromMaybe [] $ preferences >>= commands_ unless (cmds == directoryCommands) $ do let prefs = (fromMaybe emptyChatPrefs preferences) {files = Just FilesPreference {allow = FANo}, commands = Just directoryCommands} :: Preferences @@ -227,7 +250,7 @@ directoryCommands = directoryService :: DirectoryLog -> DirectoryOpts -> ChatConfig -> IO () directoryService st opts cfg = do - env <- newServiceState opts + env@ServiceState {eventQ} <- newServiceState opts let chatHooks = defaultChatHooks { preStartHook = Just $ directoryPreStartHook opts, @@ -235,10 +258,16 @@ directoryService st opts cfg = do acceptMember = Just $ acceptMemberHook opts env } simplexChatCore cfg {chatHooks} (mkChatOpts opts) $ \user cc -> - maybe id race_ (updateListingsThread_ opts env) $ - forever $ do - (_, resp) <- atomically . readTBQueue $ outputQ cc - directoryServiceEvent st opts env user cc resp + raceAny_ $ + [ forever $ do + (_, resp) <- atomically . readTBQueue $ outputQ cc + mapM_ (atomically . writeTQueue eventQ) $ crDirectoryEvent resp, + forever $ do + event <- atomically $ readTQueue eventQ + directoryServiceEvent st opts env user cc event + ] + <> maybeToList (updateListingsThread_ opts env) + <> maybeToList (linkCheckThread_ opts env) acceptMemberHook :: DirectoryOpts -> ServiceState -> GroupInfo -> GroupLinkInfo -> Profile -> IO (Either GroupRejectionReason (GroupAcceptance, GroupMemberRole)) acceptMemberHook @@ -281,13 +310,13 @@ readBlockedWordsConfig DirectoryOpts {blockedFragmentsFile, blockedWordsFile, na unless testing $ putStrLn $ "Blocked fragments: " <> show (length blockedFragments) <> ", blocked words: " <> show (length blockedWords) <> ", spelling rules: " <> show (M.size spelling) pure BlockedWordsConfig {blockedFragments, blockedWords, extensionRules, spelling} -directoryServiceEvent :: DirectoryLog -> DirectoryOpts -> ServiceState -> User -> ChatController -> Either ChatError ChatEvent -> IO () -directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName, ownersGroup, searchResults} env@ServiceState {searchRequests} user@User {userId} cc event = - forM_ (crDirectoryEvent event) $ \case +directoryServiceEvent :: DirectoryLog -> DirectoryOpts -> ServiceState -> User -> ChatController -> DirectoryEvent -> IO () +directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName, ownersGroup, searchResults} env@ServiceState {searchRequests} user@User {userId} cc = \case DEContactConnected ct -> deContactConnected ct DEGroupInvitation {contact = ct, groupInfo = g, fromMemberRole, memberRole} -> deGroupInvitation ct g fromMemberRole memberRole DEServiceJoinedGroup ctId g owner -> deServiceJoinedGroup ctId g owner DEGroupUpdated {member, fromGroup, toGroup} -> deGroupUpdated member fromGroup toGroup + DEGroupLinkCheck g -> deGroupLinkCheck g DEPendingMember g m -> dePendingMember g m DEPendingMemberMsg g m ciId t -> dePendingMemberMsg g m ciId t DEContactRoleChanged g ctId role -> deContactRoleChanged g ctId role @@ -762,6 +791,47 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName let approveCmd = MCText $ "/approve " <> tshow groupId <> ":" <> viewName displayName <> " " <> tshow gaId <> if promoted then " promote=on" else "" sendComposedMessages cc (SRDirect cId) [msg, approveCmd] + deGroupLinkCheck :: GroupInfo -> IO () + deGroupLinkCheck gInfo@GroupInfo {groupId, groupProfile = GroupProfile {publicGroup = pg_}, groupSummary = summary} = + withGroupReg gInfo "link check" $ \gr@GroupReg {groupRegStatus, dbOwnerMemberId} -> + forM_ pg_ $ \pg@PublicGroupProfile {groupLink} -> + when (groupRegStatus == GRSActive || pendingApproval groupRegStatus) $ do + let link = ACL SCMContact $ CLShort groupLink + sendChatCmd cc (APIConnectPlan userId (Just link) True Nothing) >>= \case + Right (CRConnectionPlan _ _ (CPGroupLink (GLPKnown {groupInfo = g', groupUpdated = BoolDef updated, linkOwners = ListDef owners}))) -> + checkValidOwner dbOwnerMemberId owners $ do + when updated $ reapprove pg gr groupRegStatus g' + when (updated || summary /= groupSummary g') $ listingsUpdated env + Left (ChatErrorAgent {agentError = SMP _ err}) | linkDeleted err -> + setGroupStatus logError st env cc groupId GRSRemoved $ \gr' -> + notifyOwner gr' "The channel link is no longer valid.\nThe channel is removed from the directory." + _ -> pure () + where + linkDeleted = \case + AUTH -> True + BLOCKED {} -> True + _ -> False + checkValidOwner dbOwnerMemberId owners onValid = case dbOwnerMemberId of + Just ownerGMId -> + withDB "checkGroupLink" cc (\db -> withExceptT show $ getGroupMember db (vr cc) user groupId ownerGMId) >>= \case + Right GroupMember {memberId, memberPubKey} + | any (\GroupLinkOwner {memberId = mId, memberKey} -> memberId == mId && memberPubKey == Just memberKey) owners -> onValid + _ -> setGroupStatus logError st env cc groupId GRSSuspendedBadRoles $ \gr' -> + notifyOwner gr' "The registration owner is no longer a channel owner.\nThe channel is no longer listed in the directory." + Nothing -> onValid + reapprove pg gr groupRegStatus g' = do + let gt = groupTypeStr' pg + groupRef = groupReference gInfo + notifyAdminUsers $ "The " <> gt <> " " <> groupRef <> " profile changed." + case groupRegStatus of + GRSActive -> + setGroupStatus notifyAdminUsers st env cc groupId (GRSPendingApproval 1) $ \gr' -> do + notifyOwner gr' $ "The " <> gt <> " profile has changed.\nIt is hidden from the directory until approved." + sendToApprove g' gr' 1 + GRSPendingApproval n -> + sendToApprove g' gr (n + 1) + _ -> pure () + deContactRoleChanged :: GroupInfo -> ContactId -> GroupMemberRole -> IO () deContactRoleChanged g@GroupInfo {groupId, membership = GroupMember {memberRole = serviceRole}} ctId contactRole = do logInfo $ "contact ID " <> tshow ctId <> " role changed in group " <> viewGroupName g <> " to " <> tshow contactRole @@ -893,8 +963,8 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName (_, Just (OVFailed reason)) -> sendMessage cc ct $ "Link signature verification failed: " <> reason <> ".\nYou must be the " <> gt <> " owner to register it." (Nothing, _) -> sendMessage cc ct $ "Error: no " <> gt <> " information available via the link." _ -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership. Please report it to directory admins." - GLPKnown {groupInfo = g, groupUpdated, ownerVerification} -> case ownerVerification of - Just OVVerified -> deReregistration ct g groupUpdated ownerSig + GLPKnown {groupInfo = g, groupUpdated = BoolDef updated, ownerVerification} -> case ownerVerification of + Just OVVerified -> deReregistration ct g updated ownerSig Just (OVFailed reason) -> sendMessage cc ct $ "Link signature verification failed: " <> reason <> ".\nYou must be the " <> gt <> " owner to register it." Nothing -> sendMessage cc ct $ "Error: could not verify " <> gt <> " ownership." GLPConnectingProhibit _ -> sendMessage cc ct $ "Already connecting to this " <> gt <> "." @@ -1408,7 +1478,7 @@ setGroupStatusPromo sendReply st env cc GroupReg {dbGroupId = gId} grStatus' grP Left e -> sendReply $ "Error updating group " <> tshow gId <> " status: " <> T.pack e Right (status, grPromoted) -> do when ((status == DSListed || status' == DSListed) && (status /= status' || grPromoted /= grPromoted')) $ - listingsUpdated env cc + listingsUpdated env logGUpdateStatus st gId grStatus' logGUpdatePromotion st gId grPromoted' continue @@ -1428,7 +1498,7 @@ setGroupStatus sendMsg st env cc gId grStatus' continue = do Left e -> sendMsg $ "Error updating group " <> tshow gId <> " status: " <> T.pack e Right (grStatus, gr) -> do let status = grDirectoryStatus grStatus - when ((status == DSListed || status' == DSListed) && status /= status') $ listingsUpdated env cc + when ((status == DSListed || status' == DSListed) && status /= status') $ listingsUpdated env logGUpdateStatus st gId grStatus' continue gr @@ -1437,7 +1507,7 @@ setGroupPromoted sendReply st env cc GroupReg {dbGroupId = gId} grPromoted' cont setGroupPromotedStore cc gId grPromoted' >>= \case Left e -> sendReply $ "Error updating group " <> tshow gId <> " status: " <> T.pack e Right (status, grPromoted) -> do - when (status == DSListed && grPromoted' /= grPromoted) $ listingsUpdated env cc + when (status == DSListed && grPromoted' /= grPromoted) $ listingsUpdated env logGUpdatePromotion st gId grPromoted' continue diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 2e4f64dcdd..ccef82eca9 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -95,6 +95,7 @@ This file is generated automatically. - [GroupInfo](#groupinfo) - [GroupKeys](#groupkeys) - [GroupLink](#grouplink) +- [GroupLinkOwner](#grouplinkowner) - [GroupLinkPlan](#grouplinkplan) - [GroupMember](#groupmember) - [GroupMemberAdmission](#groupmemberadmission) @@ -2264,6 +2265,15 @@ MemberSupport: - acceptMemberRole: [GroupMemberRole](#groupmemberrole) +--- + +## GroupLinkOwner + +**Record type**: +- memberId: string +- memberKey: string + + --- ## GroupLinkPlan @@ -2292,6 +2302,7 @@ Known: - groupInfo: [GroupInfo](#groupinfo) - groupUpdated: bool - ownerVerification: [OwnerVerification](#ownerverification)? +- linkOwners: [[GroupLinkOwner](#grouplinkowner)] NoRelays: - type: "noRelays" diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index de5b721b2d..50adf6f7e5 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -278,6 +278,7 @@ chatTypesDocsData = (sti @GroupKeys, STRecord, "", [], "", ""), (sti @GroupRootKey, STUnion, "GRK", [], "", ""), (sti @GroupLink, STRecord, "", [], "", ""), + (sti @GroupLinkOwner, STRecord, "", [], "", ""), (sti @GroupLinkPlan, STUnion, "GLP", [], "", ""), (sti @GroupMember, STRecord, "", [], "", ""), (sti @GroupMemberAdmission, STRecord, "", [], "", ""), @@ -482,6 +483,7 @@ deriving instance Generic GroupInfo deriving instance Generic GroupKeys deriving instance Generic GroupRootKey deriving instance Generic GroupLink +deriving instance Generic GroupLinkOwner deriving instance Generic GroupLinkPlan deriving instance Generic GroupMember deriving instance Generic GroupMemberAdmission diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 08cb225cbc..7cc9205ff4 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -2565,6 +2565,11 @@ export interface GroupLink { acceptMemberRole: GroupMemberRole } +export interface GroupLinkOwner { + memberId: string + memberKey: string +} + export type GroupLinkPlan = | GroupLinkPlan.Ok | GroupLinkPlan.OwnLink @@ -2612,6 +2617,7 @@ export namespace GroupLinkPlan { groupInfo: GroupInfo groupUpdated: boolean ownerVerification?: OwnerVerification + linkOwners: GroupLinkOwner[] } export interface NoRelays extends Interface { diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index a7f4ceced0..0b263a6fa2 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -1037,10 +1037,16 @@ data GroupLinkPlan | GLPOwnLink {groupInfo :: GroupInfo} | GLPConnectingConfirmReconnect | GLPConnectingProhibit {groupInfo_ :: Maybe GroupInfo} - | GLPKnown {groupInfo :: GroupInfo, groupUpdated :: Bool, ownerVerification :: Maybe OwnerVerification} + | GLPKnown {groupInfo :: GroupInfo, groupUpdated :: BoolDef, ownerVerification :: Maybe OwnerVerification, linkOwners :: ListDef GroupLinkOwner} | GLPNoRelays {groupSLinkData_ :: Maybe GroupShortLinkData} deriving (Show) +data GroupLinkOwner = GroupLinkOwner + { memberId :: MemberId, + memberKey :: C.PublicKeyEd25519 + } + deriving (Show) + data OwnerVerification = OVVerified | OVFailed {reason :: Text} @@ -1662,6 +1668,8 @@ $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CAP") ''ContactAddressPlan) $(JQ.deriveJSON defaultJSON ''GroupShortLinkInfo) +$(JQ.deriveJSON defaultJSON ''GroupLinkOwner) + $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FC") ''ForwardConfirmation) diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 7a5309164b..543014a346 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1772,15 +1772,14 @@ processChatCommand vr nm = \case APIGroupInfo gId -> withUser $ \user -> CRGroupInfo user <$> withFastStore (\db -> getGroupInfo db vr user gId) APIGetUpdatedGroupLinkData groupId -> withUser $ \user -> do - gInfo@GroupInfo {groupProfile = GroupProfile {publicGroup}} <- withFastStore $ \db -> getGroupInfo db vr user groupId - case publicGroup of - Just PublicGroupProfile {groupLink = sLnk} | useRelays' gInfo -> do + gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} <- withFastStore $ \db -> getGroupInfo db vr user groupId + case p of + GroupProfile {publicGroup = Just PublicGroupProfile {groupLink = sLnk}} | useRelays' gInfo -> do (_, cData) <- getShortLinkConnReq nm user sLnk groupSLinkData_ <- liftIO $ decodeLinkUserData cData - let publicGroupData_ = groupSLinkData_ >>= \GroupShortLinkData {publicGroupData} -> publicGroupData - publicMemberCount_ = (\PublicGroupData {publicMemberCount} -> publicMemberCount) <$> publicGroupData_ - gInfo' <- fromMaybe gInfo - <$> forM publicMemberCount_ (\count -> withFastStore $ \db -> setPublicMemberCount db vr user gInfo count) + gInfo' <- case groupSLinkData_ of + Just sLinkData -> fst <$> updateGroupFromLinkData user gInfo sLinkData + _ -> pure gInfo pure $ CRGroupInfo user gInfo' _ -> throwCmdError "group link data not available" APIGroupMemberInfo gId gMemberId -> withUser $ \user -> do @@ -4037,10 +4036,10 @@ processChatCommand vr nm = \case where l' = serverShortLink l con cReq = ACCL SCMContact $ CCLink cReq (Just l') - gPlan (cReq, g) = if memberRemoved (membership g) then Nothing else Just (con cReq, CPGroupLink (GLPKnown g False Nothing)) + gPlan (cReq, g) = if memberRemoved (membership g) then Nothing else Just (con cReq, CPGroupLink (GLPKnown g (BoolDef False) Nothing (ListDef []))) groupShortLinkPlan = knownLinkPlans >>= \case - Just (_, CPGroupLink (GLPKnown g _ _)) + Just (_, CPGroupLink (GLPKnown g _ _ _)) | resolveKnown -> resolveKnownGroup g Just r -> pure r Nothing -> do @@ -4065,15 +4064,15 @@ processChatCommand vr nm = \case liftIO (getGroupInfoViaUserShortLink db vr user l') >>= \case Just (cReq, g) -> pure $ Just (con cReq, CPGroupLink (GLPOwnLink g)) Nothing -> (gPlan =<<) <$> getGroupViaShortLinkToConnect db vr user l' - resolveKnownGroup g@GroupInfo {groupProfile = p} = do + resolveKnownGroup g = do (fd@FixedLinkData {rootKey = rk}, cData@(ContactLinkData _ UserContactData {owners})) <- getShortLinkConnReq' nm user l' groupSLinkData_ <- liftIO $ decodeLinkUserData cData let ov = verifyLinkOwner rk owners l' sig_ + glOwners = map (\OwnerAuth {ownerId, ownerKey} -> GroupLinkOwner {memberId = MemberId ownerId, memberKey = ownerKey}) owners (g', updated) <- case groupSLinkData_ of - Just GroupShortLinkData {groupProfile} - | p /= groupProfile -> (,True) <$> withStore (\db -> updateGroupProfile db user g groupProfile) + Just sLinkData -> updateGroupFromLinkData user g sLinkData _ -> pure (g, False) - pure (con (linkConnReq fd), CPGroupLink (GLPKnown g' updated ov)) + pure (con (linkConnReq fd), CPGroupLink (GLPKnown g' (BoolDef updated) ov (ListDef glOwners))) connectWithPlan :: User -> IncognitoEnabled -> ACreatedConnLink -> ConnectionPlan -> CM ChatResponse connectWithPlan user@User {userId} incognito ccLink plan | connectionPlanProceed plan = do @@ -4153,10 +4152,10 @@ processChatCommand vr nm = \case (Just gInfo, _) -> groupPlan gInfo linkInfo gld ov groupPlan :: GroupInfo -> Maybe GroupShortLinkInfo -> Maybe GroupShortLinkData -> Maybe OwnerVerification -> CM ConnectionPlan groupPlan gInfo@GroupInfo {membership} linkInfo gld ov - | memberStatus membership == GSMemRejected = pure $ CPGroupLink (GLPKnown gInfo False ov) + | memberStatus membership == GSMemRejected = pure $ CPGroupLink (GLPKnown gInfo (BoolDef False) ov (ListDef [])) | not (memberActive membership) && not (memberRemoved membership) = pure $ CPGroupLink (GLPConnectingProhibit $ Just gInfo) - | memberActive membership = pure $ CPGroupLink (GLPKnown gInfo False ov) + | memberActive membership = pure $ CPGroupLink (GLPKnown gInfo (BoolDef False) ov (ListDef [])) | otherwise = pure $ CPGroupLink (GLPOk linkInfo gld ov) contactCReqSchemas :: ConnReqUriData -> (ConnReqContact, ConnReqContact) contactCReqSchemas crData = diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index f82b6884b2..c6203b4b42 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1328,6 +1328,24 @@ updatePublicGroupData user gInfo pure gInfo' | otherwise = pure gInfo +updateGroupFromLinkData :: User -> GroupInfo -> GroupShortLinkData -> CM (GroupInfo, Bool) +updateGroupFromLinkData user gInfo@GroupInfo {groupProfile = p, groupSummary = GroupSummary {publicMemberCount = localCount}} GroupShortLinkData {groupProfile, publicGroupData} + | profileChanged || countChanged = do + vr <- chatVersionRange + withStore $ \db -> do + g <- if profileChanged then updateGroupProfile db user gInfo groupProfile else pure gInfo + g' <- case publicGroupData of + Just PublicGroupData {publicMemberCount} | countChanged -> + setPublicMemberCount db vr user g publicMemberCount + _ -> pure g + pure (g', profileChanged) + | otherwise = pure (gInfo, False) + where + profileChanged = p /= groupProfile + countChanged = case publicGroupData of + Just PublicGroupData {publicMemberCount} -> Just publicMemberCount /= localCount + _ -> False + -- TODO [relays] owner: set owners on updating link data (multi-owner) groupLinkData :: GroupInfo -> GroupLink -> [GroupRelay] -> (UserConnLinkData 'CMContact, CRClientData) groupLinkData gInfo@GroupInfo {groupProfile, groupSummary = GroupSummary {publicMemberCount}, membership = GroupMember {memberId}, groupKeys} GroupLink {groupLinkId} groupRelays = diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 2bd48d297a..98eb811f5a 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -2103,7 +2103,7 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case GLPConnectingConfirmReconnect -> [grpLink "connecting, allowed to reconnect"] GLPConnectingProhibit Nothing -> [grpLink "connecting"] GLPConnectingProhibit (Just g) -> connecting g - GLPKnown g@GroupInfo {preparedGroup, membership = m} _ _ -> case preparedGroup of + GLPKnown g@GroupInfo {preparedGroup, membership = m} _ _ _ -> case preparedGroup of Just PreparedGroup {connLinkStartedConnection} -> case memberStatus m of GSMemUnknown | connLinkStartedConnection -> connecting g diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index bf2f1c8128..b57127eced 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -93,6 +93,7 @@ directoryServiceTests = do it "should reject card shared by non-owner" testNonOwnerSharesCard it "should delete channel registration and leave" testDeleteChannelRegistration it "should handle re-registration when already listed" testReregistrationAlreadyListed + it "should update subscriber count periodically" testLinkCheckUpdatesCount directoryProfile :: Profile directoryProfile = Profile {displayName = "SimpleX Directory", fullName = "", shortDescr = Nothing, image = Nothing, contactLink = Nothing, peerType = Just CPTBot, preferences = Nothing} @@ -128,6 +129,7 @@ mkDirectoryOpts TestParams {tmpPath = ps} superUsers ownersGroup webFolder = runCLI = False, searchResults = 3, webFolder, + linkCheckInterval = 0, testing = True } @@ -1976,7 +1978,7 @@ testRegisterChannelViaCard ps = superUser <## "news" superUser <##. "Link to join channel: " superUser <## "You need SimpleX Chat app v6.5 to join." - superUser <## "2 subscribers" + superUser <## "1 subscribers" superUser <## "" superUser <## "To approve send:" superUser <# "'SimpleX Directory'> /approve 1:news 1" @@ -1999,7 +2001,7 @@ testRegisterChannelViaCard ps = superUser <## "news (News and Updates)" superUser <##. "Link to join channel: " superUser <## "You need SimpleX Chat app v6.5 to join." - superUser <## "3 subscribers" + superUser <## "2 subscribers" superUser <## "" superUser <## "To approve send:" superUser <# "'SimpleX Directory'> /approve 1:news 1" @@ -2074,7 +2076,7 @@ testDeleteChannelRegistration ps = superUser <## "news" superUser <##. "Link to join channel: " superUser <## "You need SimpleX Chat app v6.5 to join." - superUser <## "2 subscribers" + superUser <## "1 subscribers" superUser <## "" superUser <## "To approve send:" superUser <# "'SimpleX Directory'> /approve 1:news 1" @@ -2118,7 +2120,7 @@ testReregistrationAlreadyListed ps = superUser <## "news" superUser <##. "Link to join channel: " superUser <## "You need SimpleX Chat app v6.5 to join." - superUser <## "2 subscribers" + superUser <## "1 subscribers" superUser <## "" superUser <## "To approve send:" superUser <# "'SimpleX Directory'> /approve 1:news 1" @@ -2135,7 +2137,7 @@ testReregistrationAlreadyListed ps = bob <# "'SimpleX Directory'> news" bob <##. "Link to join channel: " bob <## "You need SimpleX Chat app v6.5 to join." - bob <## "3 subscribers" + bob <## "1 subscribers" -- owner re-shares card while already listed bob ##> "/share chat #news @'SimpleX Directory'" bob <# "@'SimpleX Directory' link to join channel #news (signed):" @@ -2143,6 +2145,79 @@ testReregistrationAlreadyListed ps = _ <- getTermLine bob -- ownerSig JSON bob <# "'SimpleX Directory'> Channel is already listed in the directory." +testLinkCheckUpdatesCount :: HasCallStack => TestParams -> IO () +testLinkCheckUpdatesCount ps = do + dsLink <- + withNewTestChatCfg ps testCfg serviceDbPrefix directoryProfile $ \ds -> + withNewTestChatCfg ps testCfg "super_user" aliceProfile $ \superUser -> do + connectUsers ds superUser + ds ##> "/ad" + getContactLink ds True + let opts = (mkDirectoryOpts ps [KnownContact 2 "alice"] Nothing Nothing) {linkCheckInterval = 1} + runDirectory testCfg opts $ + withTestChatCfg ps testCfg "super_user" $ \superUser -> do + superUser <## "subscribed 1 connections on server localhost" + withNewTestChatCfg ps testCfg "bob" bobProfile $ \bob -> + withRelay ps $ \relay -> + withNewTestChatCfg ps testCfg "cath" cathProfile $ \cath -> do + bob `connectVia` dsLink + (shortLink, fullLink) <- prepareChannel1Relay "news" bob relay + -- register and approve + bob ##> "/share chat #news @'SimpleX Directory'" + bob <# "@'SimpleX Directory' link to join channel #news (signed):" + _ <- getTermLine bob -- short link + _ <- getTermLine bob -- ownerSig JSON + bob <# "'SimpleX Directory'> Joining the channel news…" + concurrentlyN_ + [ do + relay <## "'SimpleX Directory': accepting request to join group #news..." + relay <## "#news: 'SimpleX Directory' joined the group", + bob <## "#news: relay added 'SimpleX Directory_1' to the group" + ] + bob <# "'SimpleX Directory'> Joined the channel news. Registration is pending approval — it may take up to 48 hours." + superUser <# "'SimpleX Directory'> bob submitted the channel ID 1:" + superUser <## "news" + superUser <##. "Link to join channel: " + superUser <## "You need SimpleX Chat app v6.5 to join." + superUser <## "1 subscribers" + superUser <## "" + superUser <## "To approve send:" + superUser <# "'SimpleX Directory'> /approve 1:news 1" + let approve = "/approve 1:news 1" + superUser #> ("@'SimpleX Directory' " <> approve) + superUser <# ("'SimpleX Directory'> > " <> approve) + superUser <## " Channel approved!" + bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") + bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." + -- search shows initial count + bob #> "@'SimpleX Directory' news" + bob <# "'SimpleX Directory'> > news" + bob <## " Found 1 group(s)." + bob <# "'SimpleX Directory'> news" + bob <##. "Link to join channel: " + bob <## "You need SimpleX Chat app v6.5 to join." + bob <## "1 subscribers" + -- link check updates count (bot joined) + threadDelay 1000000 + bob #> "@'SimpleX Directory' news" + bob <# "'SimpleX Directory'> > news" + bob <## " Found 1 group(s)." + bob <# "'SimpleX Directory'> news" + bob <##. "Link to join channel: " + bob <## "You need SimpleX Chat app v6.5 to join." + bob <## "2 subscribers" + -- second subscriber joins + memberJoinChannel "news" [relay] [bob] shortLink fullLink cath + -- link check updates count again + threadDelay 1000000 + bob #> "@'SimpleX Directory' news" + bob <# "'SimpleX Directory'> > news" + bob <## " Found 1 group(s)." + bob <# "'SimpleX Directory'> news" + bob <##. "Link to join channel: " + bob <## "You need SimpleX Chat app v6.5 to join." + bob <## "3 subscribers" + testGetCaptchaStr :: HasCallStack => TestParams -> IO () testGetCaptchaStr _ps = do s0 <- getCaptchaStr 0 "" From 680ba0e1a8b28210e9da51b6da19c9c7dbc0332c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 25 Apr 2026 11:09:10 +0100 Subject: [PATCH 17/77] website: translations (#6882) * Translated using Weblate (Hungarian) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Spanish) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Spanish) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * Translated using Weblate (Japanese) Currently translated at 77.0% (285 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Italian) Currently translated at 98.9% (366 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/it/ * Translated using Weblate (German) Currently translated at 100.0% (370 of 370 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/de/ * fix translations --------- Co-authored-by: summoner001 Co-authored-by: No name Co-authored-by: tetra Co-authored-by: Random Co-authored-by: mlanp Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- website/langs/de.json | 10 +++++----- website/langs/es.json | 4 ++-- website/langs/hu.json | 34 +++++++++++++++++----------------- website/langs/it.json | 8 ++++---- website/langs/ja.json | 30 +++++++++++++++++------------- 5 files changed, 45 insertions(+), 41 deletions(-) diff --git a/website/langs/de.json b/website/langs/de.json index cc79255259..c35cd8fba6 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -17,7 +17,7 @@ "simplex-explained-tab-2-p-1": "Für jede Verbindung nutzen Sie zwei separate Nachrichten-Warteschlangen, um die Nachrichten über verschiedene Server zu senden und zu empfangen.", "simplex-explained-tab-2-p-2": "Die Server leiten Nachrichten immer nur in eine Richtung weiter, ohne den vollständigen Verlauf der Nutzer-Unterhaltungen oder seiner Verbindungen zu kennen.", "simplex-explained-tab-3-p-1": "Die Server nutzen für jede Warteschlange separate, anonyme Anmeldeinformationen und wissen nicht, welchem Nutzer diese gehören.", - "simplex-explained-tab-3-p-2": "Durch die Verwendung von Tor-Zugangsservern können Nutzer ihre Metadaten-Privatsphäre weiter verbessern und Korellationen von IP-Adressen verhindern.", + "simplex-explained-tab-3-p-2": "Durch die Verwendung von Tor-Zugangsservern können Nutzer ihre Metadaten-Privatsphäre weiter verbessern und Korrelationen von IP-Adressen verhindern.", "smp-protocol": "SMP-Protokoll", "chat-bot-example": "Beispiel für einen Chatbot", "donate": "Spenden", @@ -68,7 +68,7 @@ "simplex-private-card-9-point-1": "Jede Nachrichten-Warteschlange leitet Nachrichten mit unterschiedlichen Sende- und Empfängeradressen jeweils nur in einer Richtung weiter.", "simplex-private-card-9-point-2": "Verglichen mit traditionellen Nachrichten-Brokern, werden mögliche Angriffs-Vektoren und vorhandene Metadaten reduziert.", "simplex-private-card-10-point-1": "SimpleX nutzt für jeden Nutzer-Kontakt oder jedes Gruppenmitglied eigene temporäre, anonyme und paarweise Adressen und Berechtigungsnachweise.", - "simplex-private-card-10-point-2": "SimpleX erlaubt es, Nachrichten ohne Nutzerprofil-Bezeichner zu versenden und bietet dabei bessere Metadaten-Privatsphäre an als andere Alternativen.", + "simplex-private-card-10-point-2": "SimpleX ermöglicht die Zustellung von Nachrichten ohne Nutzerprofil-Kennungen und bietet dabei bessere Metadaten-Privatsphäre als andere Alternativen.", "simplex-unique-4-title": "Sie besitzen das SimpleX-Netzwerk", "privacy-matters-1-title": "Werbung und Preisdiskriminierung", "privacy-matters-1-overlay-1-title": "Privatsphäre spart Ihnen Geld", @@ -197,13 +197,13 @@ "simplex-network-overlay-card-1-li-1": "P2P-Netzwerke vertrauen auf Varianten von DHT, um Nachrichten zu routen. DHT-Designs müssen zwischen Zustellungsgarantie und Latenz ausgleichen. Verglichen mit P2P bietet SimpleX sowohl eine bessere Zustellungsgarantie als auch eine niedrigere Latenz, weil eine Nachricht redundant und parallel über mehrere Server gesendet werden kann, wobei die durch den Empfänger ausgewählten Server genutzt werden. In P2P-Netzwerken werden Nachrichten sequentiell über O(log N)-Knoten gesendet, wobei die Knoten durch einen Algorithmus ausgewählt werden.", "simplex-unique-overlay-card-3-p-4": "Zwischen dem gesendeten und dem empfangenen Serververkehr gibt es keine gemeinsamen Kennungen oder Chiffriertexte — sodass ein Beobachter nicht ohne weiteres feststellen kann, wer mit wem kommuniziert, selbst wenn TLS kompromittiert wurde.", "simplex-unique-overlay-card-4-p-3": "Falls Sie Interesse daran haben, aktiv bei der Entwicklung des SimpleX-Netzwerks mitzuhelfen, z.B. einen Chatbot für SimpleX App-Nutzer zu entwickeln oder die Integration der SimpleX Chat-Bibliothek in mobile Apps voranzutreiben, kontaktieren Sie uns bitte für eine weitere Beratung und Unterstützung.", - "privacy-matters-overlay-card-1-p-4": "Das SimpleX-Netzwerk schützt die Privatsphäre Ihrer Verbindungen besser als jede Alternative und verhindert vollständig, dass Ihr sozialer Graph für Unternehmen oder Organisationen verfügbar wird. Selbst wenn Anwender die in der SimpleX Chat-App vorkonfigurierten Server verwenden, kennen die Server-Betreiber die Anzahl der Benutzer oder deren Verbindungen nicht.", + "privacy-matters-overlay-card-1-p-4": "Das SimpleX-Netzwerk schützt die Privatsphäre Ihrer Verbindungen besser als jede Alternative und verhindert vollständig, dass Ihr sozialer Graph für Unternehmen oder Organisationen einsehbar wird. Selbst wenn Anwender die in der SimpleX Chat-App vorkonfigurierten Server verwenden, kennen die Server-Betreiber die Anzahl der Benutzer oder deren Verbindungen nicht.", "contact-hero-header": "Sie haben eine Adresse zur Verbindung mit SimpleX Chat erhalten", "invitation-hero-header": "Sie haben einen Einmal-Link zur Verbindung mit SimpleX Chat erhalten", "privacy-matters-overlay-card-3-p-3": "Selbst in demokratischen Ländern werden normale Menschen, auch unter Nutzung ihrer „anonymen“ Benutzerkennungen, für das, was sie online teilen, verhaftet.", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat speichert alle Benutzerdaten ausschließlich auf den Endgeräten in einem portablen und verschlüsselten Datenbankformat, welches exportiert und auf jedes unterstützte Gerät übertragen werden kann.", "simplex-unique-overlay-card-2-p-2": "Auch wenn die optionale Benutzeradresse zum Versenden von Spam-Kontaktanfragen verwendet werden kann, können Sie sie ändern oder ganz löschen, ohne dass Ihre Verbindungen verloren gehen.", - "simplex-unique-overlay-card-4-p-2": "Das SimpleX-Netzwerk verwendet ein offenes Protokoll und bietet ein SDK zur Erstellung von Chatbots an. Dies ermöglicht die Erstellung von Diensten, mit denen Nutzer über SimpleX Chat-Apps interagieren können — wir sind gespannt, welche SimpleX-Dienste Sie entwickeln werden.", + "simplex-unique-overlay-card-4-p-2": "Das SimpleX-Netzwerk verwendet ein offenes Protokoll und bietet ein SDK zur Erstellung von Chatbots an. Dies ermöglicht die Erstellung von Diensten, mit denen Nutzer über SimpleX Chat-Apps interagieren können — wir freuen uns sehr darauf zu sehen, welche SimpleX-Dienste Sie entwickeln werden.", "simplex-unique-card-4-p-2": "Sie können SimpleX mit eigenen Servern oder mit den von uns zur Verfügung gestellten Servern verwenden — und sich trotzdem mit jedem Benutzer verbinden.", "why-simplex-is-unique": "Warum ist SimpleX einmalig", "contact-hero-p-1": "Die öffentlichen Schlüssel und die Adresse der Nachrichtenwarteschlange in diesem Link werden NICHT über das Netzwerk gesendet, wenn Sie diese Seite aufrufen — sie sind in dem Hash-Fragment der Link-URL enthalten.", @@ -275,7 +275,7 @@ "index-publications-optout-title": "Podcast-Interview von OptOut", "worlds-most-secure-messaging": "Das sicherste Messaging-System der Welt", "index-messaging-p1": "SimpleX-Messaging verfügt über modernste Ende-zu-Ende-Verschlüsselung.", - "index-messaging-p2": "Zu Ihrer Sicherheit und zum Schutz Ihrer Privatsphäre können Server weder Ihre Nachrichten sehen, noch mit wem Sie kommunizieren.", + "index-messaging-p2": "Zu Ihrer Sicherheit und zum Schutz Ihrer Privatsphäre können Server weder Ihre Nachrichten sehen, noch mit wem Sie kommunizieren.", "index-messaging-cta": "Lernen Sie mehr über SimpleX-Messaging", "index-nextweb-h2": "Sie besitzen die
Zukunft des Webs", "index-nextweb-p1": "SimpleX wurde aus der Überzeugung heraus entwickelt, dass Sie Eigentümer Ihrer Profile, Kontakte und Communitys bleiben müssen.", diff --git a/website/langs/es.json b/website/langs/es.json index 421cdbe9bd..6a9737ba1d 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -336,7 +336,7 @@ "file-drop-text": "Arrastra y suelta el archivo aquí", "file-drop-hint": "o", "file-choose": "Seleccionar archivo", - "file-max-size": "Max 100 MB - La app SimpleX Chat soporta archivos hasta 1GB", + "file-max-size": "Max 100 MB - La app SimpleX Chat soporta archivos hasta 1GB", "file-encrypting": "Cifrando…", "file-uploading": "Subiendo…", "file-cancel": "Cancelar", @@ -347,7 +347,7 @@ "file-expiry": "Normalmente los archivos están disponibles durante 48 horas.", "file-sec-1": "Tu archivo se ha cifrado en el navegador, los routers de datos nunca ven el contenido, el nombre o el tamaño.", "file-sec-2": "La clave de cifrado está en el fragmento hash del enlace, nunca se envía a ningún servidor.", - "file-sec-3": "Para mejor seguridad, usa la app SimpleX Chat.", + "file-sec-3": "Para mejor seguridad, usa la app SimpleX Chat.", "file-retry": "Reintentar", "file-downloading": "Descargando…", "file-decrypting": "Descifrando…", diff --git a/website/langs/hu.json b/website/langs/hu.json index da5de1128d..f1c58a2cde 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -11,7 +11,7 @@ "simplex-explained-tab-1-text": "1. Felhasználói élmény", "simplex-explained-tab-2-text": "2. Hogyan működik", "simplex-explained-tab-3-text": "3. Mit látnak a kiszolgálók", - "simplex-explained-tab-1-p-1": "Csoportokat hozhat létre, valamint kétirányú beszélgetéseket folytathat a partnereivel, ugyanúgy mint bármely más üzenetváltó-alkalmazásban.", + "simplex-explained-tab-1-p-1": "Csoportokat hozhat létre, valamint kétirányú beszélgetéseket folytathat a partnereivel, ugyanúgy mint bármely más üzenetváltó alkalmazásban.", "simplex-explained-tab-1-p-2": "Hogyan működhet egyirányú várólistával és felhasználói profilazonosítók nélkül?", "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési várólistát használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyetlen irányba továbbítják az üzeneteket, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", @@ -25,7 +25,7 @@ "terminal-cli": "Terminál CLI", "terms-and-privacy-policy": "Adatvédelmi irányelvek", "hero-header": "Újraértelmezett adatvédelem", - "hero-subheader": "Az első üzenetváltó-alkalmazás
felhasználói azonosítók nélkül", + "hero-subheader": "Az első üzenetváltó alkalmazás
felhasználói azonosítók nélkül", "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
A SimpleX azonban nem, még véletlenszerű számokkal sem.
Ez radikálisan javítja az adatvédelmet.", "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", @@ -66,7 +66,7 @@ "simplex-private-card-7-point-2": "Ha bármilyen üzenetet hozzáadnak, eltávolítanak vagy módosítanak, a címzett értesítést kap róla.", "simplex-private-card-8-point-1": "A SimpleX kiszolgálók alacsony késleltetésű keverési csomópontokként működnek — a bejövő és kimenő üzenetek sorrendje eltérő.", "simplex-private-card-9-point-1": "Minden várólista az üzenetekhez egy irányba továbbítja az üzeneteket, a különböző küldési és fogadási címeken.", - "simplex-private-card-9-point-2": "Kevesebb támadási felülettel rendelkezik, mint a hagyományos üzenetváltó-alkalmazások, és kevesebb metaadatot tesz elérhetővé.", + "simplex-private-card-9-point-2": "Kevesebb támadási felülettel rendelkezik, mint a hagyományos üzenetváltó alkalmazások, és kevesebb metaadatot tesz elérhetővé.", "simplex-private-card-10-point-1": "A SimpleX ideiglenes, névtelen, páros címeket és hitelesítő adatokat használ minden egyes felhasználói kapcsolathoz vagy csoporttaghoz.", "simplex-private-card-10-point-2": "Lehetővé teszi az üzenetek felhasználói profilazonosítók nélküli kézbesítését, ami az alternatíváknál jobb metaadat-védelmet biztosít.", "privacy-matters-1-overlay-1-title": "Az adatvédelemmel pénzt spórol meg", @@ -99,19 +99,19 @@ "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a DHT valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet O(log N) csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenessel sem, és csak az üzenetekhez használ ideiglenes, páros azonosítókat, ami jobb névtelenséget és metaadat-védelmet biztosít.", "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a MITM-támadás problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.", - "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX kliensek csak az ismert kapcsolatból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", + "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX kliensek csak az ismert kapcsolatokból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hálózat sebezhető Sybil támadással, mert minden egyes csomópont felderíthető, és a hálózat egészként működik. A támadások enyhítésére szolgáló ismert intézkedés lehet egy központi kiszolgáló (például: tracker), vagy egy drága tanúsítvány. A SimpleX hálózat nem ismeri fel a kiszolgálókat, töredezett és több elszigetelt alhálózatként működik, ami lehetetlenné teszi az egész hálózatra kiterjedő támadásokat.", "privacy-matters-overlay-card-1-p-1": "Sok nagyvállalat arra használja fel a felhasználóival kapcsolatban álló személyek adatait, hogy megbecsülje a jövedelmi helyzetüket és olyan termékeket kínáljon fel, amelyekre valójában nincs is szükségük, valamint hogy meghatározza az árakat.", "privacy-matters-overlay-card-1-p-2": "Az online kiskereskedők tudják, hogy az alacsonyabb jövedelműek nagyobb valószínűséggel vásárolnak azonnal, ezért magasabb árakat számíthatnak fel, vagy eltörölhetik a kedvezményeket.", "privacy-matters-overlay-card-1-p-3": "Egyes pénzügyi és biztosítótársaságok szociális grafikonokat használnak a kamatlábak és a díjak meghatározásához. Ez gyakran arra készteti az alacsonyabb jövedelmű embereket, hogy többet fizessenek — ez az úgynevezett „szegénységi prémium”.", "privacy-matters-overlay-card-1-p-4": "A SimpleX hálózat minden alternatívánál jobban védi a kapcsolatai adatait, teljes mértékben megakadályozva, hogy az ismeretségi-hálója bármilyen vállalat vagy szervezet számára elérhetővé váljon. Még ha az emberek a SimpleX Chat által előre beállított kiszolgálókat is használják, sem az alkalmazások, sem a kiszolgálók üzemeltetői nem ismerik, sem felhasználók számát, sem a kapcsolataikat.", "privacy-matters-overlay-card-2-p-1": "Nem is olyan régen megfigyelhettük, hogy a nagy választásokat manipulálta egy neves tanácsadó cég, amely az ismeretségi-háló segítségével eltorzította a valós világról alkotott képünket, és manipulálta a szavazatainkat.", - "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha privát kommunikációs hálózatot használ, amely nem fér hozzá az ismeretségi hálójához.", + "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha egy privát kommunikációs hálózatot használ, amely nem fér hozzá az ismeretségi hálójához.", "privacy-matters-overlay-card-2-p-3": "A SimpleX az első olyan hálózat, amely eleve nem rendelkezik felhasználói azonosítókkal, így jobban védi az ismeretségi-hálóját, mint bármely ismert alternatíva.", "privacy-matters-overlay-card-3-p-1": "Mindenkinek törődnie kell a magánélet és a kommunikáció biztonságával — az ártalmatlan beszélgetések veszélybe sodorhatják, még akkor is, ha nincs semmi rejtegetnivalója.", "privacy-matters-overlay-card-3-p-2": "Az egyik legmegdöbbentőbb a Mohamedou Ould Salahi memoárjában leírt és az „A mauritániai” c. filmben bemutatott történet. Őt bírósági tárgyalás nélkül a guantánamói táborba zárták, és ott kínozták 15 éven át, miután egy afganisztáni rokonát telefonon felhívta, akit azzal gyanúsítottak a hatóságok, hogy köze van a 9/11-es merényletekhez, holott Salahi az előző 10 évben Németországban élt.", "privacy-matters-overlay-card-3-p-3": "Átlagos embereket letartóztatnak azért, amit online megosztanak, még „névtelen” fiókjaikon keresztül is, még demokratikus országokban is.", - "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó-alkalmazást használnunk, mindannyiunknak olyan üzenetváltó-alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", + "privacy-matters-overlay-card-3-p-4": "Nem elég csak egy végpontok között titkosított üzenetváltó alkalmazást használnunk, mindannyiunknak olyan üzenetváltó alkalmazásokat kell használnunk, amelyek védik a személyes partnereink magánéletét — akikkel kapcsolatban állunk.", "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő hálózatoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — a SimpleX kiszolgálók üzemeltetői nem tudják, hogy hányan használják a kiszolgálóikat.", "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX egyirányú várólistákat használ az üzenetekhez, páronkénti, névtelen címekkel, külön a fogadott és külön az elküldött üzenetekhez, általában különböző kiszolgálókon keresztül.", "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás védi partnerei adatait, elrejtve azt a SimpleX hálózat kiszolgálói és a külső megfigyelők elől. Az IP-címe elrejtésének érdekében a Tor hálózaton keresztül is kapcsolódhat a SimpleX kiszolgálókhoz.", @@ -150,7 +150,7 @@ "scan-qr-code-from-mobile-app": "QR-kód beolvasása mobilalkalmazásból", "to-make-a-connection": "A kapcsolat létrehozásához:", "install-simplex-app": "Telepítse a SimpleX alkalmazást", - "open-simplex-app": "Simplex alkalmazás megnyitása", + "open-simplex-app": "SimpleX alkalmazás megnyitása", "tap-the-connect-button-in-the-app": "Koppintson a „kapcsolódás” gombra az alkalmazásban", "scan-the-qr-code-with-the-simplex-chat-app": "Olvassa be a QR-kódot a SimpleX Chat alkalmazással", "scan-the-qr-code-with-the-simplex-chat-app-description": "A hivatkozásban szereplő nyilvános kulcsok és az üzenetek várólistájának címe NEM lesz elküldve a hálózaton keresztül, amikor megtekinti ezt az oldalt —
azokat, a hivatkozás webcímének kivonattöredéke tartalmazza.", @@ -165,7 +165,7 @@ "copy-the-command-below-text": "másolja be az alábbi parancsot, és használja a csevegésben:", "privacy-matters-section-header": "Miért számít az adatvédelem", "privacy-matters-section-subheader": "A metaadatok — például, hogy kivel beszélget — védelmének megőrzése biztonságot nyújt a következők ellen:", - "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó-alkalmazás amit használ nem fér hozzá az adataihoz!", + "privacy-matters-section-label": "Győződjön meg arról, hogy az üzenetváltó alkalmazás amit használ nem fér hozzá az adataihoz!", "simplex-private-section-header": "Mitől lesz a SimpleX privát", "simplex-network-section-header": "SimpleX hálózat", "simplex-network-section-desc": "A SimpleX Chat a P2P- és a föderált hálózatok előnyeinek kombinálásával biztosítja a legjobb adatvédelmet.", @@ -220,7 +220,7 @@ "jobs": "Csatlakozzon a csapathoz", "please-enable-javascript": "Engedélyezze a JavaScriptet a QR-kód megjelenítéséhez.", "please-use-link-in-mobile-app": "Használja a hivatkozást a SimpleX Chat alkalmazásban", - "contact-hero-header": "Meghívót kapott a SimpleX Chaten való beszélgetéshez", + "contact-hero-header": "Ez egy meghívó a SimpleX Chaten való beszélgetéshez", "invitation-hero-header": "Kapott egy egyszer használható meghívót a SimpleX Chaten való beszélgetéshez", "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független — a szabványos webes protokollokon, például WebSocketsen keresztül is működik.", "simplex-private-card-4-point-2": "A SimpleX, Tor hálózaton keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxyt (vagy a VPN-t az iOS-ban).", @@ -316,16 +316,16 @@ "navbar-token": "Token", "navbar-old-site": "Régi oldal", "docs-dropdown-15": "Összeállítások ellenőrzése és reprodukálása", - "why-p2": "Senki sem követte nyomon a beszélgetéseit. Senki sem készített térképet arról, hogy merre járt. A magánélet nem csak egy funkció volt, hanem egy életmód.", - "why-p3": "Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot — nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött — telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód.", + "why-p2": "Senki sem követte nyomon a beszélgetéseidet. Senki sem készített térképet arról, hogy merre jártál. A magánéleted nem csak egy funkció volt, hanem az életmódod.", + "why-p3": "Aztán felléptünk az internetre, és minden platform kért belőled egy darabot — neved, telefonszámod, barátaid. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött — telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód.", "why-p4": "De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá.", - "why-p5": "Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide — Ön itt szuverén.", - "why-p6": "A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános.", + "why-p5": "Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja a magánéletedet, de mégis nyilvántartást vezet minden látogatóról. Nem vagy vendég. Otthon vagy. Nincs az a hatalom, amely beléphetne ide — te vagy a szuverén.", + "why-p6": "A beszélgetéseid hozzád tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogatsz. Ez egy olyan hely, amelyet te hozol létre magadnak. És senki sem veheti el tőled, függetlenül attól, hogy privát vagy nyilvános.", "why-p7": "A legrégebbi emberi szabadság — beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének — olyan infrastruktúrán alapul, amely nem tudja elárulni.", - "why-p8": "Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe.", - "why-tagline": "Legyen szabad a saját hálózatában.", + "why-p8": "Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, ki vagy. Így a te hatalmad soha nem kerülhet idegen kezekbe.", + "why-tagline": "Légy szabad a saját hálózatodban.", "why-footer-link": "Miért készítjük", - "why-p1": "Ön fiók nélkül született.", + "why-p1": "Fiók nélkül születtél.", "file": "Fájl", "file-desc": "Fájlok biztonságos küldése végpontok közötti titkosítással – felhasználói fiókok és nyomon követés nélkül.", "file-noscript": "A fájlátvitelhez JavaScript szükséges.", @@ -348,7 +348,7 @@ "file-expiry": "A fájlok általában 48 óráig érhetők el.", "file-sec-1": "A fájl a böngészőben lett titkosítva – az útválasztók soha nem „látják” a fájl tartalmát, nevét és méretét.", "file-sec-2": "A titkosítási kulcs a hivatkozás kivonattöredékében található – soha nem kerül elküldésre semmilyen kiszolgálóra.", - "file-sec-3": "A nagyobb biztonság érdekében használja a SimpleX Chat alkalmazást.", + "file-sec-3": "A nagyobb biztonság érdekében, használja a SimpleX Chat alkalmazást.", "file-retry": "Újra", "file-downloading": "Letöltés…", "file-decrypting": "Visszafejtés…", diff --git a/website/langs/it.json b/website/langs/it.json index 0a6870d75d..326afb82d4 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -62,8 +62,8 @@ "simplex-network-overlay-card-1-li-2": "Il design di SimpleX, a differenza della maggior parte delle reti P2P, non ha identificatori utente globali di alcun tipo, nemmeno temporanei, e usa solo identificatori temporanei a coppie, garantendo una maggiore protezione dell'anonimato e dei metadati.", "simplex-network-overlay-card-1-li-4": "Le implementazioni P2P possono essere bloccate da alcuni fornitori di internet (come BitTorrent). SimpleX è indipendente dal trasporto — può funzionare su protocolli web standard, es. WebSocket.", "hero-overlay-card-2-p-1": "Quando gli utenti hanno identità permanenti, anche se si tratta solo di un numero casuale, come un Session ID, c'è il rischio che il fornitore o un malintenzionato possano osservare come gli utenti sono connessi e quanti messaggi inviano.", - "simplex-network-overlay-card-1-li-6": "Le reti P2P possono essere vulnerabili all'attacco DRDoS, quando i client possono ritrasmettere e amplificare il traffico, con conseguente \"denial of service\" a livello di rete. I client SimpleX si limitano a inoltrare il traffico da una connessione nota e non possono essere usati da un aggressore per amplificare il traffico nell'intera rete.", - "privacy-matters-overlay-card-1-p-4": "La rete di SimpleX protegge la privacy delle tue connessioni meglio di qualsiasi alternativa, impedendo completamente che il tuo grafico sociale sia disponibile a qualsiasi azienda o organizzazione. Anche quando le persone usano i server preconfigurati in SimpleX Chat, gli operatori dei server non conoscono il numero di utenti o le loro connessioni.", + "simplex-network-overlay-card-1-li-6": "Le reti P2P possono essere vulnerabili all'attacco DRDoS, quando i client possono ritrasmettere e amplificare il traffico, con conseguente \"denial of service\" a livello di rete. I client SimpleX si limitano a inoltrare il traffico da connessioni note e non possono essere usati da un aggressore per amplificare il traffico nell'intera rete.", + "privacy-matters-overlay-card-1-p-4": "La rete di SimpleX protegge la privacy delle tue connessioni meglio di qualsiasi alternativa, impedendo completamente che il tuo grafico sociale diventi disponibile a qualsiasi azienda o organizzazione. Anche quando le persone usano i server preconfigurati in SimpleX Chat, gli operatori dei server non conoscono il numero di utenti o le loro connessioni.", "privacy-matters-overlay-card-2-p-3": "SimpleX è la prima rete che non ha alcun identificatore utente per design, proteggendo così il tuo grafico delle connessioni meglio di qualsiasi alternativa conosciuta.", "privacy-matters-overlay-card-3-p-1": "Tutti dovrebbero preoccuparsi della privacy e della sicurezza delle proprie comunicazioni — conversazioni innocue possono metterti in pericolo, anche se non hai nulla da nascondere.", "privacy-matters-overlay-card-3-p-3": "Le persone comuni vengono arrestate per ciò che condividono online, anche tramite i loro account \"anonimi\", anche nei Paesi democratici.", @@ -139,7 +139,7 @@ "simplex-private-card-6-point-1": "Molte reti di comunicazione sono vulnerabili agli attacchi MITM da parte di server o fornitori di rete.", "simplex-private-card-7-point-1": "Per garantire l'integrità, i messaggi sono numerati in sequenza e includono l'hash del messaggio precedente.", "simplex-private-card-9-point-2": "Riduce i vettori di attacco, rispetto ai broker di messaggi tradizionali, e i metadati disponibili.", - "simplex-private-card-10-point-2": "Ciò consente di recapitare messaggi senza identificatori del profilo utente, garantendo una migliore privacy dei metadati rispetto alle alternative.", + "simplex-private-card-10-point-2": "Ciò consente ai messaggi di venire recapitati senza identificatori del profilo utente, garantendo una migliore privacy dei metadati rispetto alle alternative.", "privacy-matters-1-title": "Pubblicità e discriminazione dei prezzi", "privacy-matters-2-overlay-1-title": "La privacy ti dà potere", "simplex-private-card-9-point-1": "Ogni coda di messaggi passa i messaggi in una direzione, con i diversi indirizzi di invio e ricezione.", @@ -181,7 +181,7 @@ "privacy-matters-overlay-card-1-p-2": "I rivenditori online sanno che le persone con redditi più bassi sono più propense a fare acquisti urgenti, quindi possono applicare prezzi più alti o rimuovere sconti.", "privacy-matters-overlay-card-1-p-3": "Alcune società finanziarie e assicurative usano grafici sociali per determinare i tassi di interesse e i premi. Spesso ciò fa pagare di più le persone con redditi più bassi — è noto come \"premio di povertà\".", "privacy-matters-overlay-card-2-p-1": "Non molto tempo fa abbiamo assistito alla manipolazione delle principali elezioni da una rispettabile società di consulenza che ha usato i nostri grafici sociali per distorcere la nostra visione del mondo reale e manipolare i nostri voti.", - "privacy-matters-overlay-card-2-p-2": "Per essere obiettivi e prendere decisioni indipendenti devi avere il controllo del tuo spazio informativo. È possibile solo se utilizzi una rete di comunicazione privata che non ha accesso al tuo grafico sociale.", + "privacy-matters-overlay-card-2-p-2": "Per essere obiettivi e prendere decisioni indipendenti devi avere il controllo del tuo spazio informativo. È possibile solo se usi una rete di comunicazione privata che non ha accesso al tuo grafico sociale.", "privacy-matters-overlay-card-3-p-2": "Una delle storie più scioccanti è l'esperienza di Mohamedou Ould Salahi descritta nel suo libro di memorie e mostrata nel film The Mauritanian. È stato rinchiuso nel campo di Guantánamo, senza processo, e lì è stato torturato per 15 anni dopo una telefonata a un suo parente in Afghanistan, sospettato di essere coinvolto negli attacchi dell'11/9, nonostante avesse vissuto in Germania per i precedenti 10 anni.", "join-us-on-GitHub": "Unisciti a noi su GitHub", "simplex-chat-for-the-terminal": "SimpleX Chat per il terminale", diff --git a/website/langs/ja.json b/website/langs/ja.json index aff6d62c1a..fcec6944e3 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -46,7 +46,7 @@ "simplex-explained-tab-3-text": "3. サーバーが認識するもの", "smp-protocol": "SMPプロトコル", "simplex-explained-tab-2-p-1": "接続ごとに 2 つの個別のメッセージング キューを使用して、異なるサーバー経由でメッセージを送受信します。", - "simplex-explained-tab-2-p-2": "サーバーは、ユーザーの会話や接続の全体像を把握することなく、メッセージを一方向に渡すだけです。", + "simplex-explained-tab-2-p-2": "サーバーは、ユーザーの会話や接続の全体を把握することなく、メッセージを一方向に送信するだけです。", "simplex-explained-tab-3-p-1": "サーバーはキューごとに個別の匿名認証情報を持っており、どのユーザーに属しているかはわかりません。", "simplex-explained-tab-3-p-2": "ユーザーは、Tor を使用してサーバーにアクセスし、IP アドレスによる相関を防ぐことで、メタデータのプライバシーをさらに向上させることができます。", "chat-protocol": "チャットプロトコル", @@ -63,7 +63,7 @@ "feature-7-title": "ポータブルな暗号化データベース — プロファイルを別のデバイスに移動する", "no-federated": "いいえ - 連合型", "simplex-unique-overlay-card-3-p-3": "電子メール、XMPP、Matrixなどの連携ネットワークサーバーとは異なり、SimpleXサーバーはユーザーアカウントを保存せず、メッセージの中継のみを行い、双方のプライバシーを保護します。", - "privacy-matters-overlay-card-3-p-2": "最も衝撃的な話の 1 つは、Mohamedou Ould Salahiの経験であり、彼の回顧録に記述され、『モーリタニア映画』で紹介されました。 彼は裁判も受けずにグアンタナモ収容所に入れられ、それまで10年間ドイツに住んでいたにも関わらず、9/11攻撃への関与の疑いでアフガニスタンの親戚に電話をかけた後、そこで15年間拷問を受けました。", + "privacy-matters-overlay-card-3-p-2": "最も衝撃的な話の 1 つは、Mohamedou Ould Salahiの経験であり、彼の回顧録に記述され、『モーリタニア映画』で紹介されています。彼はアフガニスタンの親戚に電話をかけた後、アメリカ同時多発テロへの関与の疑いで拘束されました。 彼は裁判も受けずにグアンタナモ収容所に入れられ、そこで15年間拷問を受けました。", "signing-key-fingerprint": "署名キーのフィンガープリント (SHA-256)", "simplex-network-2-desc": "SimpleX リレー サーバーは、ユーザー プロファイル、連絡先、配信されたメッセージを保存せず、相互に接続せず、サーバー ディレクトリもありません。", "docs-dropdown-5": "ホストXFTPサーバー", @@ -99,7 +99,7 @@ "privacy-matters-section-subheader": "メタデータのプライバシーを保護する — 話す相手 — 以下のことからあなたを守ります:", "if-you-already-installed": "すでにインストールしている場合", "join": "参加", - "privacy-matters-section-header": "プライバシーが重要である理由", + "privacy-matters-section-header": "なぜプライバシーが重要なのか", "on-this-page": "このページでは", "privacy-matters-overlay-card-1-p-2": "オンライン小売業者は、収入が低い人ほど急ぎの買い物をする可能性が高いことを知っているため、より高い価格を請求したり、割引を廃止したりすることがあります。", "simplex-unique-3-overlay-1-title": "データの所有権、管理、セキュリティ", @@ -180,7 +180,7 @@ "simplex-network-3-header": "SimpleX ネットワーク", "comparison-section-list-point-4": "オペレーターのサーバーが侵害された場合。 Signal およびその他の一部のアプリでセキュリティ コードを検証して緩和する", "simplex-private-card-2-point-1": "TLSが侵害された場合、受信したサーバー・トラフィックと送信したサーバー・トラフィックの相関を防ぐため、受信者に配信するサーバー暗号化レイヤーを追加します。", - "f-droid-page-simplex-chat-repo-section-text": "F-Droid クライアントに追加するには、QR コードをスキャンするか、次の URL を使用します:", + "f-droid-page-simplex-chat-repo-section-text": "F-Droid クライアントに追加するには、QR コードをスキャンするか、次の URL を使用してください:", "join-the-REDDIT-community": "REDDITコミュニティに参加する", "simplex-private-card-10-point-2": "ユーザー プロファイル識別子なしでメッセージを配信できるため、他の方法よりも優れたメタデータ プライバシーが提供されます。", "privacy-matters-2-title": "選挙操作", @@ -189,15 +189,15 @@ "feature-6-title": "E2E暗号化された
音声通話とビデオ通話", "simplex-network-overlay-card-1-li-2": "SimpleX 設計は、ほとんどの P2P ネットワークとは異なり、一時的であってもいかなる種類のグローバル ユーザー識別子も持たず、一時的なペアごとの識別子のみを使用するため、より優れた匿名性とメタデータ保護が提供されます。", "simplex-unique-4-title": "SimpleX ネットワークを所有", - "privacy-matters-overlay-card-3-p-3": "一般の人が、たとえ「匿名」アカウント経由であっても、オンラインで共有した内容で逮捕されます。たとえ民主主義国家であったとしても。", + "privacy-matters-overlay-card-3-p-3": "一般の人が、たとえ「匿名」アカウント経由であっても、オンラインで共有した内容で逮捕されます。それは、たとえ民主主義の国であったとしても同じです。", "simplex-unique-overlay-card-3-p-2": "エンドツーエンドで暗号化されたメッセージは、SimpleXのリレーサーバーで受信するまで一時的に保持され、その後永久に削除されます。", "simplex-private-card-7-point-1": "整合性を保証するために、メッセージには連続した番号が付けられ、前のメッセージのハッシュが含まれます。", "contact-hero-p-2": "SimpleX Chat をまだダウンロードしていませんか?", - "why-simplex-is-unique": "なぜSimpleXなのか唯一", + "why-simplex-is-unique": "なぜSimpleXが唯一無二なのか", "simplex-network-section-header": "SimpleX ネットワーク", "simplex-private-10-title": "一時的な匿名のペア識別子", "privacy-matters-1-overlay-1-linkText": "プライバシーの保護はコストを削減します", - "tap-the-connect-button-in-the-app": "アプリの 「接続」 ボタンをタップします", + "tap-the-connect-button-in-the-app": "アプリの 「接続」 ボタンをタップしてください", "comparison-section-list-point-4a": "SimpleX リレーは e2e 暗号化を侵害できません。 セキュリティ コードを検証して帯域外チャネルへの攻撃を軽減します", "simplex-network-1-overlay-linktext": "P2Pネットワークの問題点", "no-private": "いいえ - プライベート", @@ -208,7 +208,7 @@ "hero-overlay-2-title": "ユーザー ID がプライバシーに悪影響を与えるのはなぜですか?", "docs-dropdown-4": "ホストSMPサーバー", "feature-4-title": "E2E暗号化された音声メッセージ", - "privacy-matters-overlay-card-2-p-1": "つい最近まで、私たちは主要な選挙が 評判の高いコンサルティング会社によって操作されているのを観察しました。 ソーシャルグラフは私たちの現実世界の見方を歪め、私たちの投票を操作します。", + "privacy-matters-overlay-card-2-p-1": "つい最近まで、主要な選挙が 有名なコンサルティング会社によって操作されていました。 ソーシャルグラフは私たちの現実世界の見方を歪め、投票を操作しています。", "privacy-matters-overlay-card-2-p-3": "SimpleX は、設計上ユーザー識別子を持たない最初のネットワークであり、この方法で既知の代替手段よりも接続グラフを保護します。", "learn-more": "さらに詳しく", "simplex-private-8-title": "メッセージのミキシング
相関性を減らす", @@ -220,7 +220,7 @@ "protocol-1-text": "Signal、大きなプラットフォーム", "simplex-network-overlay-card-1-li-6": "P2P ネットワークは、DRDoS 攻撃に対して脆弱になる可能性があります。 クライアントがトラフィックを再ブロードキャストして増幅する可能性があり、その結果、ネットワーク全体のサービス拒否が発生する可能性があります。 SimpleX クライアントは既知の接続からのトラフィックのみを中継するため、攻撃者がネットワーク全体のトラフィックを増幅するために使用することはできません。", "if-you-already-installed-simplex-chat-for-the-terminal": "すでにターミナルに SimpleX Chat をインストールしている場合", - "docs-dropdown-8": "SimpleX ディレクトリ サービス", + "docs-dropdown-8": "SimpleX ディレクトリ", "simplex-private-card-1-point-1": "ダブルラチェットプロトコル —
完全な前方秘匿性と侵入回復機能を備えたOTRメッセージング。", "simplex-private-card-8-point-1": "SimpleX サーバーは、低遅延の混合ノードとして機能します — 受信メッセージと送信メッセージの順序が異なります。", "simplex-unique-overlay-card-2-p-1": "SimpleXネットワークには識別子がないため、ワンタイムまたは一時的なユーザー アドレスを QR コードまたはリンクとして共有しない限り、誰もあなたに連絡することはできません。", @@ -233,9 +233,9 @@ "simplex-private-7-title": "メッセージの整合性
検証", "privacy-matters-overlay-card-1-p-4": "SimpleXネットワークは、他のどのプラットフォームよりも接続のプライバシーを保護し、ソーシャル グラフが企業や組織に利用されることを完全に防ぎます。 SimpleX Chatアプリに予め設定されたサーバを利用している場合でも、サーバオペレータはユーザーの数や接続数を知ることはできません。", "hero-overlay-card-1-p-6": "詳細については、SimpleX ホワイトペーパーをご覧ください。", - "simplex-network-overlay-card-1-p-1": "P2P メッセージング プロトコルとアプリには、SimpleX よりも信頼性が低く、分析がより複雑になるさまざまな問題があり、 いくつかの種類の攻撃に対して脆弱です。", + "simplex-network-overlay-card-1-p-1": "P2P メッセージング プロトコルとアプリには、SimpleX よりも信頼性が低く、分析がより複雑になるさまざまな問題があり、また、いくつかの種類の攻撃に対して脆弱です。", "simplex-network-overlay-card-1-li-1": "P2P ネットワークは、メッセージをルーティングするために DHT の一部の変種に依存します。 DHT の設計では、配信保証と遅延のバランスを取る必要があります。 SimpleX は、受信者が選択したサーバーを使用して、メッセージを複数のサーバーを介して並行して冗長的に渡すことができるため、P2P よりも優れた配信保証と低い遅延の両方を備えています。 P2P ネットワークでは、メッセージはアルゴリズムによって選択されたノードを使用して、O(log N) 個のノードを順番に通過します。", - "privacy-matters-section-label": "メッセンジャーがあなたのデータにアクセスできないようにしてください!", + "privacy-matters-section-label": "メッセージアプリがあなたのデータにアクセスできないようにしてください!", "simplex-unique-overlay-card-3-p-1": "SimpleX Chat は、サポートされているデバイスにエクスポートして転送できるポータブル暗号化データベース形式を使用して、すべてのユーザー データをクライアント デバイスにのみ保存します。", "simplex-network-3-desc": "サーバーはユーザーを接続するための一方向キューを提供しますが、ネットワーク接続グラフは表示されません— ユーザーだけがそうします。", "simplex-private-card-3-point-1": "クライアント/サーバー接続には、強力なアルゴリズムを備えた TLS 1.2/1.3 のみが使用されます。", @@ -272,7 +272,7 @@ "index-messaging-cta": "SimpleXのメッセージ機能について詳しく知る", "index-nextweb-h2": "次のWebは
あなたのもの", "index-nextweb-p1": "SimpleXは、 アイデンティティ・連絡先・コミュニティはあなたのものであるべきだという考えに基づいています。", - "index-nextweb-p2": "オープンで分散型のネットワークで、自由で安全に人とつながり、アイデアを共有できます。", + "index-nextweb-p2": "分散型のネットワークで、自由で安全に人とつながり、アイデアを共有できます。", "index-token-h2": "続いていくコミュニティ", "index-token-p1": "コミュニティバウチャーを通じて、お気に入りのグループをサポートできます。", "index-token-p2": "バウチャーはサーバー費用に充てられ、コミュニティが自由で独立した状態を保ち続けられるようにします。", @@ -298,5 +298,9 @@ "index-publications-kuketz-title": "Mike Kuketzによるレビュー", "index-publications-optout-title": "OptOut ポッドキャストインタビュー", "send-file": "ファイルを送信", - "navbar-old-site": "旧サイト" + "navbar-old-site": "旧サイト", + "navbar-token": "トークン", + "docs-dropdown-15": "認証と再ビルド", + "index-f-droid-title": "F-Droid経由のSimpleXアプリ", + "how-secure-forward-secrecy": "前方秘匿性" } From 1c0567cf400363dde29ee0dc21a3cacd2741a97d Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 25 Apr 2026 11:13:42 +0100 Subject: [PATCH 18/77] ui: translations (#6881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Italian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (2650 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Czech) Currently translated at 95.3% (2526 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Russian) Currently translated at 97.2% (2578 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Arabic) Currently translated at 100.0% (2650 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2650 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (German) Currently translated at 100.0% (2650 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2650 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2650 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Czech) Currently translated at 95.3% (2526 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Czech) Currently translated at 54.2% (1245 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/cs/ * Translated using Weblate (Czech) Currently translated at 95.3% (2526 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Spanish) Currently translated at 97.1% (2575 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (2650 of 2650 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 87.8% (2342 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Latvian) Currently translated at 91.5% (2440 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/lv/ * Translated using Weblate (Lithuanian) Currently translated at 64.0% (1707 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/lt/ * Translated using Weblate (Catalan) Currently translated at 94.1% (2509 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ca/ * Translated using Weblate (Romanian) Currently translated at 93.3% (2490 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Romanian) Currently translated at 93.3% (2490 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Thai) Currently translated at 47.3% (1262 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2666 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2666 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Italian) Currently translated at 100.0% (2666 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (German) Currently translated at 100.0% (2666 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2666 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2666 of 2666 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2679 of 2679 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2679 of 2679 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2679 of 2679 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2679 of 2679 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2687 of 2687 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Italian) Currently translated at 100.0% (2687 of 2687 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2687 of 2687 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (German) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2687 of 2687 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2689 of 2689 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2689 of 2689 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Russian) Currently translated at 96.0% (2584 of 2689 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (German) Currently translated at 100.0% (2689 of 2689 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2689 of 2689 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (German) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Russian) Currently translated at 96.1% (2586 of 2690 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Russian) Currently translated at 95.4% (2581 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (Hebrew) Currently translated at 77.3% (2092 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2704 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Japanese) Currently translated at 73.5% (1990 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (French) Currently translated at 88.6% (2398 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 86.4% (2337 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Czech) Currently translated at 93.2% (2521 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Spanish) Currently translated at 97.7% (2644 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Finnish) Currently translated at 53.1% (1436 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/ * Translated using Weblate (German) Currently translated at 99.2% (2684 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Dutch) Currently translated at 88.5% (2394 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Thai) Currently translated at 46.5% (1258 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/th/ * Translated using Weblate (Italian) Currently translated at 99.2% (2684 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Polish) Currently translated at 93.1% (2519 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Bulgarian) Currently translated at 92.0% (2488 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/bg/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2704 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2704 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2704 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2704 of 2704 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2294 of 2294 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Hungarian) Currently translated at 99.6% (2706 of 2715 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2715 of 2715 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (2715 of 2715 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Italian) Currently translated at 99.6% (2705 of 2715 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (2715 of 2715 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Russian) Currently translated at 97.1% (2637 of 2715 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ru/ * Translated using Weblate (German) Currently translated at 100.0% (2715 of 2715 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * process localizations * ui: translation fixes for PR #6881 (de, es, hu, it, ar, ru, zh-rCN) (#6883) Fixes 17 individual issues across 11 files surfaced by line-by-line review of the new channel-feature translations on the weblate/translations branch. Merge-blockers - es: re-insert "/" in %1$d/%2$d format specifier in relay_bar_active, relay_bar_active_with_failures, relay_bar_connected, relay_bar_connected_with_errors (iOS .strings, .xliff, Android XML). Without the /, UI rendered "12 servidores" instead of "1/2 servidores". - zh-rCN: owner_verification_passed translated as "无效" (INVALID); both PASSED and FAILED branches read as failure. Now "链接签名已验证。". - es: connect_plan_this_is_your_link_for_channel_vName was left in English; translated to Spanish. Bugs and typos - de: tap_to_start_new_chat "einen neuen Chats" → "einen neuen Chat" (accusative cannot govern genitive/plural form). - de: lowercase indefinite pronouns "jemand"/"jemandem" in connect_with_someone, let_someone_connect_to_you, talk_to_someone. - de: v6_5_public_channels "freies kommunizieren" → "freies Kommunizieren" (nominalised infinitive must be capitalised). - de: %3$d Fehlgeschlagen → %3$d fehlgeschlagen in relay_bar_active_with_failures (parallels %3$d Fehler correctly; participle, not noun). - de iOS: restore dropped **%@** markdown bold around the profile name in "Your profile **%@** will be shared with channel relays and subscribers." - es: suscroptores → suscriptores (3 files). - es: requere → requiere (3 files). - it: relayvdi chat → relay di chat (2 files). - ru: ретранстляторы → ретрансляторы (configure_relays). - ru: подписчкика → подписчика (block_subscriber_for_all_question). - ar: الفناة → القناة (your_profile_shared_with_channel_relays). - zh-rCN: alert_text_msg_reception_error fixed truncation "这条消" and 接受 (accept) → 接收 (receive). - zh-rCN: channel_no_active_relays_try_later inverted meaning "频道有不活跃的中继" (HAS inactive relays) → "频道无活跃中继" (has no active relays). - zh-rCN: chat_link_signed removed stray "s": "(s已签名)" → "(已签名)". - zh-rCN: onboarding_or_show_qr_code fixed truncation "视频通" → "视频通话". Co-authored-by: Claude Opus 4.7 (1M context) --------- Co-authored-by: Random Co-authored-by: zenobit Co-authored-by: summoner001 Co-authored-by: Skyward Copied Co-authored-by: jonnysemon Co-authored-by: 大王叫我来巡山 Co-authored-by: mlanp Co-authored-by: slrslr Co-authored-by: No name Co-authored-by: Hosted Weblate Co-authored-by: Augusto Coronel <156332097+aocoronel@users.noreply.github.com> Co-authored-by: Riko Miko Co-authored-by: Anonimas Co-authored-by: fran secs Co-authored-by: Anonymous Co-authored-by: Alex Vornicu Co-authored-by: Ghost of Sparta Co-authored-by: Roee Hershberg Co-authored-by: a4318 Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> Co-authored-by: Feroli Co-authored-by: petri Co-authored-by: John m Co-authored-by: Titapa (PunPun) Chaiyakiturajai Co-authored-by: Deleted User Co-authored-by: B.O.S.S Co-authored-by: elgratea Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) --- .../cs.xcloc/Localized Contents/cs.xliff | 2 +- .../de.xcloc/Localized Contents/de.xliff | 124 +++++- .../es.xcloc/Localized Contents/es.xliff | 119 +++++- .../hu.xcloc/Localized Contents/hu.xliff | 126 +++++- .../it.xcloc/Localized Contents/it.xliff | 114 +++++- .../SimpleX SE/de.lproj/Localizable.strings | 2 +- apps/ios/bg.lproj/Localizable.strings | 29 +- apps/ios/cs.lproj/Localizable.strings | 29 +- apps/ios/de.lproj/Localizable.strings | 368 +++++++++++++++-- apps/ios/es.lproj/Localizable.strings | 371 +++++++++++++++-- apps/ios/fi.lproj/Localizable.strings | 25 +- apps/ios/fr.lproj/Localizable.strings | 32 +- apps/ios/hu.lproj/Localizable.strings | 372 ++++++++++++++++-- apps/ios/it.lproj/Localizable.strings | 360 +++++++++++++++-- apps/ios/ja.lproj/Localizable.strings | 25 +- apps/ios/nl.lproj/Localizable.strings | 34 +- apps/ios/pl.lproj/Localizable.strings | 34 +- apps/ios/ru.lproj/Localizable.strings | 34 +- apps/ios/th.lproj/Localizable.strings | 25 +- apps/ios/tr.lproj/Localizable.strings | 34 +- apps/ios/uk.lproj/Localizable.strings | 34 +- apps/ios/zh-Hans.lproj/Localizable.strings | 34 +- .../commonMain/resources/MR/ar/strings.xml | 287 ++++++++++---- .../commonMain/resources/MR/bg/strings.xml | 2 +- .../commonMain/resources/MR/ca/strings.xml | 2 +- .../commonMain/resources/MR/cs/strings.xml | 10 +- .../commonMain/resources/MR/de/strings.xml | 121 +++++- .../commonMain/resources/MR/es/strings.xml | 142 ++++++- .../commonMain/resources/MR/fi/strings.xml | 2 +- .../commonMain/resources/MR/fr/strings.xml | 2 +- .../commonMain/resources/MR/hu/strings.xml | 127 ++++-- .../commonMain/resources/MR/it/strings.xml | 111 +++++- .../commonMain/resources/MR/iw/strings.xml | 2 +- .../commonMain/resources/MR/ja/strings.xml | 2 +- .../commonMain/resources/MR/lt/strings.xml | 3 +- .../commonMain/resources/MR/lv/strings.xml | 120 +++--- .../commonMain/resources/MR/nl/strings.xml | 2 +- .../commonMain/resources/MR/pl/strings.xml | 2 +- .../resources/MR/pt-rBR/strings.xml | 5 +- .../commonMain/resources/MR/ro/strings.xml | 5 +- .../commonMain/resources/MR/ru/strings.xml | 244 +++++++++--- .../commonMain/resources/MR/th/strings.xml | 4 +- .../resources/MR/zh-rCN/strings.xml | 91 ++++- 43 files changed, 2886 insertions(+), 727 deletions(-) diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index b786562a4f..181ccb332c 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -9131,7 +9131,7 @@ Repeat join request?
You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. - Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte. + Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud odkaz později smažete. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 09a5d750e3..32f9235dd1 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -217,10 +217,12 @@ channel subscriber relay bar %d subscriber + %d Abonnent channel subscriber count %d subscribers + %d Abonnenten channel subscriber count @@ -230,6 +232,7 @@ channel subscriber relay bar %1$d/%2$d relays active + %1$d/%2$d Relais aktiv channel creation progress channel relay bar progress @@ -239,6 +242,7 @@ channel relay bar progress
%1$d/%2$d relays active, %3$d failed + %1$d/%2$d Relais aktiv, %3$d fehlgeschlagen channel creation progress with errors channel relay bar @@ -248,10 +252,12 @@ channel relay bar
%1$d/%2$d relays connected + %1$d/%2$d Relais verbunden channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors + %1$d/%2$d Relais verbunden, %3$d Fehler channel subscriber relay bar @@ -274,6 +280,7 @@ channel relay bar %lld channel events + %lld Kanalereignisse No comment provided by engineer. @@ -436,6 +443,7 @@ channel relay bar **Test relay** to retrieve its name. + **Relais testen** um seinen Namen abzurufen. No comment provided by engineer. @@ -1413,6 +1421,7 @@ swipe action Block subscriber for all? + Abonnent für alle blockieren? No comment provided by engineer. @@ -1467,6 +1476,7 @@ swipe action Broadcast + Broadcast compose placeholder for channel owner @@ -1658,14 +1668,17 @@ set passcode view Channel + Kanal No comment provided by engineer. Channel display name + Anzeigename des Kanals No comment provided by engineer. Channel full name (optional) + Vollständiger Kanalname (optional) No comment provided by engineer. @@ -1675,10 +1688,12 @@ alert subtitle Channel image + Kanalbild No comment provided by engineer. Channel link + Kanallink chat link info line @@ -1687,14 +1702,17 @@ alert subtitle Channel profile + Kanalprofil No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + Das Kanalprofil wird auf den Geräten der Abonnenten und auf den Chat‑Relais gespeichert. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + Das Kanalprofil wurde geändert. Beim Speichern wird das aktualisierte Profil an die Abonnenten des Kanals gesendet. alert message @@ -1703,14 +1721,17 @@ alert subtitle Channel will be deleted for all subscribers - this cannot be undone! + Der Kanal wird für alle Abonnenten gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + Der Kanal wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + Der Kanal wird mit %1$d von %2$d Relais gestartet. Fortfahren? alert message @@ -1804,18 +1825,22 @@ alert subtitle Chat relay + Chat-Relais No comment provided by engineer. Chat relays + Chat-Relais No comment provided by engineer. Chat relays forward messages in channels you create. + Chat‑Relais leiten Nachrichten in den von Ihnen erstellten Kanälen weiter. No comment provided by engineer. Chat relays forward messages to channel subscribers. + Chat‑Relais leiten Nachrichten an Kanal-Abonnenten weiter. No comment provided by engineer. @@ -1870,10 +1895,12 @@ alert subtitle Check relay address and try again. + Relais-Adresse überprüfen und erneut versuchen. alert message Check relay name and try again. + Relais-Name überprüfen und erneut versuchen. alert message @@ -2023,6 +2050,7 @@ alert subtitle Configure relays + Relais konfigurieren No comment provided by engineer. @@ -2436,10 +2464,12 @@ Das ist Ihr eigener Einmal-Link! Create public channel + Öffentlichen Kanal erstellen No comment provided by engineer. Create public channel (BETA) + Öffentlichen Kanal erstellen (BETA) No comment provided by engineer. @@ -2483,6 +2513,7 @@ Das ist Ihr eigener Einmal-Link! Creating channel + Kanal wird erstellt No comment provided by engineer. @@ -2650,6 +2681,7 @@ Das ist Ihr eigener Einmal-Link! Decode link + Link dekodieren relay test step @@ -2700,10 +2732,12 @@ swipe action Delete channel + Kanal löschen No comment provided by engineer. Delete channel? + Kanal löschen? No comment provided by engineer. @@ -2879,6 +2913,7 @@ alert button Delete relay + Relais löschen No comment provided by engineer. @@ -3267,6 +3302,7 @@ chat item action Edit channel profile + Kanalprofil bearbeiten No comment provided by engineer. @@ -3306,6 +3342,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Aktivieren Sie mindestens ein Chat‑Relais unter 'Netzwerk & Server'. channel creation warning @@ -3459,6 +3496,7 @@ chat item action Enter channel name… + Kanalname eingeben… No comment provided by engineer. @@ -3488,6 +3526,7 @@ chat item action Enter relay name… + Relais-Name eingeben… No comment provided by engineer. @@ -3547,6 +3586,7 @@ chat item action Error adding relay + Fehler beim Hinzufügen des Relais alert title @@ -3611,6 +3651,7 @@ chat item action Error creating channel + Fehler beim Erstellen des Kanals alert title @@ -3795,6 +3836,7 @@ chat item action Error saving channel profile + Fehler beim Speichern des Kanalprofils No comment provided by engineer. @@ -4385,6 +4427,7 @@ Fehler: %2$@ Get link + Link erhalten relay test step @@ -4893,10 +4936,12 @@ Weitere Verbesserungen sind bald verfügbar! Invalid relay address! + Ungültige Relais-Adresse! alert title Invalid relay name! + Ungültiger Relais-Name! alert title @@ -5016,6 +5061,7 @@ Weitere Verbesserungen sind bald verfügbar! Join channel + Kanal beitreten No comment provided by engineer. @@ -5107,10 +5153,12 @@ Das ist Ihr Link für die Gruppe %@! Leave channel + Kanal verlassen No comment provided by engineer. Leave channel? + Kanal verlassen? No comment provided by engineer. @@ -5418,6 +5466,7 @@ Das ist Ihr Link für die Gruppe %@! Message error + Übertragungsfehler No comment provided by engineer. @@ -5750,6 +5799,7 @@ Das ist Ihr Link für die Gruppe %@! New chat relay + Neues Chat-Relais No comment provided by engineer. @@ -5833,10 +5883,12 @@ Das ist Ihr Link für die Gruppe %@! No chat relays + Keine Chat-Relais No comment provided by engineer. No chat relays enabled. + Es sind keine Chat-Relais aktiviert. servers warning @@ -5995,6 +6047,7 @@ Das ist Ihr Link für die Gruppe %@! Not all relays connected + Es sind nicht alle Relais verbunden alert title @@ -6107,7 +6160,7 @@ Dies erfordert die Aktivierung eines VPNs. Only chat owners can change preferences. - Nur Chat-Eigentümer können die Präferenzen ändern. + Präferenzen können nur von Chat-Eigentümern geändert werden. No comment provided by engineer. @@ -6222,6 +6275,7 @@ Dies erfordert die Aktivierung eines VPNs. Open channel + Kanal öffnen new chat action @@ -6266,6 +6320,7 @@ Dies erfordert die Aktivierung eines VPNs. Open new channel + Neuen Kanal öffnen new chat action @@ -6370,10 +6425,12 @@ Dies erfordert die Aktivierung eines VPNs. Owner + Eigentümer No comment provided by engineer. Owners + Eigentümer No comment provided by engineer. @@ -6595,10 +6652,12 @@ Fehler: %@ Preset relay address + Voreingestellte Relais-Adresse No comment provided by engineer. Preset relay name + Voreingestellter Relais-Name No comment provided by engineer. @@ -6692,6 +6751,7 @@ Fehler: %@ Proceed + Fortfahren alert action @@ -7048,18 +7108,22 @@ swipe action Relay + Relais No comment provided by engineer. Relay address + Relais-Adresse alert title Relay connection failed + Relais-Verbindung fehlgeschlagen alert title Relay link + Relais-Link No comment provided by engineer. @@ -7078,6 +7142,7 @@ swipe action Relay test failed! + Relais-Test fehlgeschlagen! No comment provided by engineer. @@ -7126,10 +7191,12 @@ swipe action Remove subscriber + Abonnent entfernen No comment provided by engineer. Remove subscriber? + Abonnent entfernen? alert title @@ -7399,6 +7466,7 @@ chat item action Save (and notify subscribers) + Speichern (Abonnenten benachrichtigen) alert button @@ -7432,10 +7500,12 @@ chat item action Save channel profile + Kanalprofil speichern No comment provided by engineer. Save channel profile? + Kanalprofil speichern? alert title @@ -7639,7 +7709,7 @@ chat item action Selected chat preferences prohibit this message. - Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt. + Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt. No comment provided by engineer. @@ -7699,7 +7769,7 @@ chat item action Send link previews - Link-Vorschau senden + Linkvorschau senden No comment provided by engineer. @@ -7912,6 +7982,7 @@ chat item action Server requires authorization to connect to relay, check password. + Der Server erfordert eine Autorisierung, um eine Verbindung zum Relais herzustellen. Bitte Passwort überprüfen. relay test error @@ -8115,6 +8186,7 @@ chat item action Share relay address + Relais-Adresse teilen No comment provided by engineer. @@ -8262,7 +8334,7 @@ chat item action SimpleX channel link - SimpleX-Kanal-Link + SimpleX-Kanallink simplex link type @@ -8307,6 +8379,7 @@ chat item action SimpleX relay address + SimpleX Relais-Adresse simplex link type @@ -8484,19 +8557,24 @@ report reason Subscriber + Abonnent No comment provided by engineer. Subscriber will be removed from channel - this cannot be undone! + Abonnent wird aus dem Kanal entfernt. Dies kann nicht rückgängig gemacht werden! alert message Subscribers + Abonnenten No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + Abonnenten verbinden sich über den Relais‑Link mit dem Kanal. +Die Relais-Adresse wurde zur Einrichtung dieses Relais für diesen Kanal verwendet. No comment provided by engineer. @@ -8600,6 +8678,7 @@ Relay address was used to set up this relay for the channel. Tap Join channel + Tippen, um dem Kanal beizutreten No comment provided by engineer. @@ -8619,7 +8698,7 @@ Relay address was used to set up this relay for the channel. Tap to activate profile. - Zum Aktivieren des Profils tippen. + Tippen, um das Profil zu aktivieren. No comment provided by engineer. @@ -8638,7 +8717,7 @@ Relay address was used to set up this relay for the channel. Tap to paste link - Zum Link einfügen tippen + Tippen, um den Link einzufügen No comment provided by engineer. @@ -8664,6 +8743,7 @@ server test failure Test relay + Relais testen No comment provided by engineer. @@ -8720,6 +8800,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro The app removed this message after %lld attempts to receive it. + Die App hat diese Nachricht nach %lld Empfangsversuchen entfernt. No comment provided by engineer. @@ -8918,10 +8999,12 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This is a chat relay address, it cannot be used to connect. + Dies ist eine Chat‑Relais-Adresse, welche nicht zum Verbinden verwendet werden kann. alert message This is your link for channel %@! + Dies ist Ihr Link für den Kanal %@! new chat action @@ -9152,6 +9235,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Unblock subscriber for all? + Abonnent für alle freigeben? No comment provided by engineer. @@ -9408,6 +9492,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use for new channels + Für neue Kanäle verwenden No comment provided by engineer. @@ -9452,6 +9537,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use relay + Relais verwenden No comment provided by engineer. @@ -9500,6 +9586,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verify + Überprüfen relay test step @@ -9624,10 +9711,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wait + Abwarten alert action Wait response + Antwort abwarten relay test step @@ -9942,6 +10031,7 @@ Verbindungsanfrage wiederholen? You can share a link or a QR code - anybody will be able to join the channel. + Sie können einen Link oder QR-Code teilen - damit kann jeder dem Kanal beitreten. No comment provided by engineer. @@ -9991,6 +10081,7 @@ Verbindungsanfrage wiederholen? You connected to the channel via this relay link. + Sie haben sich über diesen Relais‑Link mit dem Kanal verbunden. No comment provided by engineer. @@ -10107,6 +10198,7 @@ Verbindungsanfrage wiederholen? You will stop receiving messages from this channel. Chat history will be preserved. + Sie werden keine Nachrichten mehr aus diesem Kanal erhalten. Der Chatverlauf bleibt erhalten. No comment provided by engineer. @@ -10156,6 +10248,7 @@ Verbindungsanfrage wiederholen? Your channel + Ihr Kanal No comment provided by engineer. @@ -10246,6 +10339,8 @@ Verbindungsanfrage wiederholen? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + Ihr Profil **%@** wird mit Kanal‑Relais und Abonnenten geteilt. +Relais können auf Kanalnachrichten zugreifen. No comment provided by engineer. @@ -10279,10 +10374,12 @@ Relays can access channel messages. Your relay address + Ihre Relais-Adresse No comment provided by engineer. Your relay name + Ihr Relais-Name No comment provided by engineer. @@ -10332,6 +10429,7 @@ Relays can access channel messages. accepted + Angenommen No comment provided by engineer. @@ -10356,6 +10454,7 @@ Relays can access channel messages. active + Aktiv No comment provided by engineer. @@ -10510,10 +10609,12 @@ marked deleted chat item preview text channel + Kanal shown as sender role for channel messages channel profile updated + Kanalprofil wurde aktualisiert snd group event chat item @@ -10664,6 +10765,7 @@ pref value deleted channel + Kanal gelöscht rcv group event chat item @@ -10778,6 +10880,7 @@ pref value error: %@ + Fehler: %@ receive error chat item @@ -10912,6 +11015,7 @@ pref value link + Link No comment provided by engineer. @@ -10986,6 +11090,7 @@ pref value new + Neu No comment provided by engineer. @@ -11113,6 +11218,7 @@ time to disappear relay + Relais member role @@ -11127,6 +11233,7 @@ time to disappear removed (%d attempts) + Entfernt (%d Versuche) receive error chat item @@ -11289,6 +11396,7 @@ Zuletzt empfangene Nachricht: %2$@ updated channel profile + Kanalprofil aktualisiert rcv group event chat item @@ -11313,6 +11421,7 @@ Zuletzt empfangene Nachricht: %2$@ via %@ + via %@ relay hostname @@ -11392,6 +11501,7 @@ Zuletzt empfangene Nachricht: %2$@ you are subscriber + Sie sind Abonnent No comment provided by engineer. @@ -11715,7 +11825,7 @@ Zuletzt empfangene Nachricht: %2$@ Selected chat preferences prohibit this message. - Die gewählten Chat-Einstellungen erlauben diese Nachricht nicht. + Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt. No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index c22ff04450..7be640c9b7 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -217,10 +217,12 @@ channel subscriber relay bar %d subscriber + %d suscriptor channel subscriber count %d subscribers + %d suscriptores channel subscriber count @@ -230,6 +232,7 @@ channel subscriber relay bar %1$d/%2$d relays active + %1$d/%2$d servidores activos channel creation progress channel relay bar progress @@ -239,6 +242,7 @@ channel relay bar progress %1$d/%2$d relays active, %3$d failed + %1$d/%2$d servidores activos, %3$d han fallado channel creation progress with errors channel relay bar @@ -248,10 +252,12 @@ channel relay bar
%1$d/%2$d relays connected + %1$d/%2$d servidores conectados channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors + %1$d/%2$d servidores conectados, %3$d errores channel subscriber relay bar @@ -274,6 +280,7 @@ channel relay bar %lld channel events + %lld eventos del canal No comment provided by engineer. @@ -288,7 +295,7 @@ channel relay bar %lld group events - %lld evento(s) de grupo + %lld evento(s) del grupo No comment provided by engineer. @@ -436,6 +443,7 @@ channel relay bar **Test relay** to retrieve its name. + **Test servidor** para recibir su nombre. No comment provided by engineer. @@ -1413,6 +1421,7 @@ swipe action Block subscriber for all? + ¿Bloquear al suscriptor para todos? No comment provided by engineer. @@ -1467,6 +1476,7 @@ swipe action Broadcast + Emisión compose placeholder for channel owner @@ -1658,14 +1668,17 @@ set passcode view Channel + Canal No comment provided by engineer. Channel display name + Título mostrado del canal No comment provided by engineer. Channel full name (optional) + Título completo del canal (opcional) No comment provided by engineer. @@ -1675,10 +1688,12 @@ alert subtitle Channel image + Imagen del canal No comment provided by engineer. Channel link + Enlace del canal chat link info line @@ -1687,14 +1702,17 @@ alert subtitle Channel profile + Perfil del canal No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + El perfil del canal se almacena en los dispositivos de los suscriptores y en los servidores de chat. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + El perfil del canal ha sido modificado. Si lo guardas, el perfil actualizado será enviado a los suscriptores. alert message @@ -1703,14 +1721,17 @@ alert subtitle Channel will be deleted for all subscribers - this cannot be undone! + El canal será eliminado para todos los suscriptores. ¡No puede deshacerse! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + El canal será eliminado para tí. ¡No puede deshacerse! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + El canal comenzará a funcionar con %1$d de %2$d servidores. ¿Continuar? alert message @@ -1804,18 +1825,22 @@ alert subtitle Chat relay + Servidor de chat No comment provided by engineer. Chat relays + Servidores de chat No comment provided by engineer. Chat relays forward messages in channels you create. + Los servidores de chat reenvían los mensajes en los canales que has creado. No comment provided by engineer. Chat relays forward messages to channel subscribers. + Los servidores de chat reenvían los mensajes a los suscriptores del canal. No comment provided by engineer. @@ -1870,10 +1895,12 @@ alert subtitle Check relay address and try again. + Comprueba la dirección del servidor y prueba de nuevo. alert message Check relay name and try again. + Comprueba el nombre del servidor y prueba de nuevo. alert message @@ -2023,6 +2050,7 @@ alert subtitle Configure relays + Configurar servidores No comment provided by engineer. @@ -2225,6 +2253,7 @@ This is your own one-time link! Connection failed + Conexión fallida No comment provided by engineer. @@ -2435,10 +2464,12 @@ This is your own one-time link! Create public channel + Crear canal público No comment provided by engineer. Create public channel (BETA) + Crear canal público (BETA) No comment provided by engineer. @@ -2482,6 +2513,7 @@ This is your own one-time link! Creating channel + Creando canal No comment provided by engineer. @@ -2649,6 +2681,7 @@ This is your own one-time link! Decode link + Decodificar enlace relay test step @@ -2699,10 +2732,12 @@ swipe action Delete channel + Eliminar canal No comment provided by engineer. Delete channel? + ¿Eliminar canal? No comment provided by engineer. @@ -2878,6 +2913,7 @@ alert button Delete relay + Eliminar servidor No comment provided by engineer. @@ -3266,6 +3302,7 @@ chat item action Edit channel profile + Editar perfil del canal No comment provided by engineer. @@ -3305,6 +3342,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Activar al menos un servidor de chat en Servidores y Redes. channel creation warning @@ -3458,6 +3496,7 @@ chat item action Enter channel name… + Introduce el título del canal… No comment provided by engineer. @@ -3487,6 +3526,7 @@ chat item action Enter relay name… + Introduce el nombre del servidor… No comment provided by engineer. @@ -3546,6 +3586,7 @@ chat item action Error adding relay + Error al añadir el servidor alert title @@ -3610,6 +3651,7 @@ chat item action Error creating channel + Error al crear el canal alert title @@ -3794,6 +3836,7 @@ chat item action Error saving channel profile + Error al guardar el perfil del canal No comment provided by engineer. @@ -4384,6 +4427,7 @@ Error: %2$@ Get link + Recibir el enlace relay test step @@ -4628,6 +4672,7 @@ Error: %2$@ If you joined or created channels, they will stop working permanently. + Si te has unido o has creado canales, dejarán de funcionar permanentemente. down migration warning @@ -4891,10 +4936,12 @@ More improvements are coming soon! Invalid relay address! + ¡Dirección de servidor no válido! alert title Invalid relay name! + ¡Nombre de servidor no válido! alert title @@ -5014,6 +5061,7 @@ More improvements are coming soon! Join channel + Unirme al canal No comment provided by engineer. @@ -5105,10 +5153,12 @@ This is your link for group %@! Leave channel + Salir del canal No comment provided by engineer. Leave channel? + ¿Salir del canal? No comment provided by engineer. @@ -5416,6 +5466,7 @@ This is your link for group %@! Message error + Mensaje de error No comment provided by engineer. @@ -5748,6 +5799,7 @@ This is your link for group %@! New chat relay + Nuevo servidor de chat No comment provided by engineer. @@ -5831,10 +5883,12 @@ This is your link for group %@! No chat relays + Sin servidores de chat No comment provided by engineer. No chat relays enabled. + Ningún servidor de chat activado. servers warning @@ -5993,6 +6047,7 @@ This is your link for group %@! Not all relays connected + Hay servidores no conectados alert title @@ -6220,6 +6275,7 @@ Requiere activación de la VPN. Open channel + Abrir canal new chat action @@ -6264,6 +6320,7 @@ Requiere activación de la VPN. Open new channel + Abrir canal nuevo new chat action @@ -6368,10 +6425,12 @@ Requiere activación de la VPN. Owner + Propietario No comment provided by engineer. Owners + Propietarios No comment provided by engineer. @@ -6593,10 +6652,12 @@ Error: %@ Preset relay address + Direcciones predefinidas No comment provided by engineer. Preset relay name + Nombres predefinidos No comment provided by engineer. @@ -6690,6 +6751,7 @@ Error: %@ Proceed + Continuar alert action @@ -7046,18 +7108,22 @@ swipe action Relay + Servidor No comment provided by engineer. Relay address + Dirección del servidor alert title Relay connection failed + La conexión con el servidor ha fallado alert title Relay link + Enlace servidor No comment provided by engineer. @@ -7076,6 +7142,7 @@ swipe action Relay test failed! + ¡El test del servidor ha fallado! No comment provided by engineer. @@ -7124,10 +7191,12 @@ swipe action Remove subscriber + Eliminar suscriptor No comment provided by engineer. Remove subscriber? + ¿Eliminar suscriptor? alert title @@ -7397,6 +7466,7 @@ chat item action Save (and notify subscribers) + Guardar (y notificar suscriptores) alert button @@ -7430,10 +7500,12 @@ chat item action Save channel profile + Guardar perfil del canal No comment provided by engineer. Save channel profile? + ¿Guardar perfil del canal? alert title @@ -7910,6 +7982,7 @@ chat item action Server requires authorization to connect to relay, check password. + El servidor requiere autorización para conectar con el servidor, comprueba la contraseña. relay test error @@ -8113,6 +8186,7 @@ chat item action Share relay address + Compartir dirección del servidor No comment provided by engineer. @@ -8305,6 +8379,7 @@ chat item action SimpleX relay address + Dirección de servidor SimpleX simplex link type @@ -8482,19 +8557,24 @@ report reason Subscriber + Suscriptor No comment provided by engineer. Subscriber will be removed from channel - this cannot be undone! + El suscriptor será eliminado del canal. ¡No puede deshacerse! alert message Subscribers + Suscriptores No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + Los suscriptores usan el enlace del servidor para conectarse a los canales. +La dirección del servidor se usó para establecer el servidor para el canal. No comment provided by engineer. @@ -8598,6 +8678,7 @@ Relay address was used to set up this relay for the channel. Tap Join channel + Pulsa Unirme al canal No comment provided by engineer. @@ -8662,6 +8743,7 @@ server test failure Test relay + Test servidor No comment provided by engineer. @@ -8718,6 +8800,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. The app removed this message after %lld attempts to receive it. + La app ha eliminado el mensaje tras %lld intentos de recibirlo. No comment provided by engineer. @@ -8916,10 +8999,12 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This is a chat relay address, it cannot be used to connect. + Esto es una dirección de servidor, no puede usarse para conectar. alert message This is your link for channel %@! + Este es tu enlace para el canal %@! new chat action @@ -9150,6 +9235,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unblock subscriber for all? + ¿Desbloquear al suscriptor para todos? No comment provided by engineer. @@ -9406,6 +9492,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use for new channels + Usar para canales nuevos No comment provided by engineer. @@ -9450,6 +9537,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Use relay + Usar servidor No comment provided by engineer. @@ -9498,6 +9586,7 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Verify + Verificar relay test step @@ -9622,10 +9711,12 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Wait + Espera alert action Wait response + Espera respuesta relay test step @@ -9940,11 +10031,12 @@ Repeat join request? You can share a link or a QR code - anybody will be able to join the channel. + Puedes compartir un enlace o código QR. Cualquiera podrá unirse al canal. No comment provided by engineer. You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. - Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. + Puedes compartir el enlace o código QR. Cualquiera podrá unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. No comment provided by engineer. @@ -9989,6 +10081,7 @@ Repeat join request? You connected to the channel via this relay link. + Te conectaste al canal mediante este enlace de servidor. No comment provided by engineer. @@ -10105,11 +10198,12 @@ Repeat connection request? You will stop receiving messages from this channel. Chat history will be preserved. + Dejarás de recibir mensajes de este canal. El historial del chat se conservará. No comment provided by engineer. You will stop receiving messages from this chat. Chat history will be preserved. - Dejarás de recibir mensajes del chat. El historial del chat se conserva. + Dejarás de recibir mensajes del chat. El historial del chat se conservará. No comment provided by engineer. @@ -10154,6 +10248,7 @@ Repeat connection request? Your channel + Tu canal No comment provided by engineer. @@ -10244,6 +10339,8 @@ Repeat connection request? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + El perfil **%@** será compartido con los servidores de canal y los suscriptores. +Los servidores tienen acceso a los mensajes del canal. No comment provided by engineer. @@ -10277,10 +10374,12 @@ Relays can access channel messages. Your relay address + Tu dirección de servidor No comment provided by engineer. Your relay name + Tu nombre del servidor No comment provided by engineer. @@ -10330,6 +10429,7 @@ Relays can access channel messages. accepted + aceptado No comment provided by engineer. @@ -10354,6 +10454,7 @@ Relays can access channel messages. active + activo No comment provided by engineer. @@ -10508,10 +10609,12 @@ marked deleted chat item preview text channel + canal shown as sender role for channel messages channel profile updated + perfil del canal actualizado snd group event chat item @@ -10662,6 +10765,7 @@ pref value deleted channel + canal eliminado rcv group event chat item @@ -10776,6 +10880,7 @@ pref value error: %@ + error: %@ receive error chat item @@ -10785,6 +10890,7 @@ pref value failed + fallo No comment provided by engineer. @@ -10909,6 +11015,7 @@ pref value link + enlace No comment provided by engineer. @@ -10983,6 +11090,7 @@ pref value new + nuevo No comment provided by engineer. @@ -11110,6 +11218,7 @@ time to disappear relay + servidor member role @@ -11124,6 +11233,7 @@ time to disappear removed (%d attempts) + eliminado (%d intentos) receive error chat item @@ -11286,6 +11396,7 @@ last received msg: %2$@ updated channel profile + perfil del canal actualizado rcv group event chat item @@ -11310,6 +11421,7 @@ last received msg: %2$@ via %@ + mediante %@ relay hostname @@ -11389,6 +11501,7 @@ last received msg: %2$@ you are subscriber + eres suscriptor No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 1b568f6157..db78c556e3 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -217,10 +217,12 @@ channel subscriber relay bar %d subscriber + %d feliratkozó channel subscriber count %d subscribers + %d feliratkozó channel subscriber count @@ -230,6 +232,7 @@ channel subscriber relay bar %1$d/%2$d relays active + %1$d/%2$d átjátszó aktív channel creation progress channel relay bar progress @@ -239,6 +242,7 @@ channel relay bar progress %1$d/%2$d relays active, %3$d failed + %1$d/%2$d átjátszó aktív, %3$d sikertelen channel creation progress with errors channel relay bar @@ -248,10 +252,12 @@ channel relay bar %1$d/%2$d relays connected + %1$d/%2$d átjátszó kapcsolódva channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors + %1$d/%2$d átjátszó kapcsolódva, %3$d hiba channel subscriber relay bar @@ -274,6 +280,7 @@ channel relay bar %lld channel events + %lld csatornaesemény No comment provided by engineer. @@ -411,7 +418,7 @@ channel relay bar **Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app. - **Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást). + **A legprivátabb**: Az alkalmazás nem használja a SimpleX Chat push-kiszolgálóját. Az alkalmazás a háttérben ellenőrzi az üzeneteket, amikor a rendszer ezt lehetővé teszi, attól függően, hogy Ön milyen gyakran használja az alkalmazást. No comment provided by engineer. @@ -421,7 +428,7 @@ channel relay bar **Please note**: you will NOT be able to recover or change passphrase if you lose it. - **Megjegyzés:** NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti. + **Megjegyzés:** NEM fogja tudni helyreállítani vagy módosítani a jelmondatot abban az esetben, ha elveszíti. No comment provided by engineer. @@ -436,6 +443,7 @@ channel relay bar **Test relay** to retrieve its name. + **Átjátszó tesztelése** a nevének lekéréséhez. No comment provided by engineer. @@ -555,7 +563,7 @@ time interval 1-time link can be used *with one contact only* - share in person or via any messenger. - Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható. + Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó alkalmazáson keresztül megosztható. No comment provided by engineer. @@ -1413,6 +1421,7 @@ swipe action Block subscriber for all? + Az összes feliratkozó számára letiltja a feliratkozót? No comment provided by engineer. @@ -1467,6 +1476,7 @@ swipe action Broadcast + Közvetítés… compose placeholder for channel owner @@ -1658,14 +1668,17 @@ set passcode view Channel + Csatorna No comment provided by engineer. Channel display name + Csatorna megjelenítendő neve No comment provided by engineer. Channel full name (optional) + Csatorna teljes neve (nem kötelező) No comment provided by engineer. @@ -1675,10 +1688,12 @@ alert subtitle Channel image + Csatornakép No comment provided by engineer. Channel link + Csatornahivatkozás chat link info line @@ -1687,14 +1702,17 @@ alert subtitle Channel profile + Csatornaprofil No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + A csatornaprofil a feliratkozók eszközén és a csevegési átjátszókon van tárolva. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + A csatornaprofil módosult. Ha menti, akkor a frissített profil el lesz küldve a csatorna feliratkozóinak. alert message @@ -1703,14 +1721,17 @@ alert subtitle Channel will be deleted for all subscribers - this cannot be undone! + A csatorna az összes feliratkozó számára törölve lesz – ez a művelet nem vonható vissza! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + A csatorna törölve lesz az Ön számára – ez a művelet nem vonható vissza! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + A csatorna %2$d átjátszóból %1$d használatával kezd el működni. Folytatja? alert message @@ -1804,18 +1825,22 @@ alert subtitle Chat relay + Csevegési átjátszó No comment provided by engineer. Chat relays + Csevegési átjátszók No comment provided by engineer. Chat relays forward messages in channels you create. + A csevegési átjátszók továbbítják az üzeneteket az Ön által létrehozott csatornákban. No comment provided by engineer. Chat relays forward messages to channel subscribers. + A csevegési átjátszók továbbítják az üzeneteket a csatorna feliratkozóinak. No comment provided by engineer. @@ -1870,10 +1895,12 @@ alert subtitle Check relay address and try again. + Ellenőrizze az átjátszó címét, és próbálja újra. alert message Check relay name and try again. + Ellenőrizze az átjátszó nevét, és próbálja újra. alert message @@ -2023,6 +2050,7 @@ alert subtitle Configure relays + Átjátszók konfigurálása No comment provided by engineer. @@ -2366,7 +2394,7 @@ Ez a saját egyszer használható meghívója! Copy error - Másolási hiba + Hiba másolása No comment provided by engineer. @@ -2436,10 +2464,12 @@ Ez a saját egyszer használható meghívója! Create public channel + Nyilvános csatorna létrehozása No comment provided by engineer. Create public channel (BETA) + Nyilvános csatorna létrehozása (BÉTA) No comment provided by engineer. @@ -2483,6 +2513,7 @@ Ez a saját egyszer használható meghívója! Creating channel + Csatorna létrehozása No comment provided by engineer. @@ -2650,6 +2681,7 @@ Ez a saját egyszer használható meghívója! Decode link + Hivatkozás dekódolása relay test step @@ -2700,10 +2732,12 @@ swipe action Delete channel + Csatorna törlése No comment provided by engineer. Delete channel? + Törli a csatornát? No comment provided by engineer. @@ -2879,6 +2913,7 @@ alert button Delete relay + Átjátszó törlése No comment provided by engineer. @@ -3267,6 +3302,7 @@ chat item action Edit channel profile + Csatornaprofil szerkesztése No comment provided by engineer. @@ -3306,6 +3342,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Engedélyezzen legalább egy csevegési átjátszót a „Hálózat és kiszolgálók” menüben. channel creation warning @@ -3459,6 +3496,7 @@ chat item action Enter channel name… + Adja meg a csatorna nevét… No comment provided by engineer. @@ -3488,6 +3526,7 @@ chat item action Enter relay name… + Adja meg az átjátszó nevét… No comment provided by engineer. @@ -3547,6 +3586,7 @@ chat item action Error adding relay + Hiba az átjátszó hozzáadásakor alert title @@ -3611,6 +3651,7 @@ chat item action Error creating channel + Hiba a csatorna létrehozásakor alert title @@ -3795,6 +3836,7 @@ chat item action Error saving channel profile + Hiba a csatornaprofil mentésekor No comment provided by engineer. @@ -4385,6 +4427,7 @@ Hiba: %2$@ Get link + Hivatkozás megtekintése relay test step @@ -4664,7 +4707,7 @@ Hiba: %2$@ Immune to spam - Védett a kéretlen tartalommal szemben + Védett a kéretlen tartalmakkal szemben No comment provided by engineer. @@ -4893,10 +4936,12 @@ További fejlesztések hamarosan! Invalid relay address! + Érvénytelen az átjátszó címe! alert title Invalid relay name! + Érvénytelen az átjátszó neve! alert title @@ -5016,6 +5061,7 @@ További fejlesztések hamarosan! Join channel + Csatlakozás a csatornához No comment provided by engineer. @@ -5107,10 +5153,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Leave channel + Csatorna elhagyása No comment provided by engineer. Leave channel? + Elhagyja a csatornát? No comment provided by engineer. @@ -5418,6 +5466,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Message error + Üzenethiba No comment provided by engineer. @@ -5750,6 +5799,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! New chat relay + Új csevegési átjátszó No comment provided by engineer. @@ -5833,10 +5883,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! No chat relays + Nincsenek csevegési átjátszók No comment provided by engineer. No chat relays enabled. + Nincsenek engedélyezve csevegési átjátszók. servers warning @@ -5995,6 +6047,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Not all relays connected + Nem minden átjátszó kapcsolódott alert title @@ -6222,6 +6275,7 @@ VPN engedélyezése szükséges. Open channel + Csatorna megnyitása new chat action @@ -6266,6 +6320,7 @@ VPN engedélyezése szükséges. Open new channel + Új csatorna megnyitása new chat action @@ -6370,10 +6425,12 @@ VPN engedélyezése szükséges. Owner + Tulajdonos No comment provided by engineer. Owners + Tulajdonosok No comment provided by engineer. @@ -6595,10 +6652,12 @@ Hiba: %@ Preset relay address + Előre beállított átjátszó címe No comment provided by engineer. Preset relay name + Előre beállított átjátszó neve No comment provided by engineer. @@ -6692,6 +6751,7 @@ Hiba: %@ Proceed + Folytatás alert action @@ -7048,18 +7108,22 @@ swipe action Relay + Átjátszó No comment provided by engineer. Relay address + Átjátszó címe alert title Relay connection failed + Nem sikerült kapcsolódni az átjátszóhoz alert title Relay link + Átjátszóhivatkozás No comment provided by engineer. @@ -7078,6 +7142,7 @@ swipe action Relay test failed! + Nem sikerült tesztelni az átjátszót! No comment provided by engineer. @@ -7126,10 +7191,12 @@ swipe action Remove subscriber + Feliratkozó eltávolítása No comment provided by engineer. Remove subscriber? + Eltávolítja a feliratkozót? alert title @@ -7399,6 +7466,7 @@ chat item action Save (and notify subscribers) + Mentés (és a feliratkozók értesítése) alert button @@ -7432,10 +7500,12 @@ chat item action Save channel profile + Csatornaprofil mentése No comment provided by engineer. Save channel profile? + Menti a csatornaprofilt? alert title @@ -7580,7 +7650,7 @@ chat item action Search or paste SimpleX link - Keresés vagy SimpleX-hivatkozás beillesztése + Keressen vagy adjon meg egy SimpleX-hivatkozást No comment provided by engineer. @@ -7912,6 +7982,7 @@ chat item action Server requires authorization to connect to relay, check password. + A kiszolgáló hitelesítést igényel az átjátszóhoz való kapcsolódáshoz, ellenőrizze a jelszavát. relay test error @@ -8115,6 +8186,7 @@ chat item action Share relay address + Átjátszó címének megosztása No comment provided by engineer. @@ -8247,7 +8319,7 @@ chat item action SimpleX address and 1-time links are safe to share via any messenger. - A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül. + A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó alkalmazáson keresztül. No comment provided by engineer. @@ -8307,6 +8379,7 @@ chat item action SimpleX relay address + SimpleX-átjátszó címe simplex link type @@ -8484,19 +8557,24 @@ report reason Subscriber + Feliratkozó No comment provided by engineer. Subscriber will be removed from channel - this cannot be undone! + A feliratkozó el lesz távolítva a csatornából – ez a művelet nem vonható vissza! alert message Subscribers + Feliratkozók No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz. +Az átjátszó címe ennek az átjátszónak a beállítására szolgált a csatornához. No comment provided by engineer. @@ -8600,6 +8678,7 @@ Relay address was used to set up this relay for the channel. Tap Join channel + Koppintson a „Csatlakozás a csatornához” gombra No comment provided by engineer. @@ -8664,6 +8743,7 @@ server test failure Test relay + Átjátszó tesztelése No comment provided by engineer. @@ -8720,6 +8800,7 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. The app removed this message after %lld attempts to receive it. + Az alkalmazás %lld sikertelen letöltési kísérlet után eltávolította ezt az üzenetet. No comment provided by engineer. @@ -8918,10 +8999,12 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. This is a chat relay address, it cannot be used to connect. + Ez egy csevegési átjátszó címe, nem használható kapcsolódásra. alert message This is your link for channel %@! + Ez a saját hivatkozása a(z) %@ nevű csatornához! new chat action @@ -9152,6 +9235,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll Unblock subscriber for all? + Az összes feliratkozó számára feloldja a feliratkozó letiltását? No comment provided by engineer. @@ -9408,6 +9492,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use for new channels + Használat új csatornákhoz No comment provided by engineer. @@ -9452,6 +9537,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Use relay + Átjátszó használata No comment provided by engineer. @@ -9500,6 +9586,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Verify + Ellenőrzés relay test step @@ -9624,10 +9711,12 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Wait + Várakozás alert action Wait response + Várakozás a válaszra relay test step @@ -9942,6 +10031,7 @@ Megismétli a csatlakozási kérést? You can share a link or a QR code - anybody will be able to join the channel. + Megoszthat egy hivatkozást vagy egy QR-kódot – bárki képes lesz csatlakozni a csatornához. No comment provided by engineer. @@ -9991,6 +10081,7 @@ Megismétli a csatlakozási kérést? You connected to the channel via this relay link. + Ön ezen az átjátszóhivatkozáson keresztül kapcsolódott a csatornához. No comment provided by engineer. @@ -10107,6 +10198,7 @@ Megismétli a kapcsolódási kérést? You will stop receiving messages from this channel. Chat history will be preserved. + Ön nem fog több üzenetet kapni ebből a csatornából. A csevegési előzmények megmaradnak. No comment provided by engineer. @@ -10156,6 +10248,7 @@ Megismétli a kapcsolódási kérést? Your channel + Saját csatorna No comment provided by engineer. @@ -10246,6 +10339,8 @@ Megismétli a kapcsolódási kérést? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + A(z) **%@** nevű profilja meg lesz osztva a csatorna átjátszóival és feliratkozóival. +Az átjátszók hozzáférhetnek a csatornaüzenetekhez. No comment provided by engineer. @@ -10279,15 +10374,17 @@ Relays can access channel messages. Your relay address + Saját átjátszó címe No comment provided by engineer. Your relay name + Saját átjátszó neve No comment provided by engineer. Your server address - Saját SMP-kiszolgálójának címe + Saját SMP-kiszolgáló címe No comment provided by engineer. @@ -10332,6 +10429,7 @@ Relays can access channel messages. accepted + elfogadva No comment provided by engineer. @@ -10356,6 +10454,7 @@ Relays can access channel messages. active + aktív No comment provided by engineer. @@ -10510,10 +10609,12 @@ marked deleted chat item preview text channel + csatorna shown as sender role for channel messages channel profile updated + csatornaprofil frissítve snd group event chat item @@ -10664,6 +10765,7 @@ pref value deleted channel + törölt csatorna rcv group event chat item @@ -10778,6 +10880,7 @@ pref value error: %@ + hiba: %@ receive error chat item @@ -10912,6 +11015,7 @@ pref value link + hivatkozás No comment provided by engineer. @@ -10986,6 +11090,7 @@ pref value new + új No comment provided by engineer. @@ -11113,6 +11218,7 @@ time to disappear relay + átjátszó member role @@ -11127,6 +11233,7 @@ time to disappear removed (%d attempts) + eltávolítva (%d kísérlet) receive error chat item @@ -11289,6 +11396,7 @@ utoljára fogadott üzenet: %2$@ updated channel profile + frissített csatornaprofil rcv group event chat item @@ -11313,6 +11421,7 @@ utoljára fogadott üzenet: %2$@ via %@ + a következőn keresztül: %@ relay hostname @@ -11392,6 +11501,7 @@ utoljára fogadott üzenet: %2$@ you are subscriber + Ön feliratkozó No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 951860ccfe..aad0075e35 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -217,10 +217,12 @@ channel subscriber relay bar %d subscriber + %d iscritto channel subscriber count %d subscribers + %d iscritti channel subscriber count @@ -230,6 +232,7 @@ channel subscriber relay bar %1$d/%2$d relays active + %1$d/%2$d relay attivo/i channel creation progress channel relay bar progress @@ -239,6 +242,7 @@ channel relay bar progress %1$d/%2$d relays active, %3$d failed + %1$d/%2$d relay attivo/i, %3$d fallito/i channel creation progress with errors channel relay bar @@ -248,10 +252,12 @@ channel relay bar
%1$d/%2$d relays connected + %1$d/%2$d relay connesso/i channel subscriber relay bar progress %1$d/%2$d relays connected, %3$d errors + %1$d/%2$d relay connesso/i, %3$d errori channel subscriber relay bar @@ -274,6 +280,7 @@ channel relay bar %lld channel events + %lld eventi del canale No comment provided by engineer. @@ -436,6 +443,7 @@ channel relay bar **Test relay** to retrieve its name. + **Prova il relay** per recuperare il suo nome. No comment provided by engineer. @@ -1413,6 +1421,7 @@ swipe action Block subscriber for all? + Bloccare l'iscritto per tutti? No comment provided by engineer. @@ -1467,6 +1476,7 @@ swipe action Broadcast + Trasmetti compose placeholder for channel owner @@ -1658,14 +1668,17 @@ set passcode view Channel + Canale No comment provided by engineer. Channel display name + Nome da mostrare del canale No comment provided by engineer. Channel full name (optional) + Nome completo del canale (facoltativo) No comment provided by engineer. @@ -1675,10 +1688,12 @@ alert subtitle Channel image + Immagine del canale No comment provided by engineer. Channel link + Link del canale chat link info line @@ -1687,14 +1702,17 @@ alert subtitle Channel profile + Profilo del canale No comment provided by engineer. Channel profile is stored on subscribers' devices and on the chat relays. + Il profilo del canale è memorizzato sui dispositivi degli iscritti e sui relay di chat. No comment provided by engineer. Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers. + Il profilo del canale è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato agli iscritti di canale. alert message @@ -1703,14 +1721,17 @@ alert subtitle Channel will be deleted for all subscribers - this cannot be undone! + Il canale verrà eliminato per tutti gli iscritti, non è reversibile! No comment provided by engineer. Channel will be deleted for you - this cannot be undone! + Il canale verrà eliminato per te, non è reversibile! No comment provided by engineer. Channel will start working with %1$d of %2$d relays. Proceed? + Il canale sarà operativo con %1$d di %2$d relay. Procedere? alert message @@ -1804,18 +1825,22 @@ alert subtitle Chat relay + Relay di chat No comment provided by engineer. Chat relays + Relay di chat No comment provided by engineer. Chat relays forward messages in channels you create. + I relay di chat inoltrano i messaggi nei canali che crei. No comment provided by engineer. Chat relays forward messages to channel subscribers. + I relay di chat inoltrano i messaggi agli iscritti del canale. No comment provided by engineer. @@ -1870,10 +1895,12 @@ alert subtitle Check relay address and try again. + Controlla l'indirizzo del relay e riprova. alert message Check relay name and try again. + Controlla il nome del relay e riprova. alert message @@ -2023,6 +2050,7 @@ alert subtitle Configure relays + Configura i relay No comment provided by engineer. @@ -2436,10 +2464,12 @@ Questo è il tuo link una tantum! Create public channel + Crea canale pubblico No comment provided by engineer. Create public channel (BETA) + Crea canale pubblico (BETA) No comment provided by engineer. @@ -2483,6 +2513,7 @@ Questo è il tuo link una tantum! Creating channel + Creazione canale No comment provided by engineer. @@ -2650,6 +2681,7 @@ Questo è il tuo link una tantum! Decode link + Decodifica il link relay test step @@ -2700,10 +2732,12 @@ swipe action Delete channel + Elimina canale No comment provided by engineer. Delete channel? + Eliminare il canale? No comment provided by engineer. @@ -2879,6 +2913,7 @@ alert button Delete relay + Elimina relay No comment provided by engineer. @@ -3267,6 +3302,7 @@ chat item action Edit channel profile + Modifica profilo canale No comment provided by engineer. @@ -3306,6 +3342,7 @@ chat item action Enable at least one chat relay in Network & Servers. + Attiva almeno un relay di chat in "Rete e server". channel creation warning @@ -3459,6 +3496,7 @@ chat item action Enter channel name… + Inserisci il nome del canale… No comment provided by engineer. @@ -3488,6 +3526,7 @@ chat item action Enter relay name… + Inserisci il nome del relay… No comment provided by engineer. @@ -3547,6 +3586,7 @@ chat item action Error adding relay + Errore di aggiunta del relay alert title @@ -3611,6 +3651,7 @@ chat item action Error creating channel + Errore di creazione del canale alert title @@ -3795,6 +3836,7 @@ chat item action Error saving channel profile + Errore di salvataggio del profilo del canale No comment provided by engineer. @@ -4385,6 +4427,7 @@ Errore: %2$@ Get link + Ottieni link relay test step @@ -4893,10 +4936,12 @@ Altri miglioramenti sono in arrivo! Invalid relay address! + Indirizzo del relay non valido! alert title Invalid relay name! + Nome del relay non valido! alert title @@ -5016,6 +5061,7 @@ Altri miglioramenti sono in arrivo! Join channel + Iscriviti al canale No comment provided by engineer. @@ -5107,10 +5153,12 @@ Questo è il tuo link per il gruppo %@! Leave channel + Esci dal canale No comment provided by engineer. Leave channel? + Uscire dal canale? No comment provided by engineer. @@ -5418,6 +5466,7 @@ Questo è il tuo link per il gruppo %@! Message error + Errore del messaggio No comment provided by engineer. @@ -5750,6 +5799,7 @@ Questo è il tuo link per il gruppo %@! New chat relay + Nuovo relay di chat No comment provided by engineer. @@ -5833,10 +5883,12 @@ Questo è il tuo link per il gruppo %@! No chat relays + Nessun relay di chat No comment provided by engineer. No chat relays enabled. + Nessun relay di chat attivato. servers warning @@ -5995,6 +6047,7 @@ Questo è il tuo link per il gruppo %@! Not all relays connected + Non tutti i relay sono connessi alert title @@ -6222,6 +6275,7 @@ Richiede l'attivazione della VPN. Open channel + Apri canale new chat action @@ -6266,6 +6320,7 @@ Richiede l'attivazione della VPN. Open new channel + Apri un canale nuovo new chat action @@ -6370,10 +6425,12 @@ Richiede l'attivazione della VPN. Owner + Proprietario No comment provided by engineer. Owners + Proprietari No comment provided by engineer. @@ -6595,10 +6652,12 @@ Errore: %@ Preset relay address + Indirizzo relay preimpostato No comment provided by engineer. Preset relay name + Nome relay preimpostato No comment provided by engineer. @@ -6692,6 +6751,7 @@ Errore: %@ Proceed + Procedi alert action @@ -7048,18 +7108,22 @@ swipe action Relay + Relay No comment provided by engineer. Relay address + Indirizzo del relay alert title Relay connection failed + Connessione del relay fallita alert title Relay link + Link del relay No comment provided by engineer. @@ -7078,6 +7142,7 @@ swipe action Relay test failed! + Prova del relay fallita! No comment provided by engineer. @@ -7126,10 +7191,12 @@ swipe action Remove subscriber + Rimuovi iscritto No comment provided by engineer. Remove subscriber? + Rimuovere l'iscritto? alert title @@ -7394,11 +7461,12 @@ chat item action Save (and notify members) - Salva (e informa i membri) + Salva (e avvisa i membri) alert button Save (and notify subscribers) + Salva (e avvisa gli iscritti) alert button @@ -7432,10 +7500,12 @@ chat item action Save channel profile + Salva il profilo del canale No comment provided by engineer. Save channel profile? + Salva il profilo del canale? alert title @@ -7912,6 +7982,7 @@ chat item action Server requires authorization to connect to relay, check password. + Il server richiede l'autorizzazione per connettersi al relay, controlla la password. relay test error @@ -8115,6 +8186,7 @@ chat item action Share relay address + Condividi l'indirizzo del relay No comment provided by engineer. @@ -8307,6 +8379,7 @@ chat item action SimpleX relay address + Indirizzo del relay SimpleX simplex link type @@ -8479,24 +8552,29 @@ report reason Subscribed - Iscritto + Iscritto/a No comment provided by engineer. Subscriber + Iscritto No comment provided by engineer. Subscriber will be removed from channel - this cannot be undone! + L'iscritto verrà rimosso dal canale, non è reversibile! alert message Subscribers + Iscritti No comment provided by engineer. Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. + Gli iscritti usano il link del relay per connettersi al canale. +L'indirizzo del relay è stato usato per impostare questo relay per il canale. No comment provided by engineer. @@ -8600,6 +8678,7 @@ Relay address was used to set up this relay for the channel. Tap Join channel + Tocca Iscriviti al canale No comment provided by engineer. @@ -8664,6 +8743,7 @@ server test failure Test relay + Prova relay No comment provided by engineer. @@ -8720,6 +8800,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. The app removed this message after %lld attempts to receive it. + L'app ha rimosso questo messaggio dopo %lld tentativi di riceverlo. No comment provided by engineer. @@ -8918,10 +8999,12 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. This is a chat relay address, it cannot be used to connect. + Questo è un indirizzo di relay di chat, non può essere usato per connettersi. alert message This is your link for channel %@! + Questo è il tuo link per il canale %@! new chat action @@ -9152,6 +9235,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Unblock subscriber for all? + Sbloccare l'iscritto per tutti? No comment provided by engineer. @@ -9408,6 +9492,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use for new channels + Usa per canali nuovi No comment provided by engineer. @@ -9452,6 +9537,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Use relay + Usa relay No comment provided by engineer. @@ -9500,6 +9586,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Verify + Verifica relay test step @@ -9624,10 +9711,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Wait + Attendi alert action Wait response + Attendi risposta relay test step @@ -9942,6 +10031,7 @@ Ripetere la richiesta di ingresso? You can share a link or a QR code - anybody will be able to join the channel. + Puoi condividere un link o un codice QR, chiunque sarà in grado di iscriversi al canale. No comment provided by engineer. @@ -9991,6 +10081,7 @@ Ripetere la richiesta di ingresso? You connected to the channel via this relay link. + Ti sei connesso/a al canale attraverso questo link del relay. No comment provided by engineer. @@ -10107,6 +10198,7 @@ Ripetere la richiesta di connessione? You will stop receiving messages from this channel. Chat history will be preserved. + Smetterai di ricevere messaggi da questo canale. La cronologia della chat sarà preservata. No comment provided by engineer. @@ -10156,6 +10248,7 @@ Ripetere la richiesta di connessione? Your channel + Il tuo canale No comment provided by engineer. @@ -10246,6 +10339,8 @@ Ripetere la richiesta di connessione? Your profile **%@** will be shared with channel relays and subscribers. Relays can access channel messages. + Il tuo profilo **%@** verrà condiviso con i relay e gli iscritti. +I relay hanno accesso ai messaggi del canale. No comment provided by engineer. @@ -10279,10 +10374,12 @@ Relays can access channel messages. Your relay address + L'indirizzo del tuo relay No comment provided by engineer. Your relay name + Il nome del tuo relay No comment provided by engineer. @@ -10332,6 +10429,7 @@ Relays can access channel messages. accepted + accettato No comment provided by engineer. @@ -10356,6 +10454,7 @@ Relays can access channel messages. active + attivo No comment provided by engineer. @@ -10510,10 +10609,12 @@ marked deleted chat item preview text channel + canale shown as sender role for channel messages channel profile updated + profilo del canale aggiornato snd group event chat item @@ -10664,6 +10765,7 @@ pref value deleted channel + canale eliminato rcv group event chat item @@ -10778,6 +10880,7 @@ pref value error: %@ + errore: %@ receive error chat item @@ -10912,6 +11015,7 @@ pref value link + link No comment provided by engineer. @@ -10986,6 +11090,7 @@ pref value new + nuovo No comment provided by engineer. @@ -11113,6 +11218,7 @@ time to disappear relay + relay member role @@ -11127,6 +11233,7 @@ time to disappear removed (%d attempts) + rimosso (%d tentativi) receive error chat item @@ -11289,6 +11396,7 @@ ultimo msg ricevuto: %2$@ updated channel profile + profilo del canale aggiornato rcv group event chat item @@ -11313,6 +11421,7 @@ ultimo msg ricevuto: %2$@ via %@ + via %@ relay hostname @@ -11392,6 +11501,7 @@ ultimo msg ricevuto: %2$@ you are subscriber + sei iscritto/a No comment provided by engineer. diff --git a/apps/ios/SimpleX SE/de.lproj/Localizable.strings b/apps/ios/SimpleX SE/de.lproj/Localizable.strings index 403fb3820a..df368686e8 100644 --- a/apps/ios/SimpleX SE/de.lproj/Localizable.strings +++ b/apps/ios/SimpleX SE/de.lproj/Localizable.strings @@ -80,7 +80,7 @@ "Please create a profile in the SimpleX app" = "Bitte erstellen Sie in der SimpleX-App ein Profil"; /* No comment provided by engineer. */ -"Selected chat preferences prohibit this message." = "Die gewählten Chat-Einstellungen erlauben diese Nachricht nicht."; +"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt."; /* No comment provided by engineer. */ "Sending a message takes longer than expected." = "Das Senden einer Nachricht dauert länger als erwartet."; diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 323c5c6985..e82e9df6c7 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -389,9 +389,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Активни връзки"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти."; - /* No comment provided by engineer. */ "Add friends" = "Добави приятели"; @@ -873,7 +870,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Български, финландски, тайландски и украински - благодарение на потребителите и [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Бизнес адрес"; /* No comment provided by engineer. */ @@ -1318,7 +1315,7 @@ server test step */ /* alert title */ "Connection error" = "Грешка при свързване"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Грешка при свързване (AUTH)"; /* chat list item title (it should not be shown */ @@ -1393,9 +1390,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Поправи име на %@?"; -/* No comment provided by engineer. */ -"Create" = "Създаване"; - /* No comment provided by engineer. */ "Create 1-time link" = "Създаване на еднократна препратка"; @@ -1802,7 +1796,7 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Редактирай групов профил"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Активирай"; /* No comment provided by engineer. */ @@ -1964,7 +1958,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "грешка"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Грешка при свързване със сървъра"; /* No comment provided by engineer. */ @@ -2306,7 +2300,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Груповата покана вече е невалидна, премахната е от подателя."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Групов линк"; /* No comment provided by engineer. */ @@ -2519,7 +2513,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "невалидни данни за чат"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Невалиден линк за връзка"; /* invalid chat item */ @@ -3040,7 +3034,7 @@ alert button new chat action */ "Ok" = "Ок"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "ОК"; /* No comment provided by engineer. */ @@ -3271,9 +3265,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "Профилна парола"; -/* alert message */ -"Profile update will be sent to your contacts." = "Актуализацията на профила ще бъде изпратена до вашите контакти."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Забрани аудио/видео разговорите."; @@ -3782,18 +3773,12 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "Сподели адрес"; -/* alert title */ -"Share address with contacts?" = "Сподели адреса с контактите?"; - /* No comment provided by engineer. */ "Share link" = "Сподели линк"; /* No comment provided by engineer. */ "Share this 1-time invite link" = "Сподели този еднократен линк за връзка"; -/* No comment provided by engineer. */ -"Share with contacts" = "Сподели с контактите"; - /* No comment provided by engineer. */ "Show calls in phone history" = "Показване на обажданията в хронологията на телефона"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 0031c2421b..104835b3d3 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -374,9 +374,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Aktivní spojení"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům."; - /* No comment provided by engineer. */ "Add friends" = "Přidat přátele"; @@ -746,7 +743,7 @@ swipe action */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulharský, finský, thajský a ukrajinský - díky uživatelům a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Obchodní adresa"; /* No comment provided by engineer. */ @@ -1017,7 +1014,7 @@ server test step */ /* alert title */ "Connection error" = "Chyba připojení"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Chyba spojení (AUTH)"; /* chat list item title (it should not be shown */ @@ -1071,9 +1068,6 @@ server test step */ /* No comment provided by engineer. */ "Core version: v%@" = "Verze jádra: v%@"; -/* No comment provided by engineer. */ -"Create" = "Vytvořit"; - /* server test step */ "Create file" = "Vytvořit soubor"; @@ -1416,7 +1410,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "Upravit profil skupiny"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Zapnout"; /* No comment provided by engineer. */ @@ -1548,7 +1542,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "chyba"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Chyba"; /* No comment provided by engineer. */ @@ -1824,7 +1818,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Skupinová pozvánka již není platná, byla odstraněna odesílatelem."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Odkaz na skupinu"; /* No comment provided by engineer. */ @@ -2010,7 +2004,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "neplatná chat data"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Neplatný odkaz na spojení"; /* invalid chat item */ @@ -2606,9 +2600,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "Heslo profilu"; -/* alert message */ -"Profile update will be sent to your contacts." = "Aktualizace profilu bude zaslána vašim kontaktům."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Zákaz audio/video hovorů."; @@ -3036,15 +3027,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "Sdílet adresu"; -/* alert title */ -"Share address with contacts?" = "Sdílet adresu s kontakty?"; - /* No comment provided by engineer. */ "Share link" = "Sdílet odkaz"; -/* No comment provided by engineer. */ -"Share with contacts" = "Sdílet s kontakty"; - /* No comment provided by engineer. */ "Show calls in phone history" = "Ukaž hovory v historii telefonu"; @@ -3578,7 +3563,7 @@ server test failure */ "You can set lock screen notification preview via settings." = "Náhled oznámení na zamykací obrazovce můžete změnit v nastavení."; /* No comment provided by engineer. */ -"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte."; +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud odkaz později smažete."; /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Tuto adresu můžete sdílet s vašimi kontakty, abyse se mohli spojit s **%@**."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 7137ccac8b..65f8320b5c 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -64,6 +64,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Link scannen / einfügen**: Um eine Verbindung über den Link herzustellen, den Sie erhalten haben."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Relais testen** um seinen Namen abzurufen."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist."; @@ -184,15 +187,38 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d übersprungene Nachricht(en)"; +/* channel subscriber count */ +"%d subscriber" = "%d Abonnent"; + +/* channel subscriber count */ +"%d subscribers" = "%d Abonnenten"; + /* time interval */ "%d weeks" = "%d Wochen"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d Relais aktiv"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d Relais aktiv, %3$d fehlgeschlagen"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d Relais verbunden"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d Relais verbunden, %3$d Fehler"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld Kanalereignisse"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld Kontakt(e) ausgewählt"; @@ -371,6 +397,9 @@ swipe action */ /* alert title */ "Accept member" = "Mitglied annehmen"; +/* No comment provided by engineer. */ +"accepted" = "Angenommen"; + /* rcv group event chat item */ "accepted %@" = "%@ angenommen"; @@ -392,15 +421,15 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Fehler bei der Bestätigung"; +/* No comment provided by engineer. */ +"active" = "Aktiv"; + /* token status text */ "Active" = "Aktiv"; /* No comment provided by engineer. */ "Active connections" = "Aktive Verbindungen"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet."; - /* No comment provided by engineer. */ "Add friends" = "Freunde aufnehmen"; @@ -851,6 +880,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "Mitglied blockieren?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "Abonnent für alle blockieren?"; + /* marked deleted chat item preview text */ "blocked" = "Blockiert"; @@ -894,10 +926,13 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Sowohl Ihr Kontakt, als auch Sie können Sprachnachrichten senden."; +/* compose placeholder for channel owner */ +"Broadcast" = "Broadcast"; + /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgarisch, Finnisch, Thailändisch und Ukrainisch - Dank der Nutzer und [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Geschäftliche Adresse"; /* No comment provided by engineer. */ @@ -1038,6 +1073,45 @@ set passcode view */ /* chat item text */ "changing address…" = "Wechsel der Empfängeradresse wurde gestartet…"; +/* shown as sender role for channel messages */ +"channel" = "Kanal"; + +/* No comment provided by engineer. */ +"Channel" = "Kanal"; + +/* No comment provided by engineer. */ +"Channel display name" = "Anzeigename des Kanals"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Vollständiger Kanalname (optional)"; + +/* No comment provided by engineer. */ +"Channel image" = "Kanalbild"; + +/* chat link info line */ +"Channel link" = "Kanallink"; + +/* No comment provided by engineer. */ +"Channel profile" = "Kanalprofil"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "Das Kanalprofil wird auf den Geräten der Abonnenten und auf den Chat‑Relais gespeichert."; + +/* snd group event chat item */ +"channel profile updated" = "Kanalprofil wurde aktualisiert"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "Das Kanalprofil wurde geändert. Beim Speichern wird das aktualisierte Profil an die Abonnenten des Kanals gesendet."; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "Der Kanal wird für alle Abonnenten gelöscht. Dies kann nicht rückgängig gemacht werden!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "Der Kanal wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "Der Kanal wird mit %1$d von %2$d Relais gestartet. Fortfahren?"; + /* No comment provided by engineer. */ "Chat" = "Chat"; @@ -1089,6 +1163,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Benutzerprofil"; +/* No comment provided by engineer. */ +"Chat relay" = "Chat-Relais"; + +/* No comment provided by engineer. */ +"Chat relays" = "Chat-Relais"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "Chat‑Relais leiten Nachrichten in den von Ihnen erstellten Kanälen weiter."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "Chat‑Relais leiten Nachrichten an Kanal-Abonnenten weiter."; + /* No comment provided by engineer. */ "Chat theme" = "Chat-Design"; @@ -1119,6 +1205,12 @@ set passcode view */ /* No comment provided by engineer. */ "Check messages when allowed." = "Wenn es erlaubt ist, Nachrichten überprüfen."; +/* alert message */ +"Check relay address and try again." = "Relais-Adresse überprüfen und erneut versuchen."; + +/* alert message */ +"Check relay name and try again." = "Relais-Name überprüfen und erneut versuchen."; + /* alert title */ "Check server address and try again." = "Überprüfen Sie die Serveradresse und versuchen Sie es nochmal."; @@ -1212,6 +1304,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-Server konfigurieren"; +/* No comment provided by engineer. */ +"Configure relays" = "Relais konfigurieren"; + /* No comment provided by engineer. */ "Configure server operators" = "Server-Betreiber konfigurieren"; @@ -1348,7 +1443,7 @@ server test step */ /* alert title */ "Connection error" = "Verbindungsfehler"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Verbindungsfehler (AUTH)"; /* chat list item title (it should not be shown */ @@ -1471,9 +1566,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Richtiger Name für %@?"; -/* No comment provided by engineer. */ -"Create" = "Erstellen"; - /* No comment provided by engineer. */ "Create 1-time link" = "Einmal-Link erstellen"; @@ -1501,6 +1593,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Profil erstellen"; +/* No comment provided by engineer. */ +"Create public channel" = "Öffentlichen Kanal erstellen"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Öffentlichen Kanal erstellen (BETA)"; + /* server test step */ "Create queue" = "Erzeuge Warteschlange"; @@ -1525,6 +1623,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Archiv-Link erzeugen"; +/* No comment provided by engineer. */ +"Creating channel" = "Kanal wird erstellt"; + /* No comment provided by engineer. */ "Creating link…" = "Link wird erstellt…"; @@ -1630,6 +1731,9 @@ server test step */ /* No comment provided by engineer. */ "Decentralized" = "Dezentral"; +/* relay test step */ +"Decode link" = "Link dekodieren"; + /* message decrypt error item */ "Decryption error" = "Entschlüsselungsfehler"; @@ -1671,6 +1775,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Kontakt löschen und benachrichtigen"; +/* No comment provided by engineer. */ +"Delete channel" = "Kanal löschen"; + +/* No comment provided by engineer. */ +"Delete channel?" = "Kanal löschen?"; + /* No comment provided by engineer. */ "Delete chat" = "Chat löschen"; @@ -1774,6 +1884,9 @@ alert button */ /* server test step */ "Delete queue" = "Lösche Warteschlange"; +/* No comment provided by engineer. */ +"Delete relay" = "Relais löschen"; + /* No comment provided by engineer. */ "Delete report" = "Meldung löschen"; @@ -1798,6 +1911,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Gelöscht um: %@"; +/* rcv group event chat item */ +"deleted channel" = "Kanal gelöscht"; + /* rcv direct event chat item */ "deleted contact" = "Gelöschter Kontakt"; @@ -2027,18 +2143,24 @@ chat item action */ /* chat item action */ "Edit" = "Bearbeiten"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Kanalprofil bearbeiten"; + /* No comment provided by engineer. */ "Edit group profile" = "Gruppenprofil bearbeiten"; /* No comment provided by engineer. */ "Empty message!" = "Leere Nachricht!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Aktivieren"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Aktivieren (vorgenommene Einstellungen bleiben erhalten)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Aktivieren Sie mindestens ein Chat‑Relais unter 'Netzwerk & Server'."; + /* alert title */ "Enable automatic message deletion?" = "Automatisches Löschen von Nachrichten aktivieren?"; @@ -2171,6 +2293,9 @@ chat item action */ /* call status */ "ended call %@" = "Anruf beendet %@"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Kanalname eingeben…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Geben Sie das korrekte Passwort ein."; @@ -2189,6 +2314,9 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "Für die Anzeige das Passwort im Suchfeld eingeben!"; +/* No comment provided by engineer. */ +"Enter relay name…" = "Relais-Name eingeben…"; + /* No comment provided by engineer. */ "Enter server manually" = "Geben Sie den Server manuell ein"; @@ -2207,7 +2335,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "Fehler"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Fehler"; /* No comment provided by engineer. */ @@ -2225,6 +2353,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; +/* alert title */ +"Error adding relay" = "Fehler beim Hinzufügen des Relais"; + /* alert title */ "Error adding server" = "Fehler beim Hinzufügen des Servers"; @@ -2261,6 +2392,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Fehler beim Erstellen der Adresse"; +/* alert title */ +"Error creating channel" = "Fehler beim Erstellen des Kanals"; + /* No comment provided by engineer. */ "Error creating group" = "Fehler beim Erzeugen der Gruppe"; @@ -2366,6 +2500,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Fehler beim Zurücksetzen der Statistiken"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Fehler beim Speichern des Kanalprofils"; + /* alert title */ "Error saving chat list" = "Fehler beim Speichern der Chat-Liste"; @@ -2450,6 +2587,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Fehler: "; +/* receive error chat item */ +"error: %@" = "Fehler: %@"; + /* alert message file error text snd error text */ @@ -2719,6 +2859,9 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Weiter reduzierter Batterieverbrauch"; +/* relay test step */ +"Get link" = "Link erhalten"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Bei Erwähnung benachrichtigt werden."; @@ -2767,7 +2910,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "Gruppe wurde gelöscht"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Gruppen-Link"; /* No comment provided by engineer. */ @@ -3037,7 +3180,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "Ungültige Chat-Daten"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Ungültiger Verbindungslink"; /* invalid chat item */ @@ -3058,6 +3201,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Ungültiger QR-Code"; +/* alert title */ +"Invalid relay address!" = "Ungültige Relais-Adresse!"; + +/* alert title */ +"Invalid relay name!" = "Ungültiger Relais-Name!"; + /* No comment provided by engineer. */ "Invalid response" = "Ungültige Reaktion"; @@ -3151,6 +3300,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "Als %@ beitreten"; +/* No comment provided by engineer. */ +"Join channel" = "Kanal beitreten"; + /* new chat sheet title */ "Join group" = "Treten Sie der Gruppe bei"; @@ -3199,6 +3351,12 @@ servers warning */ /* swipe action */ "Leave" = "Verlassen"; +/* No comment provided by engineer. */ +"Leave channel" = "Kanal verlassen"; + +/* No comment provided by engineer. */ +"Leave channel?" = "Kanal verlassen?"; + /* No comment provided by engineer. */ "Leave chat" = "Chat verlassen"; @@ -3226,6 +3384,9 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Einschränkungen"; +/* No comment provided by engineer. */ +"link" = "Link"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "Verknüpfe Mobiltelefon- und Desktop-Apps! 🔗"; @@ -3400,6 +3561,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Nachrichtenentwurf"; +/* No comment provided by engineer. */ +"Message error" = "Übertragungsfehler"; + /* item status text */ "Message forwarded" = "Nachricht weitergeleitet"; @@ -3598,6 +3762,9 @@ servers warning */ /* delete after time */ "never" = "nie"; +/* No comment provided by engineer. */ +"new" = "Neu"; + /* token status text */ "New" = "Neu"; @@ -3607,6 +3774,9 @@ servers warning */ /* No comment provided by engineer. */ "New chat experience 🎉" = "Neue Chat-Erfahrung 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Neues Chat-Relais"; + /* notification */ "New contact request" = "Neue Kontaktanfrage"; @@ -3667,6 +3837,12 @@ servers warning */ /* Authentication unavailable */ "No app password" = "Kein App-Passwort"; +/* No comment provided by engineer. */ +"No chat relays" = "Keine Chat-Relais"; + +/* servers warning */ +"No chat relays enabled." = "Es sind keine Chat-Relais aktiviert."; + /* No comment provided by engineer. */ "No chats" = "Keine Chats"; @@ -3766,6 +3942,9 @@ servers warning */ /* No comment provided by engineer. */ "No user identifiers." = "Keine Benutzerkennungen."; +/* alert title */ +"Not all relays connected" = "Es sind nicht alle Relais verbunden"; + /* No comment provided by engineer. */ "Not compatible!" = "Nicht kompatibel!"; @@ -3822,7 +4001,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3844,7 +4023,7 @@ new chat action */ "Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet."; /* No comment provided by engineer. */ -"Only chat owners can change preferences." = "Nur Chat-Eigentümer können die Präferenzen ändern."; +"Only chat owners can change preferences." = "Präferenzen können nur von Chat-Eigentümern geändert werden."; /* No comment provided by engineer. */ "Only client devices store user profiles, contacts, groups, and messages." = "Nur die Endgeräte speichern die Benutzerprofile, Kontakte, Gruppen und Nachrichten, welche über eine **2-Schichten Ende-zu-Ende-Verschlüsselung** gesendet werden."; @@ -3909,6 +4088,9 @@ new chat action */ /* No comment provided by engineer. */ "Open changes" = "Änderungen öffnen"; +/* new chat action */ +"Open channel" = "Kanal öffnen"; + /* new chat action */ "Open chat" = "Chat öffnen"; @@ -3933,6 +4115,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; +/* new chat action */ +"Open new channel" = "Neuen Kanal öffnen"; + /* new chat action */ "Open new chat" = "Neuen Chat öffnen"; @@ -3999,9 +4184,15 @@ new chat action */ /* member role */ "owner" = "Eigentümer"; +/* No comment provided by engineer. */ +"Owner" = "Eigentümer"; + /* feature role */ "owners" = "Eigentümer"; +/* No comment provided by engineer. */ +"Owners" = "Eigentümer"; + /* No comment provided by engineer. */ "Passcode" = "Zugangscode"; @@ -4137,6 +4328,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren."; +/* No comment provided by engineer. */ +"Preset relay address" = "Voreingestellte Relais-Adresse"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Voreingestellter Relais-Name"; + /* No comment provided by engineer. */ "Preset server address" = "Voreingestellte Serveradresse"; @@ -4188,6 +4385,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Zeitüberschreitung der privaten Routing-Sitzung"; +/* alert action */ +"Proceed" = "Fortfahren"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil und Serververbindungen"; @@ -4203,9 +4403,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Profil-Design"; -/* alert message */ -"Profile update will be sent to your contacts." = "Profil-Aktualisierung wird an Ihre Kontakte gesendet."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Audio-/Video-Anrufe nicht erlauben."; @@ -4412,12 +4609,30 @@ swipe action */ /* call status */ "rejected call" = "Abgelehnter Anruf"; +/* member role */ +"relay" = "Relais"; + +/* No comment provided by engineer. */ +"Relay" = "Relais"; + +/* alert title */ +"Relay address" = "Relais-Adresse"; + +/* alert title */ +"Relay connection failed" = "Relais-Verbindung fehlgeschlagen"; + +/* No comment provided by engineer. */ +"Relay link" = "Relais-Link"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "Relais-Server werden nur genutzt, wenn sie benötigt werden. Ihre IP-Adresse kann von Anderen erfasst werden."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "Relais-Server schützen Ihre IP-Adresse, aber sie können die Anrufdauer erfassen."; +/* No comment provided by engineer. */ +"Relay test failed!" = "Relais-Test fehlgeschlagen!"; + /* alert action */ "Remove" = "Entfernen"; @@ -4442,9 +4657,18 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Passwort aus dem Schlüsselbund entfernen?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Abonnent entfernen"; + +/* alert title */ +"Remove subscriber?" = "Abonnent entfernen?"; + /* No comment provided by engineer. */ "removed" = "entfernt"; +/* receive error chat item */ +"removed (%d attempts)" = "Entfernt (%d Versuche)"; + /* rcv group event chat item */ "removed %@" = "hat %@ aus der Gruppe entfernt"; @@ -4632,6 +4856,9 @@ chat item action */ /* alert button */ "Save (and notify members)" = "Speichern (und Mitglieder benachrichtigen)"; +/* alert button */ +"Save (and notify subscribers)" = "Speichern (Abonnenten benachrichtigen)"; + /* alert title */ "Save admission settings?" = "Speichern der Aufnahme-Einstellungen?"; @@ -4647,6 +4874,12 @@ chat item action */ /* No comment provided by engineer. */ "Save and update group profile" = "Gruppen-Profil sichern und aktualisieren"; +/* No comment provided by engineer. */ +"Save channel profile" = "Kanalprofil speichern"; + +/* alert title */ +"Save channel profile?" = "Kanalprofil speichern?"; + /* No comment provided by engineer. */ "Save group profile" = "Gruppenprofil speichern"; @@ -4786,7 +5019,7 @@ chat item action */ "Selected %lld" = "%lld ausgewählt"; /* No comment provided by engineer. */ -"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt."; +"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt."; /* No comment provided by engineer. */ "Self-destruct" = "Selbstzerstörung"; @@ -4822,7 +5055,7 @@ chat item action */ "Send errors" = "Fehler beim Senden"; /* No comment provided by engineer. */ -"Send link previews" = "Link-Vorschau senden"; +"Send link previews" = "Linkvorschau senden"; /* No comment provided by engineer. */ "Send live message" = "Live Nachricht senden"; @@ -4947,6 +5180,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "Server-Warteschlangen-Information: %1$@\n\nZuletzt empfangene Nachricht: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "Der Server erfordert eine Autorisierung, um eine Verbindung zum Relais herzustellen. Bitte Passwort überprüfen."; + /* server test error */ "Server requires authorization to create queues, check password." = "Der Server erfordert zum Erstellen von Warteschlangen eine Autorisierung. Bitte überprüfen Sie das Passwort."; @@ -5050,9 +5286,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Die Adresse öffentlich teilen"; -/* alert title */ -"Share address with contacts?" = "Die Adresse mit Kontakten teilen?"; - /* No comment provided by engineer. */ "Share from other apps." = "Aus anderen Apps heraus teilen."; @@ -5068,6 +5301,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Profil teilen"; +/* No comment provided by engineer. */ +"Share relay address" = "Relais-Adresse teilen"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "Die SimpleX-Adresse auf sozialen Medien teilen."; @@ -5077,9 +5313,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Mit SimpleX teilen"; -/* No comment provided by engineer. */ -"Share with contacts" = "Mit Kontakten teilen"; - /* No comment provided by engineer. */ "Share your address" = "Ihre Adresse teilen"; @@ -5138,7 +5371,7 @@ chat item action */ "SimpleX address settings" = "Einstellungen automatisch akzeptieren"; /* simplex link type */ -"SimpleX channel link" = "SimpleX-Kanal-Link"; +"SimpleX channel link" = "SimpleX-Kanallink"; /* No comment provided by engineer. */ "SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX-Chat und Flux haben vereinbart, die von Flux betriebenen Server in die App aufzunehmen."; @@ -5182,6 +5415,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Die SimpleX-Protokolle wurden von Trail of Bits überprüft."; +/* simplex link type */ +"SimpleX relay address" = "SimpleX Relais-Adresse"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Vereinfachter Inkognito-Modus"; @@ -5300,6 +5536,18 @@ report reason */ /* No comment provided by engineer. */ "Subscribed" = "Abonniert"; +/* No comment provided by engineer. */ +"Subscriber" = "Abonnent"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "Abonnent wird aus dem Kanal entfernt. Dies kann nicht rückgängig gemacht werden!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Abonnenten"; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Abonnenten verbinden sich über den Relais‑Link mit dem Kanal.\nDie Relais-Adresse wurde zur Einrichtung dieses Relais für diesen Kanal verwendet."; + /* No comment provided by engineer. */ "Subscription errors" = "Fehler beim Abonnieren"; @@ -5340,13 +5588,13 @@ report reason */ "Tap Connect to use bot" = "Verbinden tippen, um den Bot zu nutzen."; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Tippen Sie im Menü auf SimpleX-Adresse erstellen, um sie später zu erstellen."; +"Tap Join channel" = "Tippen, um dem Kanal beizutreten"; /* No comment provided by engineer. */ "Tap Join group" = "Tippen, um der Gruppe beizutreten"; /* No comment provided by engineer. */ -"Tap to activate profile." = "Zum Aktivieren des Profils tippen."; +"Tap to activate profile." = "Tippen, um das Profil zu aktivieren."; /* No comment provided by engineer. */ "Tap to Connect" = "Zum Verbinden tippen"; @@ -5358,7 +5606,7 @@ report reason */ "Tap to join incognito" = "Zum Inkognito beitreten tippen"; /* No comment provided by engineer. */ -"Tap to paste link" = "Zum Link einfügen tippen"; +"Tap to paste link" = "Tippen, um den Link einzufügen"; /* No comment provided by engineer. */ "Tap to scan" = "Zum Scannen tippen"; @@ -5394,6 +5642,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Benachrichtigungen testen"; +/* No comment provided by engineer. */ +"Test relay" = "Relais testen"; + /* No comment provided by engineer. */ "Test server" = "Teste Server"; @@ -5421,6 +5672,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "Durch Verwendung verschiedener Netzwerk-Betreiber für jede Unterhaltung schützt die App Ihre Privatsphäre."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "Die App hat diese Nachricht nach %lld Empfangsversuchen entfernt."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Die App wird eine Bestätigung bei Downloads von unbekannten Datei-Servern anfordern (außer bei .onion)."; @@ -5541,6 +5795,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Diese Gruppe existiert nicht mehr."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Dies ist eine Chat‑Relais-Adresse, welche nicht zum Verbinden verwendet werden kann."; + +/* new chat action */ +"This is your link for channel %@!" = "Dies ist Ihr Link für den Kanal %@!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden."; @@ -5673,6 +5933,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "Mitglied freigeben?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "Abonnent für alle freigeben?"; + /* rcv group event chat item */ "unblocked %@" = "hat %@ freigegeben"; @@ -5745,7 +6008,7 @@ server test failure */ /* swipe action */ "Unread" = "Ungelesen"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Verbindungs-Link wird nicht unterstützt"; /* No comment provided by engineer. */ @@ -5763,6 +6026,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "Einstellungen aktualisieren?"; +/* rcv group event chat item */ +"updated channel profile" = "Kanalprofil aktualisiert"; + /* No comment provided by engineer. */ "Updated conditions" = "Aktualisierte Nutzungsbedingungen"; @@ -5832,6 +6098,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Für Nachrichten verwenden"; +/* No comment provided by engineer. */ +"Use for new channels" = "Für neue Kanäle verwenden"; + /* No comment provided by engineer. */ "Use for new connections" = "Für neue Verbindungen nutzen"; @@ -5856,6 +6125,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Sie nutzen privates Routing mit unbekannten Servern."; +/* No comment provided by engineer. */ +"Use relay" = "Relais verwenden"; + /* No comment provided by engineer. */ "Use server" = "Server nutzen"; @@ -5898,6 +6170,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Überprüfen"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Code mit dem Desktop überprüfen"; @@ -5919,6 +6194,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Sicherheitscode überprüfen"; +/* relay hostname */ +"via %@" = "via %@"; + /* No comment provided by engineer. */ "Via browser" = "Über den Browser"; @@ -5988,6 +6266,12 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "Sprachnachrichten sind nicht erlaubt!"; +/* alert action */ +"Wait" = "Abwarten"; + +/* relay test step */ +"Wait response" = "Antwort abwarten"; + /* No comment provided by engineer. */ "waiting for answer…" = "Warten auf Antwort…"; @@ -6156,6 +6440,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "Sie sind Beobachter"; +/* No comment provided by engineer. */ +"you are subscriber" = "Sie sind Abonnent"; + /* snd group event chat item */ "you blocked %@" = "Sie haben %@ blockiert"; @@ -6198,6 +6485,9 @@ server test failure */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Über die Geräte-Einstellungen können Sie die Benachrichtigungsvorschau im Sperrbildschirm erlauben."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the channel." = "Sie können einen Link oder QR-Code teilen - damit kann jeder dem Kanal beitreten."; + /* No comment provided by engineer. */ "You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Sie können diesen Link oder QR-Code teilen - Damit kann jede Person der Gruppe beitreten. Wenn Sie den Link später löschen, werden Sie keine Gruppenmitglieder verlieren, die der Gruppe darüber beigetreten sind."; @@ -6237,6 +6527,9 @@ server test failure */ /* snd group event chat item */ "you changed role of %@ to %@" = "Sie haben die Rolle von %1$@ auf %2$@ geändert"; +/* No comment provided by engineer. */ +"You connected to the channel via this relay link." = "Sie haben sich über diesen Relais‑Link mit dem Kanal verbunden."; + /* No comment provided by engineer. */ "You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut."; @@ -6318,6 +6611,9 @@ server test failure */ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Sie können Anrufe und Benachrichtigungen auch von stummgeschalteten Profilen empfangen, solange diese aktiv sind."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this channel. Chat history will be preserved." = "Sie werden keine Nachrichten mehr aus diesem Kanal erhalten. Der Chatverlauf bleibt erhalten."; + /* No comment provided by engineer. */ "You will stop receiving messages from this chat. Chat history will be preserved." = "Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf bleibt erhalten."; @@ -6342,6 +6638,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Anrufe"; +/* No comment provided by engineer. */ +"Your channel" = "Ihr Kanal"; + /* No comment provided by engineer. */ "Your chat database" = "Chat-Datenbank"; @@ -6396,6 +6695,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Mein Profil"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "Ihr Profil **%@** wird mit Kanal‑Relais und Abonnenten geteilt.\nRelais können auf Kanalnachrichten zugreifen."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt."; @@ -6411,6 +6713,12 @@ server test failure */ /* No comment provided by engineer. */ "Your random profile" = "Ihr Zufallsprofil"; +/* No comment provided by engineer. */ +"Your relay address" = "Ihre Relais-Adresse"; + +/* No comment provided by engineer. */ +"Your relay name" = "Ihr Relais-Name"; + /* No comment provided by engineer. */ "Your server address" = "Ihre Serveradresse"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 42ed52c412..4901d67e23 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -64,6 +64,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Escanear / Pegar enlace**: para conectar mediante un enlace recibido."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Test servidor** para recibir su nombre."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain."; @@ -184,15 +187,38 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d mensaje(s) omitido(s)"; +/* channel subscriber count */ +"%d subscriber" = "%d suscriptor"; + +/* channel subscriber count */ +"%d subscribers" = "%d suscriptores"; + /* time interval */ "%d weeks" = "%d semana(s)"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d servidores activos"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d servidores activos, %3$d han fallado"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d servidores conectados"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d servidores conectados, %3$d errores"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld eventos del canal"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld contacto(s) seleccionado(s)"; @@ -200,7 +226,7 @@ "%lld file(s) with total size of %@" = "%lld archivo(s) con un tamaño total de %@"; /* No comment provided by engineer. */ -"%lld group events" = "%lld evento(s) de grupo"; +"%lld group events" = "%lld evento(s) del grupo"; /* No comment provided by engineer. */ "%lld members" = "%lld miembros"; @@ -371,6 +397,9 @@ swipe action */ /* alert title */ "Accept member" = "Aceptar miembro"; +/* No comment provided by engineer. */ +"accepted" = "aceptado"; + /* rcv group event chat item */ "accepted %@" = "%@ aceptado"; @@ -392,15 +421,15 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Errores de confirmación"; +/* No comment provided by engineer. */ +"active" = "activo"; + /* token status text */ "Active" = "Activo"; /* No comment provided by engineer. */ "Active connections" = "Conexiones activas"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos."; - /* No comment provided by engineer. */ "Add friends" = "Añadir amigos"; @@ -851,6 +880,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "¿Bloquear miembro?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "¿Bloquear al suscriptor para todos?"; + /* marked deleted chat item preview text */ "blocked" = "bloqueado"; @@ -894,10 +926,13 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Tanto tú como tu contacto podéis enviar mensajes de voz."; +/* compose placeholder for channel owner */ +"Broadcast" = "Emisión"; + /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Búlgaro, Finlandés, Tailandés y Ucraniano - gracias a los usuarios y [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Dirección empresarial"; /* No comment provided by engineer. */ @@ -1038,6 +1073,45 @@ set passcode view */ /* chat item text */ "changing address…" = "cambiando de servidor…"; +/* shown as sender role for channel messages */ +"channel" = "canal"; + +/* No comment provided by engineer. */ +"Channel" = "Canal"; + +/* No comment provided by engineer. */ +"Channel display name" = "Título mostrado del canal"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Título completo del canal (opcional)"; + +/* No comment provided by engineer. */ +"Channel image" = "Imagen del canal"; + +/* chat link info line */ +"Channel link" = "Enlace del canal"; + +/* No comment provided by engineer. */ +"Channel profile" = "Perfil del canal"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "El perfil del canal se almacena en los dispositivos de los suscriptores y en los servidores de chat."; + +/* snd group event chat item */ +"channel profile updated" = "perfil del canal actualizado"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "El perfil del canal ha sido modificado. Si lo guardas, el perfil actualizado será enviado a los suscriptores."; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "El canal será eliminado para todos los suscriptores. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "El canal será eliminado para tí. ¡No puede deshacerse!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "El canal comenzará a funcionar con %1$d de %2$d servidores. ¿Continuar?"; + /* No comment provided by engineer. */ "Chat" = "Chat"; @@ -1089,6 +1163,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Perfil de usuario"; +/* No comment provided by engineer. */ +"Chat relay" = "Servidor de chat"; + +/* No comment provided by engineer. */ +"Chat relays" = "Servidores de chat"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "Los servidores de chat reenvían los mensajes en los canales que has creado."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "Los servidores de chat reenvían los mensajes a los suscriptores del canal."; + /* No comment provided by engineer. */ "Chat theme" = "Tema de chat"; @@ -1119,6 +1205,12 @@ set passcode view */ /* No comment provided by engineer. */ "Check messages when allowed." = "Comprobar mensajes cuando se permita."; +/* alert message */ +"Check relay address and try again." = "Comprueba la dirección del servidor y prueba de nuevo."; + +/* alert message */ +"Check relay name and try again." = "Comprueba el nombre del servidor y prueba de nuevo."; + /* alert title */ "Check server address and try again." = "Comprueba la dirección del servidor e inténtalo de nuevo."; @@ -1212,6 +1304,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Configure servidores ICE"; +/* No comment provided by engineer. */ +"Configure relays" = "Configurar servidores"; + /* No comment provided by engineer. */ "Configure server operators" = "Configurar operadores de servidores"; @@ -1348,12 +1443,15 @@ server test step */ /* alert title */ "Connection error" = "Error conexión"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Error de conexión (Autenticación)"; /* chat list item title (it should not be shown */ "connection established" = "conexión establecida"; +/* No comment provided by engineer. */ +"Connection failed" = "Conexión fallida"; + /* No comment provided by engineer. */ "Connection is blocked by server operator:\n%@" = "Conexión bloqueada por el operador del servidor:\n%@"; @@ -1468,9 +1566,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "¿Corregir el nombre a %@?"; -/* No comment provided by engineer. */ -"Create" = "Crear"; - /* No comment provided by engineer. */ "Create 1-time link" = "Crear enlace de un solo uso"; @@ -1498,6 +1593,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Crear perfil"; +/* No comment provided by engineer. */ +"Create public channel" = "Crear canal público"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Crear canal público (BETA)"; + /* server test step */ "Create queue" = "Crear cola"; @@ -1522,6 +1623,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Creando enlace al archivo"; +/* No comment provided by engineer. */ +"Creating channel" = "Creando canal"; + /* No comment provided by engineer. */ "Creating link…" = "Creando enlace…"; @@ -1627,6 +1731,9 @@ server test step */ /* No comment provided by engineer. */ "Decentralized" = "Descentralizada"; +/* relay test step */ +"Decode link" = "Decodificar enlace"; + /* message decrypt error item */ "Decryption error" = "Error descifrado"; @@ -1668,6 +1775,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Eliminar y notificar contacto"; +/* No comment provided by engineer. */ +"Delete channel" = "Eliminar canal"; + +/* No comment provided by engineer. */ +"Delete channel?" = "¿Eliminar canal?"; + /* No comment provided by engineer. */ "Delete chat" = "Eliminar chat"; @@ -1771,6 +1884,9 @@ alert button */ /* server test step */ "Delete queue" = "Eliminar cola"; +/* No comment provided by engineer. */ +"Delete relay" = "Eliminar servidor"; + /* No comment provided by engineer. */ "Delete report" = "Eliminar informe"; @@ -1795,6 +1911,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Eliminado: %@"; +/* rcv group event chat item */ +"deleted channel" = "canal eliminado"; + /* rcv direct event chat item */ "deleted contact" = "contacto eliminado"; @@ -2024,18 +2143,24 @@ chat item action */ /* chat item action */ "Edit" = "Editar"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Editar perfil del canal"; + /* No comment provided by engineer. */ "Edit group profile" = "Editar perfil de grupo"; /* No comment provided by engineer. */ "Empty message!" = "¡Mensaje vacío!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Activar"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Activar (conservar anulaciones)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Activar al menos un servidor de chat en Servidores y Redes."; + /* alert title */ "Enable automatic message deletion?" = "¿Activar eliminación automática de mensajes?"; @@ -2168,6 +2293,9 @@ chat item action */ /* call status */ "ended call %@" = "llamada finalizada %@"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Introduce el título del canal…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Introduce la contraseña correcta."; @@ -2186,6 +2314,9 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "¡Introduce la contraseña arriba para mostrar!"; +/* No comment provided by engineer. */ +"Enter relay name…" = "Introduce el nombre del servidor…"; + /* No comment provided by engineer. */ "Enter server manually" = "Añadir manualmente"; @@ -2204,7 +2335,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "error"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Error"; /* No comment provided by engineer. */ @@ -2222,6 +2353,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Error al añadir miembro(s)"; +/* alert title */ +"Error adding relay" = "Error al añadir el servidor"; + /* alert title */ "Error adding server" = "Error al añadir servidor"; @@ -2258,6 +2392,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Error al crear dirección"; +/* alert title */ +"Error creating channel" = "Error al crear el canal"; + /* No comment provided by engineer. */ "Error creating group" = "Error al crear grupo"; @@ -2363,6 +2500,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Error al restablecer las estadísticas"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Error al guardar el perfil del canal"; + /* alert title */ "Error saving chat list" = "Error al guardar listas"; @@ -2447,6 +2587,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Error: "; +/* receive error chat item */ +"error: %@" = "error: %@"; + /* alert message file error text snd error text */ @@ -2501,6 +2644,9 @@ server test error */ /* No comment provided by engineer. */ "Exporting database archive…" = "Exportando base de datos…"; +/* No comment provided by engineer. */ +"failed" = "fallo"; + /* No comment provided by engineer. */ "Failed to remove passphrase" = "Error al eliminar la contraseña"; @@ -2713,6 +2859,9 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Reducción consumo de batería"; +/* relay test step */ +"Get link" = "Recibir el enlace"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Las menciones ahora se notifican."; @@ -2761,7 +2910,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "el grupo ha sido eliminado"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Enlace de grupo"; /* No comment provided by engineer. */ @@ -2872,6 +3021,9 @@ servers warning */ /* No comment provided by engineer. */ "If you enter your self-destruct passcode while opening the app:" = "Si al abrir la aplicación introduces el código de autodestrucción:"; +/* down migration warning */ +"If you joined or created channels, they will stop working permanently." = "Si te has unido o has creado canales, dejarán de funcionar permanentemente."; + /* No comment provided by engineer. */ "If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Si necesitas usar el chat ahora pulsa **Hacerlo más tarde** más abajo (se ofrecerá migrar la base de datos cuando se reinicie la aplicación)."; @@ -3028,7 +3180,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "datos Chat no válidos"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Enlace de conexión no válido"; /* invalid chat item */ @@ -3049,6 +3201,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Código QR no válido"; +/* alert title */ +"Invalid relay address!" = "¡Dirección de servidor no válido!"; + +/* alert title */ +"Invalid relay name!" = "¡Nombre de servidor no válido!"; + /* No comment provided by engineer. */ "Invalid response" = "Respuesta no válida"; @@ -3142,6 +3300,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "Unirme como %@"; +/* No comment provided by engineer. */ +"Join channel" = "Unirme al canal"; + /* new chat sheet title */ "Join group" = "Unirme al grupo"; @@ -3190,6 +3351,12 @@ servers warning */ /* swipe action */ "Leave" = "Salir"; +/* No comment provided by engineer. */ +"Leave channel" = "Salir del canal"; + +/* No comment provided by engineer. */ +"Leave channel?" = "¿Salir del canal?"; + /* No comment provided by engineer. */ "Leave chat" = "Salir del chat"; @@ -3217,6 +3384,9 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Limitaciones"; +/* No comment provided by engineer. */ +"link" = "enlace"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "¡Enlazar aplicación móvil con ordenador! 🔗"; @@ -3391,6 +3561,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Borrador de mensaje"; +/* No comment provided by engineer. */ +"Message error" = "Mensaje de error"; + /* item status text */ "Message forwarded" = "Mensaje reenviado"; @@ -3589,6 +3762,9 @@ servers warning */ /* delete after time */ "never" = "nunca"; +/* No comment provided by engineer. */ +"new" = "nuevo"; + /* token status text */ "New" = "Nuevo"; @@ -3598,6 +3774,9 @@ servers warning */ /* No comment provided by engineer. */ "New chat experience 🎉" = "Nueva experiencia de chat 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Nuevo servidor de chat"; + /* notification */ "New contact request" = "Nueva solicitud de contacto"; @@ -3658,6 +3837,12 @@ servers warning */ /* Authentication unavailable */ "No app password" = "Sin contraseña de la aplicación"; +/* No comment provided by engineer. */ +"No chat relays" = "Sin servidores de chat"; + +/* servers warning */ +"No chat relays enabled." = "Ningún servidor de chat activado."; + /* No comment provided by engineer. */ "No chats" = "Sin chats"; @@ -3757,6 +3942,9 @@ servers warning */ /* No comment provided by engineer. */ "No user identifiers." = "Sin identificadores de usuario."; +/* alert title */ +"Not all relays connected" = "Hay servidores no conectados"; + /* No comment provided by engineer. */ "Not compatible!" = "¡No compatible!"; @@ -3813,7 +4001,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3900,6 +4088,9 @@ new chat action */ /* No comment provided by engineer. */ "Open changes" = "Abrir cambios"; +/* new chat action */ +"Open channel" = "Abrir canal"; + /* new chat action */ "Open chat" = "Abrir chat"; @@ -3924,6 +4115,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Abrir menú migración a otro dispositivo"; +/* new chat action */ +"Open new channel" = "Abrir canal nuevo"; + /* new chat action */ "Open new chat" = "Abrir chat nuevo"; @@ -3990,9 +4184,15 @@ new chat action */ /* member role */ "owner" = "propietario"; +/* No comment provided by engineer. */ +"Owner" = "Propietario"; + /* feature role */ "owners" = "propietarios"; +/* No comment provided by engineer. */ +"Owners" = "Propietarios"; + /* No comment provided by engineer. */ "Passcode" = "Código de acceso"; @@ -4128,6 +4328,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva el último borrador del mensaje con los datos adjuntos."; +/* No comment provided by engineer. */ +"Preset relay address" = "Direcciones predefinidas"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Nombres predefinidos"; + /* No comment provided by engineer. */ "Preset server address" = "Dirección predefinida del servidor"; @@ -4179,6 +4385,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Timeout enrutamiento privado"; +/* alert action */ +"Proceed" = "Continuar"; + /* No comment provided by engineer. */ "Profile and server connections" = "Eliminar perfil y conexiones"; @@ -4194,9 +4403,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Tema del perfil"; -/* alert message */ -"Profile update will be sent to your contacts." = "La actualización del perfil se enviará a tus contactos."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "No se permiten llamadas y videollamadas."; @@ -4403,12 +4609,30 @@ swipe action */ /* call status */ "rejected call" = "llamada rechazada"; +/* member role */ +"relay" = "servidor"; + +/* No comment provided by engineer. */ +"Relay" = "Servidor"; + +/* alert title */ +"Relay address" = "Dirección del servidor"; + +/* alert title */ +"Relay connection failed" = "La conexión con el servidor ha fallado"; + +/* No comment provided by engineer. */ +"Relay link" = "Enlace servidor"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "El servidor de retransmisión protege tu IP pero puede ver la duración de la llamada."; +/* No comment provided by engineer. */ +"Relay test failed!" = "¡El test del servidor ha fallado!"; + /* alert action */ "Remove" = "Eliminar"; @@ -4433,9 +4657,18 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "¿Eliminar contraseña de Keychain?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Eliminar suscriptor"; + +/* alert title */ +"Remove subscriber?" = "¿Eliminar suscriptor?"; + /* No comment provided by engineer. */ "removed" = "expulsado"; +/* receive error chat item */ +"removed (%d attempts)" = "eliminado (%d intentos)"; + /* rcv group event chat item */ "removed %@" = "ha expulsado a %@"; @@ -4623,6 +4856,9 @@ chat item action */ /* alert button */ "Save (and notify members)" = "Guardar (y notificar miembros)"; +/* alert button */ +"Save (and notify subscribers)" = "Guardar (y notificar suscriptores)"; + /* alert title */ "Save admission settings?" = "¿Guardar configuración?"; @@ -4638,6 +4874,12 @@ chat item action */ /* No comment provided by engineer. */ "Save and update group profile" = "Guardar y actualizar perfil del grupo"; +/* No comment provided by engineer. */ +"Save channel profile" = "Guardar perfil del canal"; + +/* alert title */ +"Save channel profile?" = "¿Guardar perfil del canal?"; + /* No comment provided by engineer. */ "Save group profile" = "Guardar perfil de grupo"; @@ -4938,6 +5180,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "información cola del servidor: %1$@\n\núltimo mensaje recibido: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "El servidor requiere autorización para conectar con el servidor, comprueba la contraseña."; + /* server test error */ "Server requires authorization to create queues, check password." = "El servidor requiere autorización para crear colas, comprueba la contraseña."; @@ -5041,9 +5286,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Campartir dirección públicamente"; -/* alert title */ -"Share address with contacts?" = "¿Compartir la dirección con los contactos?"; - /* No comment provided by engineer. */ "Share from other apps." = "Comparte desde otras aplicaciones."; @@ -5059,6 +5301,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Perfil a compartir"; +/* No comment provided by engineer. */ +"Share relay address" = "Compartir dirección del servidor"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "Comparte tu dirección SimpleX en redes sociales."; @@ -5068,9 +5313,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Compartir con Simplex"; -/* No comment provided by engineer. */ -"Share with contacts" = "Compartir con contactos"; - /* No comment provided by engineer. */ "Share your address" = "Comparte tu dirección"; @@ -5173,6 +5415,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Protocolos de SimpleX auditados por Trail of Bits."; +/* simplex link type */ +"SimpleX relay address" = "Dirección de servidor SimpleX"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modo incógnito simplificado"; @@ -5291,6 +5536,18 @@ report reason */ /* No comment provided by engineer. */ "Subscribed" = "Suscritas"; +/* No comment provided by engineer. */ +"Subscriber" = "Suscriptor"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "El suscriptor será eliminado del canal. ¡No puede deshacerse!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Suscriptores"; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Los suscriptores usan el enlace del servidor para conectarse a los canales.\nLa dirección del servidor se usó para establecer el servidor para el canal."; + /* No comment provided by engineer. */ "Subscription errors" = "Errores de suscripción"; @@ -5331,7 +5588,7 @@ report reason */ "Tap Connect to use bot" = "Pulsa Conectar para usar el bot"; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Pulsa Crear dirección SimpleX en el menú para crearla más tarde."; +"Tap Join channel" = "Pulsa Unirme al canal"; /* No comment provided by engineer. */ "Tap Join group" = "Pulsa Unirme al grupo"; @@ -5385,6 +5642,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Probar notificaciones"; +/* No comment provided by engineer. */ +"Test relay" = "Test servidor"; + /* No comment provided by engineer. */ "Test server" = "Probar servidor"; @@ -5412,6 +5672,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "La aplicación protege tu privacidad mediante el uso de diferentes operadores en cada conversación."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "La app ha eliminado el mensaje tras %lld intentos de recibirlo."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto si son .onion)."; @@ -5532,6 +5795,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Este grupo ya no existe."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Esto es una dirección de servidor, no puede usarse para conectar."; + +/* new chat action */ +"This is your link for channel %@!" = "Este es tu enlace para el canal %@!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Este enlace requiere una versión más reciente de la aplicación. Por favor, actualiza la aplicación o pide a tu contacto un enlace compatible."; @@ -5664,6 +5933,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "¿Desbloquear miembro?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "¿Desbloquear al suscriptor para todos?"; + /* rcv group event chat item */ "unblocked %@" = "ha desbloqueado a %@"; @@ -5736,7 +6008,7 @@ server test failure */ /* swipe action */ "Unread" = "No leído"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Enlace de conexión no compatible"; /* No comment provided by engineer. */ @@ -5754,6 +6026,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "¿Actualizar configuración?"; +/* rcv group event chat item */ +"updated channel profile" = "perfil del canal actualizado"; + /* No comment provided by engineer. */ "Updated conditions" = "Condiciones actualizadas"; @@ -5823,6 +6098,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Uso para mensajes"; +/* No comment provided by engineer. */ +"Use for new channels" = "Usar para canales nuevos"; + /* No comment provided by engineer. */ "Use for new connections" = "Para conexiones nuevas"; @@ -5847,6 +6125,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Usar enrutamiento privado con servidores de mensaje desconocidos."; +/* No comment provided by engineer. */ +"Use relay" = "Usar servidor"; + /* No comment provided by engineer. */ "Use server" = "Usar servidor"; @@ -5889,6 +6170,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Verificar"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Verificar código con ordenador"; @@ -5910,6 +6194,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Comprobar código de seguridad"; +/* relay hostname */ +"via %@" = "mediante %@"; + /* No comment provided by engineer. */ "Via browser" = "Mediante navegador"; @@ -5979,6 +6266,12 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "¡Mensajes de voz no permitidos!"; +/* alert action */ +"Wait" = "Espera"; + +/* relay test step */ +"Wait response" = "Espera respuesta"; + /* No comment provided by engineer. */ "waiting for answer…" = "esperando respuesta…"; @@ -6147,6 +6440,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "Tu rol es observador"; +/* No comment provided by engineer. */ +"you are subscriber" = "eres suscriptor"; + /* snd group event chat item */ "you blocked %@" = "has bloqueado a %@"; @@ -6190,7 +6486,10 @@ server test failure */ "You can set lock screen notification preview via settings." = "Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración."; /* No comment provided by engineer. */ -"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo."; +"You can share a link or a QR code - anybody will be able to join the channel." = "Puedes compartir un enlace o código QR. Cualquiera podrá unirse al canal."; + +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puedes compartir el enlace o código QR. Cualquiera podrá unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo."; /* No comment provided by engineer. */ "You can share this address with your contacts to let them connect with **%@**." = "Puedes compartir esta dirección con tus contactos para que puedan conectar con **%@**."; @@ -6228,6 +6527,9 @@ server test failure */ /* snd group event chat item */ "you changed role of %@ to %@" = "has cambiado el rol de %1$@ a %2$@"; +/* No comment provided by engineer. */ +"You connected to the channel via this relay link." = "Te conectaste al canal mediante este enlace de servidor."; + /* No comment provided by engineer. */ "You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; @@ -6310,7 +6612,10 @@ server test failure */ "You will still receive calls and notifications from muted profiles when they are active." = "Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos."; /* No comment provided by engineer. */ -"You will stop receiving messages from this chat. Chat history will be preserved." = "Dejarás de recibir mensajes del chat. El historial del chat se conserva."; +"You will stop receiving messages from this channel. Chat history will be preserved." = "Dejarás de recibir mensajes de este canal. El historial del chat se conservará."; + +/* No comment provided by engineer. */ +"You will stop receiving messages from this chat. Chat history will be preserved." = "Dejarás de recibir mensajes del chat. El historial del chat se conservará."; /* No comment provided by engineer. */ "You will stop receiving messages from this group. Chat history will be preserved." = "Dejarás de recibir mensajes del grupo. El historial del chat se conservará."; @@ -6333,6 +6638,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Llamadas"; +/* No comment provided by engineer. */ +"Your channel" = "Tu canal"; + /* No comment provided by engineer. */ "Your chat database" = "Base de datos"; @@ -6387,6 +6695,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Tu perfil"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "El perfil **%@** será compartido con los servidores de canal y los suscriptores.\nLos servidores tienen acceso a los mensajes del canal."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "El perfil **%@** será compartido."; @@ -6402,6 +6713,12 @@ server test failure */ /* No comment provided by engineer. */ "Your random profile" = "Tu perfil aleatorio"; +/* No comment provided by engineer. */ +"Your relay address" = "Tu dirección de servidor"; + +/* No comment provided by engineer. */ +"Your relay name" = "Tu nombre del servidor"; + /* No comment provided by engineer. */ "Your server address" = "Dirección del servidor"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 5f81f94fb8..74f071a6e0 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -257,9 +257,6 @@ swipe action */ /* call status */ "accepted call" = "hyväksytty puhelu"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi."; - /* No comment provided by engineer. */ "Add profile" = "Lisää profiili"; @@ -699,7 +696,7 @@ server test step */ /* alert title */ "Connection error" = "Yhteysvirhe"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Yhteysvirhe (AUTH)"; /* chat list item title (it should not be shown */ @@ -753,9 +750,6 @@ server test step */ /* No comment provided by engineer. */ "Core version: v%@" = "Ydinversio: v%@"; -/* No comment provided by engineer. */ -"Create" = "Luo"; - /* server test step */ "Create file" = "Luo tiedosto"; @@ -1098,7 +1092,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "Muokkaa ryhmäprofiilia"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Salli"; /* No comment provided by engineer. */ @@ -1227,7 +1221,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "virhe"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Virhe"; /* No comment provided by engineer. */ @@ -1497,7 +1491,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "Ryhmäkutsu ei ole enää voimassa, lähettäjä poisti sen."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Ryhmälinkki"; /* No comment provided by engineer. */ @@ -1683,7 +1677,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "virheelliset keskustelu-tiedot"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Virheellinen yhteyslinkki"; /* invalid chat item */ @@ -2273,9 +2267,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "Profiilin salasana"; -/* alert message */ -"Profile update will be sent to your contacts." = "Profiilipäivitys lähetetään kontakteillesi."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Estä ääni- ja videopuhelut."; @@ -2697,15 +2688,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "Jaa osoite"; -/* alert title */ -"Share address with contacts?" = "Jaa osoite kontakteille?"; - /* No comment provided by engineer. */ "Share link" = "Jaa linkki"; -/* No comment provided by engineer. */ -"Share with contacts" = "Jaa kontaktien kanssa"; - /* No comment provided by engineer. */ "Show calls in phone history" = "Näytä puhelut puhelinhistoriassa"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 30f28c6b8c..279bf9f3a0 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -392,9 +392,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Connections actives"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts."; - /* No comment provided by engineer. */ "Add friends" = "Ajouter des amis"; @@ -864,7 +861,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgare, finnois, thaïlandais et ukrainien - grâce aux utilisateurs et à [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat) !"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Adresse professionnelle"; /* No comment provided by engineer. */ @@ -1291,7 +1288,7 @@ server test step */ /* alert title */ "Connection error" = "Erreur de connexion"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Erreur de connexion (AUTH)"; /* chat list item title (it should not be shown */ @@ -1396,9 +1393,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Corriger le nom pour %@ ?"; -/* No comment provided by engineer. */ -"Create" = "Créer"; - /* No comment provided by engineer. */ "Create 1-time link" = "Créer un lien unique"; @@ -1937,7 +1931,7 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Modifier le profil du groupe"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Activer"; /* No comment provided by engineer. */ @@ -2108,7 +2102,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "erreur"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Erreur"; /* No comment provided by engineer. */ @@ -2610,7 +2604,7 @@ servers warning */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "L'invitation du groupe n'est plus valide, elle a été supprimé par l'expéditeur."; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Lien du groupe"; /* No comment provided by engineer. */ @@ -2841,7 +2835,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "données de chat invalides"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Lien de connection invalide"; /* invalid chat item */ @@ -3509,7 +3503,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3815,9 +3809,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Thème de profil"; -/* alert message */ -"Profile update will be sent to your contacts." = "La mise à jour du profil sera envoyée à vos contacts."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Interdire les appels audio/vidéo."; @@ -4515,9 +4506,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Partager publiquement votre adresse"; -/* alert title */ -"Share address with contacts?" = "Partager l'adresse avec vos contacts ?"; - /* No comment provided by engineer. */ "Share from other apps." = "Partager depuis d'autres applications."; @@ -4536,9 +4524,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Partager sur SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Partager avec vos contacts"; - /* No comment provided by engineer. */ "Show → on messages sent via private routing." = "Afficher → sur les messages envoyés via le routage privé."; @@ -4767,9 +4752,6 @@ chat item action */ /* No comment provided by engineer. */ "Tap button " = "Appuyez sur le bouton "; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Appuyez sur Créer une adresse SimpleX dans le menu pour la créer ultérieurement."; - /* No comment provided by engineer. */ "Tap to activate profile." = "Appuyez pour activer un profil."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 93eb633dd7..db3c4c8255 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -50,13 +50,13 @@ "**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van."; /* No comment provided by engineer. */ -"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**Legprivátabb:** ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; +"**Most private**: do not use SimpleX Chat push server. The app will check messages in background, when the system allows it, depending on how often you use the app." = "**A legprivátabb**: Az alkalmazás nem használja a SimpleX Chat push-kiszolgálóját. Az alkalmazás a háttérben ellenőrzi az üzeneteket, amikor a rendszer ezt lehetővé teszi, attól függően, hogy Ön milyen gyakran használja az alkalmazást."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Megjegyzés:** ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja a partnereitől érkező üzenetek visszafejtését."; /* No comment provided by engineer. */ -"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Megjegyzés:** NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti."; +"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Megjegyzés:** NEM fogja tudni helyreállítani vagy módosítani a jelmondatot abban az esetben, ha elveszíti."; /* No comment provided by engineer. */ "**Recommended**: device token and end-to-end encrypted notifications are sent to SimpleX Chat push server, but it does not see the message content, size or who it is from." = "**Megjegyzés:** az eszköztoken és az értesítések el lesznek küldve a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; @@ -64,6 +64,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Hivatkozás beolvasása / beillesztése**: egy kapott hivatkozáson keresztüli kapcsolódáshoz."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Átjátszó tesztelése** a nevének lekéréséhez."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali leküldéses értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges."; @@ -184,15 +187,38 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d üzenet kihagyva"; +/* channel subscriber count */ +"%d subscriber" = "%d feliratkozó"; + +/* channel subscriber count */ +"%d subscribers" = "%d feliratkozó"; + /* time interval */ "%d weeks" = "%d hét"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d átjátszó aktív"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d átjátszó aktív, %3$d sikertelen"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d átjátszó kapcsolódva"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d átjátszó kapcsolódva, %3$d hiba"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld csatornaesemény"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld partner kiválasztva"; @@ -293,7 +319,7 @@ time interval */ "1-time link" = "Egyszer használható meghívó"; /* No comment provided by engineer. */ -"1-time link can be used *with one contact only* - share in person or via any messenger." = "Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható."; +"1-time link can be used *with one contact only* - share in person or via any messenger." = "Az egyszer használható meghívó egy hivatkozás és *csak egyetlen partnerrel használható* – személyesen vagy bármilyen üzenetváltó alkalmazáson keresztül megosztható."; /* No comment provided by engineer. */ "5 minutes" = "5 perc"; @@ -371,6 +397,9 @@ swipe action */ /* alert title */ "Accept member" = "Tag befogadása"; +/* No comment provided by engineer. */ +"accepted" = "elfogadva"; + /* rcv group event chat item */ "accepted %@" = "befogadta őt: %@"; @@ -392,15 +421,15 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Visszaigazolási hibák"; +/* No comment provided by engineer. */ +"active" = "aktív"; + /* token status text */ "Active" = "Aktív"; /* No comment provided by engineer. */ "Active connections" = "Aktív kapcsolatok száma"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára."; - /* No comment provided by engineer. */ "Add friends" = "Barátok hozzáadása"; @@ -851,6 +880,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "Letiltja a tagot?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "Az összes feliratkozó számára letiltja a feliratkozót?"; + /* marked deleted chat item preview text */ "blocked" = "letiltva"; @@ -894,10 +926,13 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Mindkét fél küldhet hangüzeneteket."; +/* compose placeholder for channel owner */ +"Broadcast" = "Közvetítés…"; + /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bolgár, finn, thai és ukrán – köszönet a felhasználóknak és a [Weblate-nek](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Üzleti cím"; /* No comment provided by engineer. */ @@ -1038,6 +1073,45 @@ set passcode view */ /* chat item text */ "changing address…" = "cím módosítása…"; +/* shown as sender role for channel messages */ +"channel" = "csatorna"; + +/* No comment provided by engineer. */ +"Channel" = "Csatorna"; + +/* No comment provided by engineer. */ +"Channel display name" = "Csatorna megjelenítendő neve"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Csatorna teljes neve (nem kötelező)"; + +/* No comment provided by engineer. */ +"Channel image" = "Csatornakép"; + +/* chat link info line */ +"Channel link" = "Csatornahivatkozás"; + +/* No comment provided by engineer. */ +"Channel profile" = "Csatornaprofil"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "A csatornaprofil a feliratkozók eszközén és a csevegési átjátszókon van tárolva."; + +/* snd group event chat item */ +"channel profile updated" = "csatornaprofil frissítve"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "A csatornaprofil módosult. Ha menti, akkor a frissített profil el lesz küldve a csatorna feliratkozóinak."; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "A csatorna az összes feliratkozó számára törölve lesz – ez a művelet nem vonható vissza!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "A csatorna törölve lesz az Ön számára – ez a művelet nem vonható vissza!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "A csatorna %2$d átjátszóból %1$d használatával kezd el működni. Folytatja?"; + /* No comment provided by engineer. */ "Chat" = "Csevegés"; @@ -1089,6 +1163,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Csevegési profil"; +/* No comment provided by engineer. */ +"Chat relay" = "Csevegési átjátszó"; + +/* No comment provided by engineer. */ +"Chat relays" = "Csevegési átjátszók"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "A csevegési átjátszók továbbítják az üzeneteket az Ön által létrehozott csatornákban."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "A csevegési átjátszók továbbítják az üzeneteket a csatorna feliratkozóinak."; + /* No comment provided by engineer. */ "Chat theme" = "Csevegés témája"; @@ -1119,6 +1205,12 @@ set passcode view */ /* No comment provided by engineer. */ "Check messages when allowed." = "Üzenetek ellenőrzése, amikor engedélyezett."; +/* alert message */ +"Check relay address and try again." = "Ellenőrizze az átjátszó címét, és próbálja újra."; + +/* alert message */ +"Check relay name and try again." = "Ellenőrizze az átjátszó nevét, és próbálja újra."; + /* alert title */ "Check server address and try again." = "Kiszolgáló címének ellenőrzése és újrapróbálkozás."; @@ -1212,6 +1304,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-kiszolgálók beállítása"; +/* No comment provided by engineer. */ +"Configure relays" = "Átjátszók konfigurálása"; + /* No comment provided by engineer. */ "Configure server operators" = "Kiszolgálóüzemeltetők beállítása"; @@ -1348,7 +1443,7 @@ server test step */ /* alert title */ "Connection error" = "Kapcsolódási hiba"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Kapcsolódási hiba (AUTH)"; /* chat list item title (it should not be shown */ @@ -1460,7 +1555,7 @@ server test step */ "Copy" = "Másolás"; /* No comment provided by engineer. */ -"Copy error" = "Másolási hiba"; +"Copy error" = "Hiba másolása"; /* No comment provided by engineer. */ "Core version: v%@" = "Fő verzió: v%@"; @@ -1471,9 +1566,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Helyesbíti a nevet a következőre: %@?"; -/* No comment provided by engineer. */ -"Create" = "Létrehozás"; - /* No comment provided by engineer. */ "Create 1-time link" = "Egyszer használható meghívó létrehozása"; @@ -1501,6 +1593,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Profil létrehozása"; +/* No comment provided by engineer. */ +"Create public channel" = "Nyilvános csatorna létrehozása"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Nyilvános csatorna létrehozása (BÉTA)"; + /* server test step */ "Create queue" = "Várólista létrehozása"; @@ -1525,6 +1623,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Archívum hivatkozás létrehozása"; +/* No comment provided by engineer. */ +"Creating channel" = "Csatorna létrehozása"; + /* No comment provided by engineer. */ "Creating link…" = "Hivatkozás létrehozása…"; @@ -1630,6 +1731,9 @@ server test step */ /* No comment provided by engineer. */ "Decentralized" = "Decentralizált"; +/* relay test step */ +"Decode link" = "Hivatkozás dekódolása"; + /* message decrypt error item */ "Decryption error" = "Titkosítás-visszafejtési hiba"; @@ -1671,6 +1775,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Törlés, és a partner értesítése"; +/* No comment provided by engineer. */ +"Delete channel" = "Csatorna törlése"; + +/* No comment provided by engineer. */ +"Delete channel?" = "Törli a csatornát?"; + /* No comment provided by engineer. */ "Delete chat" = "Csevegés törlése"; @@ -1774,6 +1884,9 @@ alert button */ /* server test step */ "Delete queue" = "Várólista törlése"; +/* No comment provided by engineer. */ +"Delete relay" = "Átjátszó törlése"; + /* No comment provided by engineer. */ "Delete report" = "Jelentés törlése"; @@ -1798,6 +1911,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Törölve: %@"; +/* rcv group event chat item */ +"deleted channel" = "törölt csatorna"; + /* rcv direct event chat item */ "deleted contact" = "törölt partner"; @@ -2027,18 +2143,24 @@ chat item action */ /* chat item action */ "Edit" = "Szerkesztés"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Csatornaprofil szerkesztése"; + /* No comment provided by engineer. */ "Edit group profile" = "Csoportprofil szerkesztése"; /* No comment provided by engineer. */ "Empty message!" = "Üres üzenet!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Engedélyezés"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Engedélyezés (egyéni beállítások megtartása)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Engedélyezzen legalább egy csevegési átjátszót a „Hálózat és kiszolgálók” menüben."; + /* alert title */ "Enable automatic message deletion?" = "Engedélyezi az automatikus üzenettörlést?"; @@ -2171,6 +2293,9 @@ chat item action */ /* call status */ "ended call %@" = "%@ hívása véget ért"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Adja meg a csatorna nevét…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Adja meg a helyes jelmondatot."; @@ -2189,6 +2314,9 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "Adja meg a jelszót fentebb a megjelenítéshez!"; +/* No comment provided by engineer. */ +"Enter relay name…" = "Adja meg az átjátszó nevét…"; + /* No comment provided by engineer. */ "Enter server manually" = "Kiszolgáló megadása kézzel"; @@ -2207,7 +2335,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "hiba"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Hiba"; /* No comment provided by engineer. */ @@ -2225,6 +2353,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Hiba történt a tag(ok) hozzáadásakor"; +/* alert title */ +"Error adding relay" = "Hiba az átjátszó hozzáadásakor"; + /* alert title */ "Error adding server" = "Hiba történt a kiszolgáló hozzáadásakor"; @@ -2261,6 +2392,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Hiba történt a cím létrehozásakor"; +/* alert title */ +"Error creating channel" = "Hiba a csatorna létrehozásakor"; + /* No comment provided by engineer. */ "Error creating group" = "Hiba történt a csoport létrehozásakor"; @@ -2366,6 +2500,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Hiba történt a statisztikák visszaállításakor"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Hiba a csatornaprofil mentésekor"; + /* alert title */ "Error saving chat list" = "Hiba történt a csevegési lista mentésekor"; @@ -2450,6 +2587,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Hiba: "; +/* receive error chat item */ +"error: %@" = "hiba: %@"; + /* alert message file error text snd error text */ @@ -2719,6 +2859,9 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Tovább csökkentett akkumulátor-használat"; +/* relay test step */ +"Get link" = "Hivatkozás megtekintése"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Kapjon értesítést, ha megemlítik."; @@ -2767,7 +2910,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "csoport törölve"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Csoporthivatkozás"; /* No comment provided by engineer. */ @@ -2900,7 +3043,7 @@ servers warning */ "Immediately" = "Azonnal"; /* No comment provided by engineer. */ -"Immune to spam" = "Védett a kéretlen tartalommal szemben"; +"Immune to spam" = "Védett a kéretlen tartalmakkal szemben"; /* No comment provided by engineer. */ "Import" = "Importálás"; @@ -3037,7 +3180,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "érvénytelen csevegésadat"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Érvénytelen kapcsolattartási hivatkozás"; /* invalid chat item */ @@ -3058,6 +3201,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Érvénytelen QR-kód"; +/* alert title */ +"Invalid relay address!" = "Érvénytelen az átjátszó címe!"; + +/* alert title */ +"Invalid relay name!" = "Érvénytelen az átjátszó neve!"; + /* No comment provided by engineer. */ "Invalid response" = "Érvénytelen válasz"; @@ -3151,6 +3300,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "Csatlakozás mint: %@"; +/* No comment provided by engineer. */ +"Join channel" = "Csatlakozás a csatornához"; + /* new chat sheet title */ "Join group" = "Csatlakozás a csoporthoz"; @@ -3199,6 +3351,12 @@ servers warning */ /* swipe action */ "Leave" = "Elhagyás"; +/* No comment provided by engineer. */ +"Leave channel" = "Csatorna elhagyása"; + +/* No comment provided by engineer. */ +"Leave channel?" = "Elhagyja a csatornát?"; + /* No comment provided by engineer. */ "Leave chat" = "Csevegés elhagyása"; @@ -3226,6 +3384,9 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Korlátozások"; +/* No comment provided by engineer. */ +"link" = "hivatkozás"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗"; @@ -3400,6 +3561,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Piszkozatok"; +/* No comment provided by engineer. */ +"Message error" = "Üzenethiba"; + /* item status text */ "Message forwarded" = "Továbbított üzenet"; @@ -3598,6 +3762,9 @@ servers warning */ /* delete after time */ "never" = "soha"; +/* No comment provided by engineer. */ +"new" = "új"; + /* token status text */ "New" = "Új"; @@ -3607,6 +3774,9 @@ servers warning */ /* No comment provided by engineer. */ "New chat experience 🎉" = "Új csevegési élmény 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Új csevegési átjátszó"; + /* notification */ "New contact request" = "Új partneri kapcsolatkérés"; @@ -3667,6 +3837,12 @@ servers warning */ /* Authentication unavailable */ "No app password" = "Nincs alkalmazás jelszó"; +/* No comment provided by engineer. */ +"No chat relays" = "Nincsenek csevegési átjátszók"; + +/* servers warning */ +"No chat relays enabled." = "Nincsenek engedélyezve csevegési átjátszók."; + /* No comment provided by engineer. */ "No chats" = "Nincsenek csevegések"; @@ -3766,6 +3942,9 @@ servers warning */ /* No comment provided by engineer. */ "No user identifiers." = "Nincsenek felhasználói azonosítók."; +/* alert title */ +"Not all relays connected" = "Nem minden átjátszó kapcsolódott"; + /* No comment provided by engineer. */ "Not compatible!" = "Nem kompatibilis!"; @@ -3822,7 +4001,7 @@ alert button new chat action */ "Ok" = "Rendben"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "Rendben"; /* No comment provided by engineer. */ @@ -3909,6 +4088,9 @@ new chat action */ /* No comment provided by engineer. */ "Open changes" = "Módosítások megtekintése"; +/* new chat action */ +"Open channel" = "Csatorna megnyitása"; + /* new chat action */ "Open chat" = "Csevegés megnyitása"; @@ -3933,6 +4115,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Átköltöztetés indítása egy másik eszközre"; +/* new chat action */ +"Open new channel" = "Új csatorna megnyitása"; + /* new chat action */ "Open new chat" = "Új csevegés megnyitása"; @@ -3999,9 +4184,15 @@ new chat action */ /* member role */ "owner" = "tulajdonos"; +/* No comment provided by engineer. */ +"Owner" = "Tulajdonos"; + /* feature role */ "owners" = "tulajdonosok"; +/* No comment provided by engineer. */ +"Owners" = "Tulajdonosok"; + /* No comment provided by engineer. */ "Passcode" = "Jelkód"; @@ -4137,6 +4328,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt."; +/* No comment provided by engineer. */ +"Preset relay address" = "Előre beállított átjátszó címe"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Előre beállított átjátszó neve"; + /* No comment provided by engineer. */ "Preset server address" = "Előre beállított kiszolgáló címe"; @@ -4188,6 +4385,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Privát útválasztás időtúllépése"; +/* alert action */ +"Proceed" = "Folytatás"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil és kiszolgálókapcsolatok"; @@ -4203,9 +4403,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Profiltéma"; -/* alert message */ -"Profile update will be sent to your contacts." = "A profilfrissítés el lesz küldve a partnerei számára."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "A hívások kezdeményezése le van tiltva."; @@ -4412,12 +4609,30 @@ swipe action */ /* call status */ "rejected call" = "elutasított hívás"; +/* member role */ +"relay" = "átjátszó"; + +/* No comment provided by engineer. */ +"Relay" = "Átjátszó"; + +/* alert title */ +"Relay address" = "Átjátszó címe"; + +/* alert title */ +"Relay connection failed" = "Nem sikerült kapcsolódni az átjátszóhoz"; + +/* No comment provided by engineer. */ +"Relay link" = "Átjátszóhivatkozás"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "Az átjátszó csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "Az átjátszó megvédi az IP-címét, de megfigyelheti a hívás időtartamát."; +/* No comment provided by engineer. */ +"Relay test failed!" = "Nem sikerült tesztelni az átjátszót!"; + /* alert action */ "Remove" = "Eltávolítás"; @@ -4442,9 +4657,18 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Eltávolítja a jelmondatot a kulcstartóból?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Feliratkozó eltávolítása"; + +/* alert title */ +"Remove subscriber?" = "Eltávolítja a feliratkozót?"; + /* No comment provided by engineer. */ "removed" = "eltávolítva"; +/* receive error chat item */ +"removed (%d attempts)" = "eltávolítva (%d kísérlet)"; + /* rcv group event chat item */ "removed %@" = "eltávolította őt: %@"; @@ -4632,6 +4856,9 @@ chat item action */ /* alert button */ "Save (and notify members)" = "Mentés (és a tagok értesítése)"; +/* alert button */ +"Save (and notify subscribers)" = "Mentés (és a feliratkozók értesítése)"; + /* alert title */ "Save admission settings?" = "Menti a befogadási beállításokat?"; @@ -4647,6 +4874,12 @@ chat item action */ /* No comment provided by engineer. */ "Save and update group profile" = "Mentés és a csoportprofil frissítése"; +/* No comment provided by engineer. */ +"Save channel profile" = "Csatornaprofil mentése"; + +/* alert title */ +"Save channel profile?" = "Menti a csatornaprofilt?"; + /* No comment provided by engineer. */ "Save group profile" = "Csoportprofil mentése"; @@ -4741,7 +4974,7 @@ chat item action */ "Search links" = "Hivatkozások keresése"; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Keresés vagy SimpleX-hivatkozás beillesztése"; +"Search or paste SimpleX link" = "Keressen vagy adjon meg egy SimpleX-hivatkozást"; /* No comment provided by engineer. */ "Search videos" = "Videók keresése"; @@ -4947,6 +5180,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "a kiszolgáló várólista információi: %1$@\n\nutoljára fogadott üzenet: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "A kiszolgáló hitelesítést igényel az átjátszóhoz való kapcsolódáshoz, ellenőrizze a jelszavát."; + /* server test error */ "Server requires authorization to create queues, check password." = "A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze a jelszavát."; @@ -5050,9 +5286,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Cím nyilvános megosztása"; -/* alert title */ -"Share address with contacts?" = "Megosztja a címet a partnereivel?"; - /* No comment provided by engineer. */ "Share from other apps." = "Megosztás más alkalmazásokból."; @@ -5068,6 +5301,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Profil megosztása"; +/* No comment provided by engineer. */ +"Share relay address" = "Átjátszó címének megosztása"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "SimpleX-cím megosztása a közösségi médiában."; @@ -5077,9 +5313,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Megosztás a SimpleXben"; -/* No comment provided by engineer. */ -"Share with contacts" = "Megosztás a partnerekkel"; - /* No comment provided by engineer. */ "Share your address" = "Saját cím megosztása"; @@ -5129,7 +5362,7 @@ chat item action */ "SimpleX Address" = "SimpleX-cím"; /* No comment provided by engineer. */ -"SimpleX address and 1-time links are safe to share via any messenger." = "A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül."; +"SimpleX address and 1-time links are safe to share via any messenger." = "A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó alkalmazáson keresztül."; /* No comment provided by engineer. */ "SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó?"; @@ -5182,6 +5415,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "A SimpleX protokollokat a Trail of Bits auditálta."; +/* simplex link type */ +"SimpleX relay address" = "SimpleX-átjátszó címe"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Egyszerűsített inkognitómód"; @@ -5300,6 +5536,18 @@ report reason */ /* No comment provided by engineer. */ "Subscribed" = "Feliratkozva"; +/* No comment provided by engineer. */ +"Subscriber" = "Feliratkozó"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "A feliratkozó el lesz távolítva a csatornából – ez a művelet nem vonható vissza!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Feliratkozók"; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz.\nAz átjátszó címe ennek az átjátszónak a beállítására szolgált a csatornához."; + /* No comment provided by engineer. */ "Subscription errors" = "Feliratkozási hibák"; @@ -5340,7 +5588,7 @@ report reason */ "Tap Connect to use bot" = "Koppintson a „Kapcsolódás” gombra a bot használatához"; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Koppintson a SimpleX-cím létrehozása menüpontra a későbbi létrehozáshoz."; +"Tap Join channel" = "Koppintson a „Csatlakozás a csatornához” gombra"; /* No comment provided by engineer. */ "Tap Join group" = "Koppintson a „Csatlakozás a csoporthoz” gombra"; @@ -5394,6 +5642,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Értesítések tesztelése"; +/* No comment provided by engineer. */ +"Test relay" = "Átjátszó tesztelése"; + /* No comment provided by engineer. */ "Test server" = "Kiszolgáló tesztelése"; @@ -5421,6 +5672,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "Az alkalmazás úgy védi az adatait, hogy minden egyes beszélgetéshez más-más üzemeltetőt használ."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "Az alkalmazás %lld sikertelen letöltési kísérlet után eltávolította ezt az üzenetet."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról (kivéve .onion) történő letöltések megerősítését."; @@ -5541,6 +5795,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Ez a csoport már nem létezik."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Ez egy csevegési átjátszó címe, nem használható kapcsolódásra."; + +/* new chat action */ +"This is your link for channel %@!" = "Ez a saját hivatkozása a(z) %@ nevű csatornához!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Ez a hivatkozás újabb alkalmazásverziót igényel. Frissítse az alkalmazást vagy kérjen egy kompatibilis hivatkozást a partnerétől."; @@ -5673,6 +5933,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "Feloldja a tag letiltását?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "Az összes feliratkozó számára feloldja a feliratkozó letiltását?"; + /* rcv group event chat item */ "unblocked %@" = "feloldotta %@ letiltását"; @@ -5745,7 +6008,7 @@ server test failure */ /* swipe action */ "Unread" = "Olvasatlan"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Nem támogatott kapcsolattartási hivatkozás"; /* No comment provided by engineer. */ @@ -5763,6 +6026,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "Frissíti a beállításokat?"; +/* rcv group event chat item */ +"updated channel profile" = "frissített csatornaprofil"; + /* No comment provided by engineer. */ "Updated conditions" = "Frissített feltételek"; @@ -5832,6 +6098,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Használat az üzenetekhez"; +/* No comment provided by engineer. */ +"Use for new channels" = "Használat új csatornákhoz"; + /* No comment provided by engineer. */ "Use for new connections" = "Használat új kapcsolatokhoz"; @@ -5856,6 +6125,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Privát útválasztás használata az ismeretlen kiszolgálókhoz."; +/* No comment provided by engineer. */ +"Use relay" = "Átjátszó használata"; + /* No comment provided by engineer. */ "Use server" = "Kiszolgáló használata"; @@ -5898,6 +6170,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Ellenőrzés"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Kód ellenőrzése a számítógépen"; @@ -5919,6 +6194,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Biztonsági kód ellenőrzése"; +/* relay hostname */ +"via %@" = "a következőn keresztül: %@"; + /* No comment provided by engineer. */ "Via browser" = "Böngészőn keresztül"; @@ -5988,6 +6266,12 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "A hangüzenetek le vannak tiltva!"; +/* alert action */ +"Wait" = "Várakozás"; + +/* relay test step */ +"Wait response" = "Várakozás a válaszra"; + /* No comment provided by engineer. */ "waiting for answer…" = "várakozás a válaszra…"; @@ -6156,6 +6440,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "Ön megfigyelő"; +/* No comment provided by engineer. */ +"you are subscriber" = "Ön feliratkozó"; + /* snd group event chat item */ "you blocked %@" = "Ön letiltotta őt: %@"; @@ -6198,6 +6485,9 @@ server test failure */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "A lezárási képernyő értesítési előnézetét az „Értesítések” menüben állíthatja be."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the channel." = "Megoszthat egy hivatkozást vagy egy QR-kódot – bárki képes lesz csatlakozni a csatornához."; + /* No comment provided by engineer. */ "You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoporthivatkozást később törli, akkor nem fogja elveszíteni a csoport meglévő tagjait."; @@ -6237,6 +6527,9 @@ server test failure */ /* snd group event chat item */ "you changed role of %@ to %@" = "Ön a következőre módosította %1$@ szerepkörét: „%2$@”"; +/* No comment provided by engineer. */ +"You connected to the channel via this relay link." = "Ön ezen az átjátszóhivatkozáson keresztül kapcsolódott a csatornához."; + /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nem sikerült ellenőrizni; próbálja meg újra."; @@ -6318,6 +6611,9 @@ server test failure */ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this channel. Chat history will be preserved." = "Ön nem fog több üzenetet kapni ebből a csatornából. A csevegési előzmények megmaradnak."; + /* No comment provided by engineer. */ "You will stop receiving messages from this chat. Chat history will be preserved." = "Nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak."; @@ -6342,6 +6638,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Hívások"; +/* No comment provided by engineer. */ +"Your channel" = "Saját csatorna"; + /* No comment provided by engineer. */ "Your chat database" = "Csevegési adatbázis"; @@ -6396,6 +6695,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Saját profil"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "A(z) **%@** nevű profilja meg lesz osztva a csatorna átjátszóival és feliratkozóival.\nAz átjátszók hozzáférhetnek a csatornaüzenetekhez."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "A(z) **%@** nevű profilja meg lesz osztva."; @@ -6412,7 +6714,13 @@ server test failure */ "Your random profile" = "Véletlenszerű profil"; /* No comment provided by engineer. */ -"Your server address" = "Saját SMP-kiszolgálójának címe"; +"Your relay address" = "Saját átjátszó címe"; + +/* No comment provided by engineer. */ +"Your relay name" = "Saját átjátszó neve"; + +/* No comment provided by engineer. */ +"Your server address" = "Saját SMP-kiszolgáló címe"; /* No comment provided by engineer. */ "Your servers" = "Saját kiszolgálók"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 69045c9c23..0c1bdccfad 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -64,6 +64,9 @@ /* No comment provided by engineer. */ "**Scan / Paste link**: to connect via a link you received." = "**Scansiona / Incolla link**: per connetterti tramite un link che hai ricevuto."; +/* No comment provided by engineer. */ +"**Test relay** to retrieve its name." = "**Prova il relay** per recuperare il suo nome."; + /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi."; @@ -184,15 +187,38 @@ /* integrity error chat item */ "%d skipped message(s)" = "%d messaggio/i saltato/i"; +/* channel subscriber count */ +"%d subscriber" = "%d iscritto"; + +/* channel subscriber count */ +"%d subscribers" = "%d iscritti"; + /* time interval */ "%d weeks" = "%d settimane"; +/* channel creation progress +channel relay bar progress */ +"%d/%d relays active" = "%1$d/%2$d relay attivo/i"; + +/* channel creation progress with errors +channel relay bar */ +"%d/%d relays active, %d failed" = "%1$d/%2$d relay attivo/i, %3$d fallito/i"; + +/* channel subscriber relay bar progress */ +"%d/%d relays connected" = "%1$d/%2$d relay connesso/i"; + +/* channel subscriber relay bar */ +"%d/%d relays connected, %d errors" = "%1$d/%2$d relay connesso/i, %3$d errori"; + /* No comment provided by engineer. */ "%lld" = "%lld"; /* No comment provided by engineer. */ "%lld %@" = "%lld %@"; +/* No comment provided by engineer. */ +"%lld channel events" = "%lld eventi del canale"; + /* No comment provided by engineer. */ "%lld contact(s) selected" = "%lld contatto/i selezionato/i"; @@ -371,6 +397,9 @@ swipe action */ /* alert title */ "Accept member" = "Accetta membro"; +/* No comment provided by engineer. */ +"accepted" = "accettato"; + /* rcv group event chat item */ "accepted %@" = "%@ accettato"; @@ -392,15 +421,15 @@ swipe action */ /* No comment provided by engineer. */ "Acknowledgement errors" = "Errori di riconoscimento"; +/* No comment provided by engineer. */ +"active" = "attivo"; + /* token status text */ "Active" = "Attivo"; /* No comment provided by engineer. */ "Active connections" = "Connessioni attive"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti."; - /* No comment provided by engineer. */ "Add friends" = "Aggiungi amici"; @@ -851,6 +880,9 @@ swipe action */ /* No comment provided by engineer. */ "Block member?" = "Bloccare il membro?"; +/* No comment provided by engineer. */ +"Block subscriber for all?" = "Bloccare l'iscritto per tutti?"; + /* marked deleted chat item preview text */ "blocked" = "bloccato"; @@ -894,10 +926,13 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Both you and your contact can send voice messages." = "Sia tu che il tuo contatto potete inviare messaggi vocali."; +/* compose placeholder for channel owner */ +"Broadcast" = "Trasmetti"; + /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaro, finlandese, tailandese e ucraino - grazie agli utenti e a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Indirizzo di lavoro"; /* No comment provided by engineer. */ @@ -1038,6 +1073,45 @@ set passcode view */ /* chat item text */ "changing address…" = "cambio indirizzo…"; +/* shown as sender role for channel messages */ +"channel" = "canale"; + +/* No comment provided by engineer. */ +"Channel" = "Canale"; + +/* No comment provided by engineer. */ +"Channel display name" = "Nome da mostrare del canale"; + +/* No comment provided by engineer. */ +"Channel full name (optional)" = "Nome completo del canale (facoltativo)"; + +/* No comment provided by engineer. */ +"Channel image" = "Immagine del canale"; + +/* chat link info line */ +"Channel link" = "Link del canale"; + +/* No comment provided by engineer. */ +"Channel profile" = "Profilo del canale"; + +/* No comment provided by engineer. */ +"Channel profile is stored on subscribers' devices and on the chat relays." = "Il profilo del canale è memorizzato sui dispositivi degli iscritti e sui relay di chat."; + +/* snd group event chat item */ +"channel profile updated" = "profilo del canale aggiornato"; + +/* alert message */ +"Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers." = "Il profilo del canale è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato agli iscritti di canale."; + +/* No comment provided by engineer. */ +"Channel will be deleted for all subscribers - this cannot be undone!" = "Il canale verrà eliminato per tutti gli iscritti, non è reversibile!"; + +/* No comment provided by engineer. */ +"Channel will be deleted for you - this cannot be undone!" = "Il canale verrà eliminato per te, non è reversibile!"; + +/* alert message */ +"Channel will start working with %d of %d relays. Proceed?" = "Il canale sarà operativo con %1$d di %2$d relay. Procedere?"; + /* No comment provided by engineer. */ "Chat" = "Chat"; @@ -1089,6 +1163,18 @@ set passcode view */ /* No comment provided by engineer. */ "Chat profile" = "Profilo utente"; +/* No comment provided by engineer. */ +"Chat relay" = "Relay di chat"; + +/* No comment provided by engineer. */ +"Chat relays" = "Relay di chat"; + +/* No comment provided by engineer. */ +"Chat relays forward messages in channels you create." = "I relay di chat inoltrano i messaggi nei canali che crei."; + +/* No comment provided by engineer. */ +"Chat relays forward messages to channel subscribers." = "I relay di chat inoltrano i messaggi agli iscritti del canale."; + /* No comment provided by engineer. */ "Chat theme" = "Tema della chat"; @@ -1119,6 +1205,12 @@ set passcode view */ /* No comment provided by engineer. */ "Check messages when allowed." = "Controlla i messaggi quando consentito."; +/* alert message */ +"Check relay address and try again." = "Controlla l'indirizzo del relay e riprova."; + +/* alert message */ +"Check relay name and try again." = "Controlla il nome del relay e riprova."; + /* alert title */ "Check server address and try again." = "Controlla l'indirizzo del server e riprova."; @@ -1212,6 +1304,9 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Configura server ICE"; +/* No comment provided by engineer. */ +"Configure relays" = "Configura i relay"; + /* No comment provided by engineer. */ "Configure server operators" = "Configura gli operatori dei server"; @@ -1348,7 +1443,7 @@ server test step */ /* alert title */ "Connection error" = "Errore di connessione"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Errore di connessione (AUTH)"; /* chat list item title (it should not be shown */ @@ -1471,9 +1566,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Correggere il nome a %@?"; -/* No comment provided by engineer. */ -"Create" = "Crea"; - /* No comment provided by engineer. */ "Create 1-time link" = "Crea link una tantum"; @@ -1501,6 +1593,12 @@ server test step */ /* No comment provided by engineer. */ "Create profile" = "Crea profilo"; +/* No comment provided by engineer. */ +"Create public channel" = "Crea canale pubblico"; + +/* No comment provided by engineer. */ +"Create public channel (BETA)" = "Crea canale pubblico (BETA)"; + /* server test step */ "Create queue" = "Crea coda"; @@ -1525,6 +1623,9 @@ server test step */ /* No comment provided by engineer. */ "Creating archive link" = "Creazione link dell'archivio"; +/* No comment provided by engineer. */ +"Creating channel" = "Creazione canale"; + /* No comment provided by engineer. */ "Creating link…" = "Creazione link…"; @@ -1630,6 +1731,9 @@ server test step */ /* No comment provided by engineer. */ "Decentralized" = "Decentralizzato"; +/* relay test step */ +"Decode link" = "Decodifica il link"; + /* message decrypt error item */ "Decryption error" = "Errore di decifrazione"; @@ -1671,6 +1775,12 @@ swipe action */ /* No comment provided by engineer. */ "Delete and notify contact" = "Elimina e avvisa il contatto"; +/* No comment provided by engineer. */ +"Delete channel" = "Elimina canale"; + +/* No comment provided by engineer. */ +"Delete channel?" = "Eliminare il canale?"; + /* No comment provided by engineer. */ "Delete chat" = "Elimina chat"; @@ -1774,6 +1884,9 @@ alert button */ /* server test step */ "Delete queue" = "Elimina coda"; +/* No comment provided by engineer. */ +"Delete relay" = "Elimina relay"; + /* No comment provided by engineer. */ "Delete report" = "Elimina la segnalazione"; @@ -1798,6 +1911,9 @@ alert button */ /* copied message info */ "Deleted at: %@" = "Eliminato il: %@"; +/* rcv group event chat item */ +"deleted channel" = "canale eliminato"; + /* rcv direct event chat item */ "deleted contact" = "contatto eliminato"; @@ -2027,18 +2143,24 @@ chat item action */ /* chat item action */ "Edit" = "Modifica"; +/* No comment provided by engineer. */ +"Edit channel profile" = "Modifica profilo canale"; + /* No comment provided by engineer. */ "Edit group profile" = "Modifica il profilo del gruppo"; /* No comment provided by engineer. */ "Empty message!" = "Messaggio vuoto!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Attiva"; /* No comment provided by engineer. */ "Enable (keep overrides)" = "Attiva (mantieni sostituzioni)"; +/* channel creation warning */ +"Enable at least one chat relay in Network & Servers." = "Attiva almeno un relay di chat in \"Rete e server\"."; + /* alert title */ "Enable automatic message deletion?" = "Attivare l'eliminazione automatica dei messaggi?"; @@ -2171,6 +2293,9 @@ chat item action */ /* call status */ "ended call %@" = "chiamata terminata %@"; +/* No comment provided by engineer. */ +"Enter channel name…" = "Inserisci il nome del canale…"; + /* No comment provided by engineer. */ "Enter correct passphrase." = "Inserisci la password giusta."; @@ -2189,6 +2314,9 @@ chat item action */ /* No comment provided by engineer. */ "Enter password above to show!" = "Inserisci la password sopra per mostrare!"; +/* No comment provided by engineer. */ +"Enter relay name…" = "Inserisci il nome del relay…"; + /* No comment provided by engineer. */ "Enter server manually" = "Inserisci il server a mano"; @@ -2207,7 +2335,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "errore"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Errore"; /* No comment provided by engineer. */ @@ -2225,6 +2353,9 @@ chat item action */ /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; +/* alert title */ +"Error adding relay" = "Errore di aggiunta del relay"; + /* alert title */ "Error adding server" = "Errore di aggiunta del server"; @@ -2261,6 +2392,9 @@ chat item action */ /* No comment provided by engineer. */ "Error creating address" = "Errore nella creazione dell'indirizzo"; +/* alert title */ +"Error creating channel" = "Errore di creazione del canale"; + /* No comment provided by engineer. */ "Error creating group" = "Errore nella creazione del gruppo"; @@ -2366,6 +2500,9 @@ chat item action */ /* No comment provided by engineer. */ "Error resetting statistics" = "Errore di azzeramento statistiche"; +/* No comment provided by engineer. */ +"Error saving channel profile" = "Errore di salvataggio del profilo del canale"; + /* alert title */ "Error saving chat list" = "Errore nel salvataggio dell'elenco di chat"; @@ -2450,6 +2587,9 @@ chat item action */ /* No comment provided by engineer. */ "Error: " = "Errore: "; +/* receive error chat item */ +"error: %@" = "errore: %@"; + /* alert message file error text snd error text */ @@ -2719,6 +2859,9 @@ servers warning */ /* No comment provided by engineer. */ "Further reduced battery usage" = "Ulteriore riduzione del consumo della batteria"; +/* relay test step */ +"Get link" = "Ottieni link"; + /* No comment provided by engineer. */ "Get notified when mentioned." = "Ricevi una notifica quando menzionato."; @@ -2767,7 +2910,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "il gruppo è eliminato"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Link del gruppo"; /* No comment provided by engineer. */ @@ -3037,7 +3180,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "dati chat non validi"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Link di connessione non valido"; /* invalid chat item */ @@ -3058,6 +3201,12 @@ servers warning */ /* No comment provided by engineer. */ "Invalid QR code" = "Codice QR non valido"; +/* alert title */ +"Invalid relay address!" = "Indirizzo del relay non valido!"; + +/* alert title */ +"Invalid relay name!" = "Nome del relay non valido!"; + /* No comment provided by engineer. */ "Invalid response" = "Risposta non valida"; @@ -3151,6 +3300,9 @@ servers warning */ /* No comment provided by engineer. */ "Join as %@" = "entra come %@"; +/* No comment provided by engineer. */ +"Join channel" = "Iscriviti al canale"; + /* new chat sheet title */ "Join group" = "Entra nel gruppo"; @@ -3199,6 +3351,12 @@ servers warning */ /* swipe action */ "Leave" = "Esci"; +/* No comment provided by engineer. */ +"Leave channel" = "Esci dal canale"; + +/* No comment provided by engineer. */ +"Leave channel?" = "Uscire dal canale?"; + /* No comment provided by engineer. */ "Leave chat" = "Esci dalla chat"; @@ -3226,6 +3384,9 @@ servers warning */ /* No comment provided by engineer. */ "Limitations" = "Limitazioni"; +/* No comment provided by engineer. */ +"link" = "link"; + /* No comment provided by engineer. */ "Link mobile and desktop apps! 🔗" = "Collega le app mobile e desktop! 🔗"; @@ -3400,6 +3561,9 @@ servers warning */ /* No comment provided by engineer. */ "Message draft" = "Bozza del messaggio"; +/* No comment provided by engineer. */ +"Message error" = "Errore del messaggio"; + /* item status text */ "Message forwarded" = "Messaggio inoltrato"; @@ -3598,6 +3762,9 @@ servers warning */ /* delete after time */ "never" = "mai"; +/* No comment provided by engineer. */ +"new" = "nuovo"; + /* token status text */ "New" = "Nuovo"; @@ -3607,6 +3774,9 @@ servers warning */ /* No comment provided by engineer. */ "New chat experience 🎉" = "Una nuova esperienza di chat 🎉"; +/* No comment provided by engineer. */ +"New chat relay" = "Nuovo relay di chat"; + /* notification */ "New contact request" = "Nuova richiesta di contatto"; @@ -3667,6 +3837,12 @@ servers warning */ /* Authentication unavailable */ "No app password" = "Nessuna password dell'app"; +/* No comment provided by engineer. */ +"No chat relays" = "Nessun relay di chat"; + +/* servers warning */ +"No chat relays enabled." = "Nessun relay di chat attivato."; + /* No comment provided by engineer. */ "No chats" = "Nessuna chat"; @@ -3766,6 +3942,9 @@ servers warning */ /* No comment provided by engineer. */ "No user identifiers." = "Nessun identificatore utente."; +/* alert title */ +"Not all relays connected" = "Non tutti i relay sono connessi"; + /* No comment provided by engineer. */ "Not compatible!" = "Non compatibile!"; @@ -3822,7 +4001,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -3909,6 +4088,9 @@ new chat action */ /* No comment provided by engineer. */ "Open changes" = "Apri le modifiche"; +/* new chat action */ +"Open channel" = "Apri canale"; + /* new chat action */ "Open chat" = "Apri chat"; @@ -3933,6 +4115,9 @@ new chat action */ /* authentication reason */ "Open migration to another device" = "Apri migrazione ad un altro dispositivo"; +/* new chat action */ +"Open new channel" = "Apri un canale nuovo"; + /* new chat action */ "Open new chat" = "Apri una chat nuova"; @@ -3999,9 +4184,15 @@ new chat action */ /* member role */ "owner" = "proprietario"; +/* No comment provided by engineer. */ +"Owner" = "Proprietario"; + /* feature role */ "owners" = "proprietari"; +/* No comment provided by engineer. */ +"Owners" = "Proprietari"; + /* No comment provided by engineer. */ "Passcode" = "Codice di accesso"; @@ -4137,6 +4328,12 @@ new chat action */ /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva la bozza dell'ultimo messaggio, con gli allegati."; +/* No comment provided by engineer. */ +"Preset relay address" = "Indirizzo relay preimpostato"; + +/* No comment provided by engineer. */ +"Preset relay name" = "Nome relay preimpostato"; + /* No comment provided by engineer. */ "Preset server address" = "Indirizzo server preimpostato"; @@ -4188,6 +4385,9 @@ new chat action */ /* alert title */ "Private routing timeout" = "Scadenza dell'instradamento privato"; +/* alert action */ +"Proceed" = "Procedi"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profilo e connessioni al server"; @@ -4203,9 +4403,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Tema del profilo"; -/* alert message */ -"Profile update will be sent to your contacts." = "L'aggiornamento del profilo verrà inviato ai tuoi contatti."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Proibisci le chiamate audio/video."; @@ -4412,12 +4609,30 @@ swipe action */ /* call status */ "rejected call" = "chiamata rifiutata"; +/* member role */ +"relay" = "relay"; + +/* No comment provided by engineer. */ +"Relay" = "Relay"; + +/* alert title */ +"Relay address" = "Indirizzo del relay"; + +/* alert title */ +"Relay connection failed" = "Connessione del relay fallita"; + +/* No comment provided by engineer. */ +"Relay link" = "Link del relay"; + /* No comment provided by engineer. */ "Relay server is only used if necessary. Another party can observe your IP address." = "Il server relay viene usato solo se necessario. Un altro utente può osservare il tuo indirizzo IP."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "Il server relay protegge il tuo indirizzo IP, ma può osservare la durata della chiamata."; +/* No comment provided by engineer. */ +"Relay test failed!" = "Prova del relay fallita!"; + /* alert action */ "Remove" = "Rimuovi"; @@ -4442,9 +4657,18 @@ swipe action */ /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Rimuovere la password dal portachiavi?"; +/* No comment provided by engineer. */ +"Remove subscriber" = "Rimuovi iscritto"; + +/* alert title */ +"Remove subscriber?" = "Rimuovere l'iscritto?"; + /* No comment provided by engineer. */ "removed" = "rimosso"; +/* receive error chat item */ +"removed (%d attempts)" = "rimosso (%d tentativi)"; + /* rcv group event chat item */ "removed %@" = "ha rimosso %@"; @@ -4630,7 +4854,10 @@ chat item action */ "Save (and notify contacts)" = "Salva (e avvisa i contatti)"; /* alert button */ -"Save (and notify members)" = "Salva (e informa i membri)"; +"Save (and notify members)" = "Salva (e avvisa i membri)"; + +/* alert button */ +"Save (and notify subscribers)" = "Salva (e avvisa gli iscritti)"; /* alert title */ "Save admission settings?" = "Salvare le impostazioni di ammissione?"; @@ -4647,6 +4874,12 @@ chat item action */ /* No comment provided by engineer. */ "Save and update group profile" = "Salva e aggiorna il profilo del gruppo"; +/* No comment provided by engineer. */ +"Save channel profile" = "Salva il profilo del canale"; + +/* alert title */ +"Save channel profile?" = "Salva il profilo del canale?"; + /* No comment provided by engineer. */ "Save group profile" = "Salva il profilo del gruppo"; @@ -4947,6 +5180,9 @@ chat item action */ /* queue info */ "server queue info: %@\n\nlast received msg: %@" = "info coda server: %1$@\n\nultimo msg ricevuto: %2$@"; +/* relay test error */ +"Server requires authorization to connect to relay, check password." = "Il server richiede l'autorizzazione per connettersi al relay, controlla la password."; + /* server test error */ "Server requires authorization to create queues, check password." = "Il server richiede l'autorizzazione di creare code, controlla la password."; @@ -5050,9 +5286,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Condividi indirizzo pubblicamente"; -/* alert title */ -"Share address with contacts?" = "Condividere l'indirizzo con i contatti?"; - /* No comment provided by engineer. */ "Share from other apps." = "Condividi da altre app."; @@ -5068,6 +5301,9 @@ chat item action */ /* No comment provided by engineer. */ "Share profile" = "Condividi il profilo"; +/* No comment provided by engineer. */ +"Share relay address" = "Condividi l'indirizzo del relay"; + /* No comment provided by engineer. */ "Share SimpleX address on social media." = "Condividi l'indirizzo SimpleX sui social media."; @@ -5077,9 +5313,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Condividi in SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Condividi con i contatti"; - /* No comment provided by engineer. */ "Share your address" = "Condividi il tuo indirizzo"; @@ -5182,6 +5415,9 @@ chat item action */ /* No comment provided by engineer. */ "SimpleX protocols reviewed by Trail of Bits." = "Protocolli di SimpleX esaminati da Trail of Bits."; +/* simplex link type */ +"SimpleX relay address" = "Indirizzo del relay SimpleX"; + /* No comment provided by engineer. */ "Simplified incognito mode" = "Modalità incognito semplificata"; @@ -5298,7 +5534,19 @@ report reason */ "Submit" = "Invia"; /* No comment provided by engineer. */ -"Subscribed" = "Iscritto"; +"Subscribed" = "Iscritto/a"; + +/* No comment provided by engineer. */ +"Subscriber" = "Iscritto"; + +/* alert message */ +"Subscriber will be removed from channel - this cannot be undone!" = "L'iscritto verrà rimosso dal canale, non è reversibile!"; + +/* No comment provided by engineer. */ +"Subscribers" = "Iscritti"; + +/* No comment provided by engineer. */ +"Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Gli iscritti usano il link del relay per connettersi al canale.\nL'indirizzo del relay è stato usato per impostare questo relay per il canale."; /* No comment provided by engineer. */ "Subscription errors" = "Errori di iscrizione"; @@ -5340,7 +5588,7 @@ report reason */ "Tap Connect to use bot" = "Tocca Connetti per usare il bot"; /* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Tocca Crea indirizzo SimpleX nel menu per crearlo più tardi."; +"Tap Join channel" = "Tocca Iscriviti al canale"; /* No comment provided by engineer. */ "Tap Join group" = "Tocca Entra nel gruppo"; @@ -5394,6 +5642,9 @@ server test failure */ /* No comment provided by engineer. */ "Test notifications" = "Prova le notifiche"; +/* No comment provided by engineer. */ +"Test relay" = "Prova relay"; + /* No comment provided by engineer. */ "Test server" = "Prova server"; @@ -5421,6 +5672,9 @@ server test failure */ /* No comment provided by engineer. */ "The app protects your privacy by using different operators in each conversation." = "L'app protegge la tua privacy usando diversi operatori in ogni conversazione."; +/* No comment provided by engineer. */ +"The app removed this message after %lld attempts to receive it." = "L'app ha rimosso questo messaggio dopo %lld tentativi di riceverlo."; + /* No comment provided by engineer. */ "The app will ask to confirm downloads from unknown file servers (except .onion)." = "L'app chiederà di confermare i download da server di file sconosciuti (eccetto .onion)."; @@ -5541,6 +5795,12 @@ server test failure */ /* No comment provided by engineer. */ "This group no longer exists." = "Questo gruppo non esiste più."; +/* alert message */ +"This is a chat relay address, it cannot be used to connect." = "Questo è un indirizzo di relay di chat, non può essere usato per connettersi."; + +/* new chat action */ +"This is your link for channel %@!" = "Questo è il tuo link per il canale %@!"; + /* No comment provided by engineer. */ "This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Questo link richiede una versione più recente dell'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile."; @@ -5673,6 +5933,9 @@ server test failure */ /* No comment provided by engineer. */ "Unblock member?" = "Sbloccare il membro?"; +/* No comment provided by engineer. */ +"Unblock subscriber for all?" = "Sbloccare l'iscritto per tutti?"; + /* rcv group event chat item */ "unblocked %@" = "ha sbloccato %@"; @@ -5745,7 +6008,7 @@ server test failure */ /* swipe action */ "Unread" = "Non letto"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Link di connessione non supportato"; /* No comment provided by engineer. */ @@ -5763,6 +6026,9 @@ server test failure */ /* No comment provided by engineer. */ "Update settings?" = "Aggiornare le impostazioni?"; +/* rcv group event chat item */ +"updated channel profile" = "profilo del canale aggiornato"; + /* No comment provided by engineer. */ "Updated conditions" = "Condizioni aggiornate"; @@ -5832,6 +6098,9 @@ server test failure */ /* No comment provided by engineer. */ "Use for messages" = "Usa per i messaggi"; +/* No comment provided by engineer. */ +"Use for new channels" = "Usa per canali nuovi"; + /* No comment provided by engineer. */ "Use for new connections" = "Usa per connessioni nuove"; @@ -5856,6 +6125,9 @@ server test failure */ /* No comment provided by engineer. */ "Use private routing with unknown servers." = "Usa l'instradamento privato con server sconosciuti."; +/* No comment provided by engineer. */ +"Use relay" = "Usa relay"; + /* No comment provided by engineer. */ "Use server" = "Usa il server"; @@ -5898,6 +6170,9 @@ server test failure */ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* relay test step */ +"Verify" = "Verifica"; + /* No comment provided by engineer. */ "Verify code with desktop" = "Verifica il codice con il desktop"; @@ -5919,6 +6194,9 @@ server test failure */ /* No comment provided by engineer. */ "Verify security code" = "Verifica codice di sicurezza"; +/* relay hostname */ +"via %@" = "via %@"; + /* No comment provided by engineer. */ "Via browser" = "Via browser"; @@ -5988,6 +6266,12 @@ server test failure */ /* No comment provided by engineer. */ "Voice messages prohibited!" = "Messaggi vocali vietati!"; +/* alert action */ +"Wait" = "Attendi"; + +/* relay test step */ +"Wait response" = "Attendi risposta"; + /* No comment provided by engineer. */ "waiting for answer…" = "in attesa di risposta…"; @@ -6156,6 +6440,9 @@ server test failure */ /* No comment provided by engineer. */ "you are observer" = "sei un osservatore"; +/* No comment provided by engineer. */ +"you are subscriber" = "sei iscritto/a"; + /* snd group event chat item */ "you blocked %@" = "hai bloccato %@"; @@ -6198,6 +6485,9 @@ server test failure */ /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Puoi impostare l'anteprima della notifica nella schermata di blocco tramite le impostazioni."; +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the channel." = "Puoi condividere un link o un codice QR, chiunque sarà in grado di iscriversi al canale."; + /* No comment provided by engineer. */ "You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Puoi condividere un link o un codice QR: chiunque potrà unirsi al gruppo. Non perderai i membri del gruppo se in seguito lo elimini."; @@ -6237,6 +6527,9 @@ server test failure */ /* snd group event chat item */ "you changed role of %@ to %@" = "hai cambiato il ruolo di %1$@ in %2$@"; +/* No comment provided by engineer. */ +"You connected to the channel via this relay link." = "Ti sei connesso/a al canale attraverso questo link del relay."; + /* No comment provided by engineer. */ "You could not be verified; please try again." = "Non è stato possibile verificarti, riprova."; @@ -6318,6 +6611,9 @@ server test failure */ /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "Continuerai a ricevere chiamate e notifiche da profili silenziati quando sono attivi."; +/* No comment provided by engineer. */ +"You will stop receiving messages from this channel. Chat history will be preserved." = "Smetterai di ricevere messaggi da questo canale. La cronologia della chat sarà preservata."; + /* No comment provided by engineer. */ "You will stop receiving messages from this chat. Chat history will be preserved." = "Non riceverai più messaggi da questa chat. La cronologia della chat verrà conservata."; @@ -6342,6 +6638,9 @@ server test failure */ /* No comment provided by engineer. */ "Your calls" = "Le tue chiamate"; +/* No comment provided by engineer. */ +"Your channel" = "Il tuo canale"; + /* No comment provided by engineer. */ "Your chat database" = "Il tuo database della chat"; @@ -6396,6 +6695,9 @@ server test failure */ /* No comment provided by engineer. */ "Your profile" = "Il tuo profilo"; +/* No comment provided by engineer. */ +"Your profile **%@** will be shared with channel relays and subscribers.\nRelays can access channel messages." = "Il tuo profilo **%@** verrà condiviso con i relay e gli iscritti.\nI relay hanno accesso ai messaggi del canale."; + /* No comment provided by engineer. */ "Your profile **%@** will be shared." = "Verrà condiviso il tuo profilo **%@**."; @@ -6411,6 +6713,12 @@ server test failure */ /* No comment provided by engineer. */ "Your random profile" = "Il tuo profilo casuale"; +/* No comment provided by engineer. */ +"Your relay address" = "L'indirizzo del tuo relay"; + +/* No comment provided by engineer. */ +"Your relay name" = "Il nome del tuo relay"; + /* No comment provided by engineer. */ "Your server address" = "L'indirizzo del tuo server"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 38f90a2cad..ace0f7a227 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -377,9 +377,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "アクティブな接続"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。"; - /* No comment provided by engineer. */ "Add friends" = "友達を追加"; @@ -969,7 +966,7 @@ server test step */ /* alert title */ "Connection error" = "接続エラー"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "接続エラー (AUTH)"; /* chat list item title (it should not be shown */ @@ -1026,9 +1023,6 @@ server test step */ /* No comment provided by engineer. */ "Core version: v%@" = "コアのバージョン: v%@"; -/* No comment provided by engineer. */ -"Create" = "作成"; - /* server test step */ "Create file" = "ファイルを作成"; @@ -1386,7 +1380,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "グループのプロフィールを編集"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "有効"; /* No comment provided by engineer. */ @@ -1518,7 +1512,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "エラー"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "エラー"; /* No comment provided by engineer. */ @@ -1788,7 +1782,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "グループ招待が無効となり、送信元によって取り消されました。"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "グループのリンク"; /* No comment provided by engineer. */ @@ -1974,7 +1968,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "無効なチャットデータ"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "無効な接続リンク"; /* invalid chat item */ @@ -2579,9 +2573,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "プロフィールのパスワード"; -/* alert message */ -"Profile update will be sent to your contacts." = "連絡先にプロフィール更新のお知らせが届きます。"; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "音声/ビデオ通話を禁止する 。"; @@ -2979,15 +2970,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "アドレスを共有する"; -/* alert title */ -"Share address with contacts?" = "アドレスを連絡先と共有しますか?"; - /* No comment provided by engineer. */ "Share link" = "リンクを送る"; -/* No comment provided by engineer. */ -"Share with contacts" = "連絡先と共有する"; - /* No comment provided by engineer. */ "Show calls in phone history" = "通話履歴を表示"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index d4ace22665..b4bc7cfca7 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -395,9 +395,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Actieve verbindingen"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden."; - /* No comment provided by engineer. */ "Add friends" = "Vrienden toevoegen"; @@ -867,7 +864,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaars, Fins, Thais en Oekraïens - dankzij de gebruikers en [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Zakelijk adres"; /* No comment provided by engineer. */ @@ -1306,7 +1303,7 @@ server test step */ /* alert title */ "Connection error" = "Verbindingsfout"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Verbindingsfout (AUTH)"; /* chat list item title (it should not be shown */ @@ -1420,9 +1417,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Juiste naam voor %@?"; -/* No comment provided by engineer. */ -"Create" = "Maak"; - /* No comment provided by engineer. */ "Create 1-time link" = "Eenmalige link maken"; @@ -1964,7 +1958,7 @@ chat item action */ /* No comment provided by engineer. */ "Edit group profile" = "Groep profiel bewerken"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Inschakelen"; /* No comment provided by engineer. */ @@ -2135,7 +2129,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "fout"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Fout"; /* No comment provided by engineer. */ @@ -2655,7 +2649,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "groep is verwijderd"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Groep link"; /* No comment provided by engineer. */ @@ -2916,7 +2910,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "ongeldige gesprek gegevens"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Ongeldige verbinding link"; /* invalid chat item */ @@ -3662,7 +3656,7 @@ alert button new chat action */ "Ok" = "OK"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -4010,9 +4004,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Profiel thema"; -/* alert message */ -"Profile update will be sent to your contacts." = "Profiel update wordt naar uw contacten verzonden."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Audio/video gesprekken verbieden."; @@ -4797,9 +4788,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Adres openbaar delen"; -/* alert title */ -"Share address with contacts?" = "Adres delen met contacten?"; - /* No comment provided by engineer. */ "Share from other apps." = "Delen vanuit andere apps."; @@ -4818,9 +4806,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Delen op SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Delen met contacten"; - /* No comment provided by engineer. */ "Short link" = "Korte link"; @@ -5062,9 +5047,6 @@ report reason */ /* No comment provided by engineer. */ "Tap button " = "Tik op de knop "; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Tik op SimpleX-adres maken in het menu om het later te maken."; - /* No comment provided by engineer. */ "Tap to activate profile." = "Tik hier om profiel te activeren."; @@ -5441,7 +5423,7 @@ server test failure */ /* swipe action */ "Unread" = "Ongelezen"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Niet-ondersteunde verbindingslink"; /* No comment provided by engineer. */ diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 7c28c9591c..d0962a036a 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -398,9 +398,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Aktywne połączenia"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; - /* No comment provided by engineer. */ "Add friends" = "Dodaj znajomych"; @@ -897,7 +894,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bułgarski, fiński, tajski i ukraiński – dzięki użytkownikom i [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Adres firmowy"; /* No comment provided by engineer. */ @@ -1348,7 +1345,7 @@ server test step */ /* alert title */ "Connection error" = "Błąd połączenia"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Błąd połączenia (UWIERZYTELNIANIE)"; /* chat list item title (it should not be shown */ @@ -1471,9 +1468,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Poprawić imię na %@?"; -/* No comment provided by engineer. */ -"Create" = "Utwórz"; - /* No comment provided by engineer. */ "Create 1-time link" = "Utwórz jednorazowy link"; @@ -2033,7 +2027,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "Pusta wiadomość!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Włącz"; /* No comment provided by engineer. */ @@ -2207,7 +2201,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "błąd"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Błąd"; /* No comment provided by engineer. */ @@ -2767,7 +2761,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "grupa została usunięta"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Link do grupy"; /* No comment provided by engineer. */ @@ -3037,7 +3031,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "nieprawidłowe dane czatu"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Nieprawidłowy link połączenia"; /* invalid chat item */ @@ -3822,7 +3816,7 @@ alert button new chat action */ "Ok" = "Ok"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -4203,9 +4197,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Motyw profilu"; -/* alert message */ -"Profile update will be sent to your contacts." = "Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Zabroń połączeń audio/wideo."; @@ -5050,9 +5041,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Udostępnij adres publicznie"; -/* alert title */ -"Share address with contacts?" = "Udostępnić adres kontaktom?"; - /* No comment provided by engineer. */ "Share from other apps." = "Udostępnij z innych aplikacji."; @@ -5077,9 +5065,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Udostępnij do SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Udostępnij kontaktom"; - /* No comment provided by engineer. */ "Share your address" = "Udostępnij swój adres"; @@ -5339,9 +5324,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to use bot" = "Dotknij Połącz aby użyć bota"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Dotknij Stwórz adres SimpleX w menu aby utworzyć go później."; - /* No comment provided by engineer. */ "Tap Join group" = "Dotknij Dołącz do grupy"; @@ -5745,7 +5727,7 @@ server test failure */ /* swipe action */ "Unread" = "Nieprzeczytane"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Nieobsługiwane łącze połączenia"; /* No comment provided by engineer. */ diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index eee33cc574..f06ba61b27 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -398,9 +398,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Активные соединения"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам."; - /* No comment provided by engineer. */ "Add friends" = "Добавить друзей"; @@ -891,7 +888,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Болгарский, финский, тайский и украинский - благодаря пользователям и [Weblate] (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Бизнес адрес"; /* No comment provided by engineer. */ @@ -1342,7 +1339,7 @@ server test step */ /* alert title */ "Connection error" = "Ошибка соединения"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Ошибка соединения (AUTH)"; /* chat list item title (it should not be shown */ @@ -1462,9 +1459,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Исправить имя на %@?"; -/* No comment provided by engineer. */ -"Create" = "Создать"; - /* No comment provided by engineer. */ "Create 1-time link" = "Создать одноразовую ссылку"; @@ -2018,7 +2012,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "Пустое сообщение!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Включить"; /* No comment provided by engineer. */ @@ -2192,7 +2186,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "ошибка"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Ошибка"; /* No comment provided by engineer. */ @@ -2746,7 +2740,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "группа удалена"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Ссылка группы"; /* No comment provided by engineer. */ @@ -3010,7 +3004,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "ошибка данных чата"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Ошибка в ссылке контакта"; /* invalid chat item */ @@ -3786,7 +3780,7 @@ alert button new chat action */ "Ok" = "Ок"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "OK"; /* No comment provided by engineer. */ @@ -4167,9 +4161,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Тема профиля"; -/* alert message */ -"Profile update will be sent to your contacts." = "Обновлённый профиль будет отправлен Вашим контактам."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Запретить аудио/видео звонки."; @@ -4996,9 +4987,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Поделитесь адресом"; -/* alert title */ -"Share address with contacts?" = "Поделиться адресом с контактами?"; - /* No comment provided by engineer. */ "Share from other apps." = "Поделитесь из других приложений."; @@ -5023,9 +5011,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Поделиться в SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Поделиться с контактами"; - /* No comment provided by engineer. */ "Share your address" = "Поделитесь Вашим адресом"; @@ -5285,9 +5270,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to use bot" = "Нажмите Соединиться, чтобы использовать бот"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Нажмите Создать адрес SimpleX в меню, чтобы создать его позже."; - /* No comment provided by engineer. */ "Tap Join group" = "Нажмите Вступить в группу"; @@ -5691,7 +5673,7 @@ server test failure */ /* swipe action */ "Unread" = "Не прочитано"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Ссылка не поддерживается"; /* No comment provided by engineer. */ diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index ffdd2006a6..925fbdc9df 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -233,9 +233,6 @@ swipe action */ /* call status */ "accepted call" = "รับสายแล้ว"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ"; - /* No comment provided by engineer. */ "Add profile" = "เพิ่มโปรไฟล์"; @@ -672,7 +669,7 @@ server test step */ /* alert title */ "Connection error" = "การเชื่อมต่อผิดพลาด"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "การเชื่อมต่อผิดพลาด (AUTH)"; /* chat list item title (it should not be shown */ @@ -726,9 +723,6 @@ server test step */ /* No comment provided by engineer. */ "Core version: v%@" = "รุ่นหลัก: v%@"; -/* No comment provided by engineer. */ -"Create" = "สร้าง"; - /* server test step */ "Create file" = "สร้างไฟล์"; @@ -1056,7 +1050,7 @@ alert button */ /* No comment provided by engineer. */ "Edit group profile" = "แก้ไขโปรไฟล์กลุ่ม"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "เปิดใช้งาน"; /* No comment provided by engineer. */ @@ -1182,7 +1176,7 @@ alert button */ /* No comment provided by engineer. */ "error" = "ผิดพลาด"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "ผิดพลาด"; /* No comment provided by engineer. */ @@ -1449,7 +1443,7 @@ server test error */ /* No comment provided by engineer. */ "Group invitation is no longer valid, it was removed by sender." = "คำเชิญเข้าร่วมกลุ่มใช้ไม่ถูกต้องอีกต่อไป คำเชิญถูกลบโดยผู้ส่ง"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "ลิงค์กลุ่ม"; /* No comment provided by engineer. */ @@ -1632,7 +1626,7 @@ server test error */ /* No comment provided by engineer. */ "invalid chat data" = "ข้อมูลแชทไม่ถูกต้อง"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "ลิงค์เชื่อมต่อไม่ถูกต้อง"; /* invalid chat item */ @@ -2213,9 +2207,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile password" = "รหัสผ่านโปรไฟล์"; -/* alert message */ -"Profile update will be sent to your contacts." = "การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ"; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "ห้ามการโทรด้วยเสียง/วิดีโอ"; @@ -2625,15 +2616,9 @@ chat item action */ /* No comment provided by engineer. */ "Share address" = "แชร์ที่อยู่"; -/* alert title */ -"Share address with contacts?" = "แชร์ที่อยู่กับผู้ติดต่อ?"; - /* No comment provided by engineer. */ "Share link" = "แชร์ลิงก์"; -/* No comment provided by engineer. */ -"Share with contacts" = "แชร์กับผู้ติดต่อ"; - /* No comment provided by engineer. */ "Show calls in phone history" = "แสดงการโทรในประวัติการโทร"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 30d83d16dd..5da3bf688a 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -398,9 +398,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Aktif bağlantılar"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek."; - /* No comment provided by engineer. */ "Add friends" = "Arkadaş ekle"; @@ -891,7 +888,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgarca, Fince, Tayca ve Ukraynaca - kullanıcılara ve [Weblate] e teşekkürler! (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "İş adresi"; /* No comment provided by engineer. */ @@ -1342,7 +1339,7 @@ server test step */ /* alert title */ "Connection error" = "Bağlantı hatası"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Bağlantı hatası (DOĞRULAMA)"; /* chat list item title (it should not be shown */ @@ -1462,9 +1459,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "İsim %@ olarak düzeltilsin mi?"; -/* No comment provided by engineer. */ -"Create" = "Oluştur"; - /* No comment provided by engineer. */ "Create 1-time link" = "Tek kullanımlık bağlantı oluştur"; @@ -2018,7 +2012,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "Boş mesaj!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Etkinleştir"; /* No comment provided by engineer. */ @@ -2192,7 +2186,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "hata"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Hata"; /* No comment provided by engineer. */ @@ -2730,7 +2724,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "grup silindi"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Grup bağlantısı"; /* No comment provided by engineer. */ @@ -2994,7 +2988,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "geçersi̇z sohbet verisi"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Geçersiz bağlanma bağlantısı"; /* invalid chat item */ @@ -3767,7 +3761,7 @@ alert button new chat action */ "Ok" = "Tamam"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "TAMAM"; /* No comment provided by engineer. */ @@ -4148,9 +4142,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Profil teması"; -/* alert message */ -"Profile update will be sent to your contacts." = "Profil güncellemesi kişilerinize gönderilecektir."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Sesli/görüntülü aramaları yasakla."; @@ -4977,9 +4968,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Adresinizi herkese açık olarak paylaşın"; -/* alert title */ -"Share address with contacts?" = "Kişilerle adres paylaşılsın mı?"; - /* No comment provided by engineer. */ "Share from other apps." = "Diğer uygulamalardan paylaşın."; @@ -5004,9 +4992,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "SimpleX ile paylaş"; -/* No comment provided by engineer. */ -"Share with contacts" = "Kişilerle paylaş"; - /* No comment provided by engineer. */ "Share your address" = "Adresini paylaş"; @@ -5266,9 +5251,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to use bot" = "Botu kullanmak için Bağlan tuşuna bas"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Daha sonra oluşturmak için menüden BasitX adresi oluştur'a dokunun."; - /* No comment provided by engineer. */ "Tap Join group" = "Gruba katıl'a dokunun"; @@ -5669,7 +5651,7 @@ server test failure */ /* swipe action */ "Unread" = "Okunmamış"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Desteklenmeyen bağlantı bağlantısı"; /* No comment provided by engineer. */ diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index bf86d6a339..6b6604016c 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -398,9 +398,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "Активні з'єднання"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам."; - /* No comment provided by engineer. */ "Add friends" = "Додайте друзів"; @@ -879,7 +876,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Болгарською, фінською, тайською та українською мовами - завдяки користувачам та [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "Адреса підприємства"; /* No comment provided by engineer. */ @@ -1330,7 +1327,7 @@ server test step */ /* alert title */ "Connection error" = "Помилка підключення"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "Помилка підключення (AUTH)"; /* chat list item title (it should not be shown */ @@ -1447,9 +1444,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "Виправити ім'я на %@?"; -/* No comment provided by engineer. */ -"Create" = "Створити"; - /* No comment provided by engineer. */ "Create 1-time link" = "Створити одноразове посилання"; @@ -2000,7 +1994,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "Порожнє повідомлення!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "Увімкнути"; /* No comment provided by engineer. */ @@ -2174,7 +2168,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "помилка"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "Помилка"; /* No comment provided by engineer. */ @@ -2706,7 +2700,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "групу видалено"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "Посилання на групу"; /* No comment provided by engineer. */ @@ -2970,7 +2964,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "невірні дані чату"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "Неправильне посилання для підключення"; /* invalid chat item */ @@ -3737,7 +3731,7 @@ alert button new chat action */ "Ok" = "Гаразд"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "ОК"; /* No comment provided by engineer. */ @@ -4103,9 +4097,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "Тема профілю"; -/* alert message */ -"Profile update will be sent to your contacts." = "Оновлення профілю буде надіслано вашим контактам."; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "Заборонити аудіо/відеодзвінки."; @@ -4923,9 +4914,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "Поділіться адресою публічно"; -/* alert title */ -"Share address with contacts?" = "Поділіться адресою з контактами?"; - /* No comment provided by engineer. */ "Share from other apps." = "Діліться з інших програм."; @@ -4950,9 +4938,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "Поділіться з SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "Поділіться з контактами"; - /* No comment provided by engineer. */ "Share your address" = "Поділіться своєю адресою"; @@ -5209,9 +5194,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to send request" = "Натисніть Підключитися, щоб відправити запит"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше."; - /* No comment provided by engineer. */ "Tap Join group" = "Натисніть Приєднатися до групи"; @@ -5606,7 +5588,7 @@ server test failure */ /* swipe action */ "Unread" = "Непрочитане"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "Несумісне посилання для підключення"; /* No comment provided by engineer. */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 43a0f665e4..ca1eb754ef 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -395,9 +395,6 @@ swipe action */ /* No comment provided by engineer. */ "Active connections" = "活动连接"; -/* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。"; - /* No comment provided by engineer. */ "Add friends" = "添加好友"; @@ -894,7 +891,7 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "保加利亚语、芬兰语、泰语和乌克兰语——感谢用户和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; -/* No comment provided by engineer. */ +/* chat link info line */ "Business address" = "企业地址"; /* No comment provided by engineer. */ @@ -1345,7 +1342,7 @@ server test step */ /* alert title */ "Connection error" = "连接错误"; -/* No comment provided by engineer. */ +/* conn error description */ "Connection error (AUTH)" = "连接错误(AUTH)"; /* chat list item title (it should not be shown */ @@ -1465,9 +1462,6 @@ server test step */ /* alert message */ "Correct name to %@?" = "将名称更正为 %@?"; -/* No comment provided by engineer. */ -"Create" = "创建"; - /* No comment provided by engineer. */ "Create 1-time link" = "创建一次性链接"; @@ -2024,7 +2018,7 @@ chat item action */ /* No comment provided by engineer. */ "Empty message!" = "空消息!"; -/* No comment provided by engineer. */ +/* alert button */ "Enable" = "启用"; /* No comment provided by engineer. */ @@ -2198,7 +2192,7 @@ chat item action */ /* No comment provided by engineer. */ "error" = "错误"; -/* No comment provided by engineer. */ +/* conn error description */ "Error" = "错误"; /* No comment provided by engineer. */ @@ -2752,7 +2746,7 @@ servers warning */ /* No comment provided by engineer. */ "group is deleted" = "群被删除了"; -/* No comment provided by engineer. */ +/* chat link info line */ "Group link" = "群组链接"; /* No comment provided by engineer. */ @@ -3019,7 +3013,7 @@ servers warning */ /* No comment provided by engineer. */ "invalid chat data" = "无效聊天数据"; -/* No comment provided by engineer. */ +/* conn error description */ "Invalid connection link" = "无效的连接链接"; /* invalid chat item */ @@ -3801,7 +3795,7 @@ alert button new chat action */ "Ok" = "好的"; -/* No comment provided by engineer. */ +/* alert button */ "OK" = "好的"; /* No comment provided by engineer. */ @@ -4179,9 +4173,6 @@ new chat action */ /* No comment provided by engineer. */ "Profile theme" = "个人资料主题"; -/* alert message */ -"Profile update will be sent to your contacts." = "个人资料更新将被发送给您的联系人。"; - /* No comment provided by engineer. */ "Prohibit audio/video calls." = "禁止音频/视频通话。"; @@ -5020,9 +5011,6 @@ chat item action */ /* No comment provided by engineer. */ "Share address publicly" = "公开分享地址"; -/* alert title */ -"Share address with contacts?" = "与联系人分享地址?"; - /* No comment provided by engineer. */ "Share from other apps." = "从其他应用程序共享。"; @@ -5047,9 +5035,6 @@ chat item action */ /* No comment provided by engineer. */ "Share to SimpleX" = "分享到 SimpleX"; -/* No comment provided by engineer. */ -"Share with contacts" = "与联系人分享"; - /* No comment provided by engineer. */ "Share your address" = "分享地址"; @@ -5306,9 +5291,6 @@ report reason */ /* No comment provided by engineer. */ "Tap Connect to use bot" = "轻按“连接”使用机器人"; -/* No comment provided by engineer. */ -"Tap Create SimpleX address in the menu to create it later." = "要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址”"; - /* No comment provided by engineer. */ "Tap Join group" = "轻按加入群"; @@ -5703,7 +5685,7 @@ server test failure */ /* swipe action */ "Unread" = "未读"; -/* No comment provided by engineer. */ +/* conn error description */ "Unsupported connection link" = "不支持的连接链接"; /* No comment provided by engineer. */ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 3045f3035d..cb92b386a0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -26,7 +26,7 @@ خوادم الاتصالات الجديدة لملف تعريف الدردشة الحالي الخاص بك سيتم تغيير عنوان الاستلام إلى خادم مختلف. سيتم إكمال تغيير العنوان بعد اتصال المرسل بالإنترنت. هذا الرابط ليس رابط اتصال صالح! - يسمح + اسمح أضِف خوادم مُعدة مسبقًا أضِف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! @@ -106,7 +106,7 @@ تجزئة رسالة سيئة معرّف رسالة سيئ انتهت المكالمة - تغير + غيِّر لون إضافي ثانوي " \nمتوفر في v5.1" @@ -122,7 +122,7 @@ 1 دقيقة 30 ثانية ألغِ الرسالة الحيّة - إلغاء + ألغِ لكل جهة اتصال وعضو في المجموعة\n. الرجاء ملاحظة: إذا كان لديك العديد من الاتصالات، فقد يكون استهلاك البطارية وحركة المرور أعلى بكثير وقد تفشل بعض الاتصالات.]]> جارٍ الاتصال… مكالمة صوتية @@ -162,19 +162,19 @@ مفعّل مفعّلة لك يمكن لجهات الاتصال تحديد الرسائل لحذفها؛ ستتمكن من مشاهدتها. - جار الاتصال… + يتصل… خطأ في الإتصال (المصادقة) خطأ في حذف جهة الاتصال جهة الاتصال مخفية: - نسخ + انسخ اتصل متصل انضمام إلى المجموعة؟ اتصل عبر رابط لمرة واحدة؟ تغيير عنوان الاستلام؟ نٌسخت إلى الحافظة - مسح - امسح الدردشة + امحُ + امحُ الدردشة أنشئ عنوان الدردشات تأكيد عبارة المرور الجديدة… @@ -199,19 +199,19 @@ تأكد من بيانات اعتمادك أنشئ عنوان SimpleX متابعة - تحدث مع المطورين + دردش مع المطوِّرين سياق الأيقونة إحباط تغيير العنوان؟ إحباط سيتم إحباط تغيير العنوان. سيتم استخدام عنوان الاستلام القديم. - مسح الدردشة؟ + محو الدردشة؟ وحدة تحكم الدردشة ضبط خوادم ICE الاتصال ملف تعريف الدردشة الإصدار الأساسي: v%s أنشئ ملف تعريف - جار الاتصال… + يتصل… انتهى متصل %1$d تخطت الرسائل @@ -221,13 +221,13 @@ خطأ في تعمية قاعدة البيانات توقفت الدردشة مكتمل - جاري الاتصال (أعلن) + يتصل (أُعلن) الاتصال إحباط تغيير العنوان أنشئ مجموعة سرية قارن رموز الأمان مع جهات اتصالك. الواجهة الصينية والاسبانية - مسح + امحُ %1$s يريد التواصل معك عبر جارِ تغيير العنوان… جارِ تغيير العنوان ل%s… @@ -240,17 +240,17 @@ تغيير وضع التدمير الذاتي تغيير رمز المرور التدمير الذاتي تأكيد ترقيات قاعدة البيانات - الاتصال (دعوة مقدمة) - مسح + يتصل (دعوة مقدمة) + امحُ خطأ في إنشاء رابط المجموعة (حاضِر) فعّل أبقِ TCP على قيد الحياة - جار الاتصال… - جار الاتصال… + يتصل… + يتصل… أرسلت طلب الاتصال! حُذفت قاعدة بيانات الدردشة جارِ تغيير العنوان… - جار الاتصال (قُبِل) + يتصل (قُبِل) فُحصت جهة الاتصال %1$s أعضاء أنشئ رابط المجموعة @@ -271,7 +271,7 @@ اتصل عبر عنوان التواصل؟ خطأ في حذف رابط المجموعة التحقق من الرسائل الجديدة كل 10 دقائق لمدة تصل إلى دقيقة واحدة - جار الاتصال + يتصل خطأ خطأ في حذف ملف تعريف المستخدم زر الاغلاق @@ -279,7 +279,7 @@ تغيير الدور أدخل كلمة المرور في البحث تسمح جهة الاتصال - تأكيد + أكِّد جهة الاتصال ليست متصلة بعد! اتصال تواصل عبر الرابط @@ -294,7 +294,7 @@ خطأ في حذف اتصال جهة الاتصال المنتظر أدخل رسالة ترحيب… متصل - جار الاتصال + يتصل مفعّلة للاتصال تغيير رمز المرور متصل @@ -308,18 +308,18 @@ تواصل عبر الرابط / رمز QR أنشئ رابط دعوة لمرة واحدة تحقق من عنوان الخادم وحاول مرة أخرى. - امسح التحقُّق + امحُ التحقُّق أنشئ عنوانًا للسماح للأشخاص بالتواصل معك. أدخل الخادم يدويًا ملون لدى جهة الاتصال التعمية بين الطريفين أنشئ أنشئ ملف تعريفك - مكالمة جارية... + مكالمة جارية فعّل التدمير الذاتي الموافقة على التعمية… الموافقة على التعمية لـ%s… - متصل (مقدم) + يتصل (مقدم) وافق التعمية التعمية نعم التعمية نعم ل%s @@ -335,7 +335,7 @@ احذف جميع الملفات احذف بعد احذف الملف - حذف + احذف حذف رسالة العضو؟ احذف احذف الرسائل @@ -398,7 +398,7 @@ مخصص تخصيص ومشاركة سمات الألوان. الخروج بدون حفظ - أدوات المطور + أدوات المطوِّر احذف قائمة الانتظار خطأ في تحديث خصوصية المستخدم مسح رمز QR.]]> @@ -471,7 +471,7 @@ يمكن للأعضاء إرسال الملفات والوسائط. تفضيلات المجموعة سريع ولا تنتظر حتى يصبح المرسل متصلاً بالإنترنت! - إخفاء + أخفِ كيفية الاستخدام كيف يعمل SimpleX التخفي عبر رابط عنوان جهة الاتصال @@ -602,7 +602,7 @@ مصادقة الجهاز غير مفعّلة. يمكنك تشغيل قفل SimpleX عبر الإعدادات، بمجرد تفعيل مصادقة الجهاز. نزّل الملف عطّل قفل SimpleX - تحرير + حرّر اسم ملف التعريف: البريد الإلكتروني أدخل أسمك: @@ -709,7 +709,7 @@ خطأ في تسليم الرسالة الشبكة والخوادم إشراف - فتح في تطبيق الجوال، ثم انقر فوق اتصال في التطبيق.]]> + فتح في تطبيق الجوال، ثم انقر فوق اتصال في التطبيق.]]> تحت الإشراف في: %s ردود الفعل الرسائل ممنوعة في هذه الدردشة. أُشرف بواسطة %s @@ -798,7 +798,7 @@ جارِ فتح قاعدة البيانات… جهة اتصالك فقط يمكنها إرسال رسائل صوتية. ألصق - لم يُعثر على عبارة المرور في Keystore، يُرجى إدخالها يدويًا. ربما حدث هذا إذا استعدت بيانات التطبيق باستخدام أداة النسخ الاحتياطي. إذا لم يكن الأمر كذلك، يُرجى التواصل مع المطورين. + لم يُعثر على عبارة المرور في Keystore، يُرجى إدخالها يدويًا. ربما حدث هذا إذا استعدت بيانات التطبيق باستخدام أداة النسخ الاحتياطي. إذا لم يكن الأمر كذلك، يُرجى التواصل مع المطوِّرين. افتح الدردشة قد يؤدي فتح الرابط في المتصفح إلى تقليل خصوصية الاتصال وأمانه. ستظهر روابط SimpleX غير الموثوقة باللون الأحمر. أنت فقط يمكنك إضافة ردود الفعل على الرسالة. @@ -816,7 +816,7 @@ مكالمة قيد الانتظار تقوم أجهزة العميل فقط بتخزين ملفات تعريف المستخدمين وجهات الاتصال والمجموعات والرسائل. صفّر الألوان - حفظ + احفظ عنوان الخادم المُعد مسبقًا حفظ وإشعار أعضاء المجموعة دوري @@ -827,18 +827,18 @@ صورة ملف التعريف الإشعارات خاصة يُرجى تخزين عبارة المرور بشكل آمن، فلن تتمكن من الوصول إلى الدردشة إذا فقدتها. - يُرجى تحديث التطبيق والتواصل مع المطورين. + يُرجى تحديث التطبيق والتواصل مع المطوِّرين. دليل المستخدم.]]> غيّر ملفات تعريف الدردشة اسحب الوصول - كشف + اكشف سيتم إيقاف استلام الملف. رفض قيم التطبيق منفذ احفظ إعدادات عنوان SimpleX إعادة تعريف الخصوصية - الرجاء الإبلاغ للمطورين. + يُرجى إبلاغ المطوِّرين بذلك. الخصوصية والأمان أزل إزالة عبارة المرور من Keystore؟ @@ -853,7 +853,7 @@ استلمت إجابة… مستودع GitHub.]]> رفض - يحمي خادم الترحيل عنوان IP الخاص بك، ولكن يمكنه مراقبة مدة المكالمة. + يحمي خادم المُرحل عنوان IP الخاص بك، ولكن يمكنه مراقبة مُدّة المكالمة. الرجاء إدخال كلمة المرور السابقة بعد استعادة نسخة احتياطية لقاعدة البيانات. لا يمكن التراجع عن هذا الإجراء. استعادة النسخة الاحتياطية لقاعدة البيانات؟ حفظ @@ -924,7 +924,7 @@ صفّر المنفذ %d خادم مُعد مسبقًا - يتم استخدام خادم الترحيل فقط إذا لزم الأمر. يمكن لطرف آخر مراقبة عنوان IP الخاص بك. + يُستخدم خادم المُرحل فقط إذا لزم الأمر. يمكن لطرف آخر مراقبة عنوان IP الخاص بك. حفظ وإشعار جهة الاتصال إعادة التشغيل استلمت في: %s @@ -946,7 +946,7 @@ إرسال رسالة إرسال أرسل رسالة حيّة - فشلت تجربة الخادم! + فشل اختبار الخادم! احفظ عبارة المرور في Keystore أرسل رسالة مباشرة إرسال عبر @@ -960,8 +960,8 @@ رسالة مرسلة عيّن تفضيلات المجموعة عيّنها بدلاً من استيثاق النظام. - مشاركة - إرسال + شارك + أرسل احفظ عبارة المرور وافتح الدردشة حدد جهات الاتصال تعيين يوم واحد @@ -1048,7 +1048,7 @@ يبدأ… شُغّل قفل SimpleX أظهر: - أظهر خيارات المطور + أظهر خيارات المطوِّر simplexmq: v%s (%2s) يتطلب الخادم إذنًا لإنشاء قوائم انتظار، تحقق من كلمة المرور. يتطلب الخادم إذنًا للرفع، تحقق من كلمة المرور. @@ -1093,7 +1093,7 @@ اختبر الخوادم لا معرّفات مُستخدم دعم SIMPLEX CHAT - تبديل + بدِّل العنوان الرئيسي سيتم وضع علامة على الرسالة على أنها تحت الإشراف لجميع الأعضاء. انقر للانضمام @@ -1117,7 +1117,7 @@ لاستلام الإشعارات، يُرجى إدخال عبارة مرور قاعدة البيانات مصادقة النظام يعمل التعمية واتفاقية التعمية الجديدة غير مطلوبة. قد ينتج عن ذلك أخطاء في الاتصال! - لا يمكن فك ترميز الصورة. من فضلك، جرب صورة مختلفة أو تواصل مع المطورين. + لا يمكن فك ترميز الصورة. من فضلك، جرّب صورة مختلفة أو تواصل مع المطوِّرين. سيتم حذف الرسالة لجميع الأعضاء. الصور كثيرة! مقاطع الفيديو كثيرة! @@ -1253,13 +1253,13 @@ إلغاء إخفاء ملف تعريف يجب أن تكون جهة الاتصال متصلة بالإنترنت حتى يكتمل الاتصال. \nيمكنك إلغاء هذا الاتصال وإزالة جهة الاتصال (والمحاولة لاحقًا باستخدام رابط جديد). - فتح في تطبيق الجوال.]]> + فتح في تطبيق الجوّال.]]> استخدم للاتصالات الجديدة استخدم الخادم عنوان خادمك قاعدة بيانات دردشتك أنت مدعو إلى المجموعة. انضم للتواصل مع أعضاء المجموعة. - لقد انضممت إلى هذه المجموعة. جارِ الاتصال بدعوة عضو المجموعة. + لقد انضممت إلى هذه المجموعة. يتصل بدعوة عضو المجموعة. غيّرتَ العنوان ل%s إلغاء إخفاء ملف تعريف الدردشة الرسائل الصوتية ممنوعة في هذه الدردشة. @@ -1284,7 +1284,7 @@ مكالمة فيديو الرسائل الصوتية ممنوعة. فتح القفل - رفع الملف + ارفع الملف لا يمكن التحقق منك؛ الرجاء المحاولة مرة اخرى. رسالة صوتية رسالة صوتية… @@ -1292,14 +1292,14 @@ أنت المراقب! تحتاج إلى السماح لجهة اتصالك بإرسال رسائل صوتية لتتمكن من إرسالها. أرسلت جهة اتصالك ملفًا أكبر من الحجم الأقصى المعتمد حاليًا (%1$s). - الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> + الاتصال بمطوِّري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات.]]> خادمك يُخزن ملف تعريفك على جهازك ومشاركته فقط مع جهات اتصالك. لا تستطيع خوادم SimpleX رؤية ملف تعريفك. الفيديو مقفل الفيديو مُشغَّل مع رسالة ترحيب اختيارية. قريباً! - هذه الميزة ليست مدعومة بعد. جرب الإصدار القادم. + هذه الميزة ليست مدعومة بعد. جرّب الإصدار القادم. عطّل (الاحتفاظ بتجاوزات المجموعة) فعِّل لجميع المجموعات إرسال الإيصالات مفعّلة لـ%d مجموعات @@ -1366,7 +1366,7 @@ فعّل وضع التخفي عند الاتصال. أرسل لاتصال طُلب اتصال - جارٍ الاتصال بالفعل! + يتصل بالفعل! مجموعات أفضل و%d أحداث أخرى جارٍ انضمام بالفعل إلى المجموعة! @@ -1388,7 +1388,7 @@ اتصل بنفسك؟ سطح المكتب متصل بسطح المكتب - جار الاتصال بسطح المكتب + الاتصال بسطح المكتب أجهزة سطح المكتب الاسم الصحيح لـ%s؟ حذف %d رسالة؟ @@ -1408,10 +1408,10 @@ مُكتشف عبر الشبكة المحلية قطع اتصال سطح المكتب؟ إصدار تطبيق سطح المكتب %s غير متوافق مع هذا التطبيق. - توسيع + وسِّع هل تريد تكرار طلب الاتصال؟ خطأ في إعادة التفاوض بشأن التعمية - %1$s.]]> + %1$s.]]> خطأ لقد انضممت بالفعل إلى المجموعة عبر هذا الرابط. %s و%s @@ -1429,7 +1429,7 @@ (جديد)]]> فك ربط سطح المكتب؟ خيارات سطح المكتب المرتبطة - لا يمكن فك تشفير الفيديو. من فضلك، جرب مقطع فيديو مختلفًا أو اتصل بالمطورين. + لا يمكن فك ترميز الفيديو. من فضلك، جرّب مقطع فيديو مختلفًا أو اتصل بالمطوِّرين. %s متصل أسطح المكتب المرتبطة مجموعات التخفي @@ -1466,7 +1466,7 @@ تحقق من الرمز على الجوّال أدخل اسم الجهاز هذا… خطأ - لقد شاركت مسار ملف غير صالح. أبلغ عن المشكلة لمطوري التطبيق. + لقد شاركت مسار ملف غير صالح. أبلغ عن المشكلة لمطوِّري التطبيق. اسم غير صالح! ألصق عنوان سطح المكتب %1$s!]]> @@ -1483,13 +1483,13 @@ تحقق من الاتصال أعِد التحميل عشوائي - في انتظار اتصال الجوال: - للسماح لتطبيق الجوال بالاتصال بسطح المكتب، افتح هذا المنفذ في جدار الحماية لديك، إذا فعلته + في انتظار اتصال الجوّال: + للسماح لتطبيق الجوّال بالاتصال بسطح المكتب، افتح هذا المنفذ في جدار الحماية لديك، إذا فعّلته أنشئ ملف تعريف الدردشة عرض التحطم فتح منفذ في جدار الحماية - اقطع اتصال الجوالات - لا يوجد جوال متصل + اقطع اتصال الجوّالات + لا يوجد جوّال متصل خطأ في إظهار المحتوى خطأ في إظهار الرسالة انتهت المكالمة %1$s @@ -1526,30 +1526,26 @@ أظهر الأخطاء الداخلية خطأ فادح خطأ داخلي - يُرجى إبلاغ المطورين بذلك: -\n%s - يُرجى إبلاغ المطورين بذلك: -\n%s -\n -\nيوصى بإعادة تشغيل التطبيق. + يُرجى إبلاغ المطوِّرين بذلك: \n%s + يُرجى إبلاغ المطوِّرين بذلك: \n%s \n \nيوصى بإعادة تشغيل التطبيق. أعد تشغيل الدردشة - %s غير نشط]]> + %s غير نشط]]> أظهر مكالمات API البطيئة غير معروف حدّثت ملف التعريف - %s مفقود]]> - %s لديه إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين]]> - %s في حالة سيئة]]> + %s مفقود]]> + %s لديه إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين]]> + %s في حالة سيئة]]> اسم العرض غير صالح! اسم العرض هذا غير صالح. الرجاء اختيار اسم آخر. توقف الاتصال توقف الاتصال - %s بسبب: %s]]> + %s بسبب: %s]]> قُطع الاتصال بسبب: %s - %s]]> - %s]]> + %s]]> + %s]]> سطح المكتب غير نشط - %s مشغول]]> + %s مشغول]]> انتهت المهلة أثناء الاتصال بسطح المكتب قُطع اتصال سطح المكتب الاتصال بسطح المكتب في حالة سيئة @@ -1558,7 +1554,7 @@ يحتوي سطح المكتب على إصدار غير مدعوم. يُرجى التأكد من استخدام نفس الإصدار على كلا الجهازين العضو %1$s وظيفة بطيئة - خيارات المطور + خيارات المطوِّر تغيّر العضو %1$s إلى %2$s أزلت عنوان الاتصال أزلت صورة ملف التعريف @@ -1581,7 +1577,7 @@ حدث خطأ أثناء إنشاء الرسالة حدث خطأ أثناء حذف الملاحظات الخاصة ملاحظات خاصة - مسح الملاحظات الخاصة؟ + محو الملاحظات الخاصة؟ أُنشئ في: %s رسالة محفوظة إلغاء حظر العضو للجميع؟ @@ -1604,7 +1600,7 @@ مكالمة فيديو مكالمة صوتية أنهيّ المكالمة - متصفح الويب الافتراضي مطلوب للمكالمات. يُرجى تضبيط المتصفح الافتراضي في النظام، ومشاركة المزيد من المعلومات مع المطورين. + متصفح الويب الافتراضي مطلوب للمكالمات. يُرجى تضبيط المتصفح الافتراضي في النظام، ومشاركة المزيد من المعلومات مع المطوِّرين. حدث خطأ أثناء فتح المتصفح أرشف وأرفع يمكن للمُدراء حظر عضو للجميع. @@ -1648,7 +1644,7 @@ تحقق من عبارة المرور تأكد من أنك تتذكر عبارة مرور قاعدة البيانات لترحيلها. التحقق من عبارة مرور قاعدة البيانات - خطأ في عرض الإشعار، تواصل بالمطورين. + خطأ في عرض الإشعار، تواصل بالمطوِّرين. مكالمات صورة في صورة استخدم التطبيق أثناء المكالمة. رحّل إلى جهاز آخر عبر رمز QR. @@ -1819,8 +1815,7 @@ معلومات قائمة انتظار الرسائل احمِ عنوان IP الخاص بك من مُرحلات المُراسلة التي اختارتها جهات اتصالك. \nفعّل في إعدادات *الشبكة والخوادم*. سمات دردشة جديدة - حدث خطأ أثناء تهيئة WebView. حدّث نظامك إلى الإصدار الجديد. يُرجى التواصل بالمطورين. -\nError: %s + حدث خطأ أثناء تهيئة WebView. حدّث نظامك إلى الإصدار الجديد. يُرجى التواصل بالمطوِّرين. \nError: %s تحسين تسليم الرسائل مع انخفاض استخدام البطارية. مفتاح خاطئ أو عنوان مجموعة الملف غير معروف - على الأرجح حُذف الملف. @@ -1834,8 +1829,7 @@ حالة الرسالة: %s خطأ في النسخ استُخدم هذا الرابط مع جوّال آخر، يُرجى إنشاء رابط جديد على سطح المكتب. - يُرجى التحقق من اتصال الهاتف المحمول وسطح المكتب بنفس الشبكة المحلية، وأن جدار حماية سطح المكتب يسمح بالاتصال. -\nيُرجى مشاركة أي مشاكل أُخرى مع المطورين. + يُرجى التحقق من اتصال الهاتف المحمول وسطح المكتب بنفس الشبكة المحلية، وأن جدار حماية سطح المكتب يسمح بالاتصال. \nيُرجى مشاركة أي مشاكل أُخرى مع المطوِّرين. لا يمكن إرسال الرسالة تفضيلات الدردشة المحدّدة تحظر هذه الرسالة. التفاصيل @@ -1919,7 +1913,7 @@ رُفع القطع متصل الخوادم المتصلة - جارِ الاتصال + يتصل الاتصالات النشطة ملف التعريف الحالي أخطاء الحذف @@ -1988,7 +1982,7 @@ المكالمات ممنوعة! لا يمكن مكالمة أحد أعضاء المجموعة لا يمكن إرسال رسالة إلى عضو المجموعة - جارِ الاتصال بجهة الاتصال، يُرجى الانتظار أو التحقق لاحقًا! + يتصل بجهة الاتصال، يُرجى الانتظار أو التحقق لاحقًا! جهات الاتصال المؤرشفة ادعُ لا توجد جهات اتصال مُصفاة @@ -1998,7 +1992,7 @@ يُرجى الطلب من جهة اتصالك تفعيل المكالمات. حذف %d رسائل الأعضاء؟ سيتم وضع علامة على الرسائل للحذف. سيتمكن المُستلم/(المُستلمون) من الكشف عن هذه الرسائل. - حدد + حدّد سيتم حذف الرسائل لجميع الأعضاء. سيتم وضع علامة على الرسائل على أنها تحت الإشراف لجميع الأعضاء. الرسالة @@ -2211,7 +2205,7 @@ الإشعارات والبطارية فقط مالكي الدردشة يمكنهم تغيير التفضيلات. الخصوصية لعملائك. - الجوالات عن بُعد + الجوّالات عن بُعد ادعُ للدردشة مغادرة المجموعة؟ سيتم إزالة العضو من الدردشة - لا يمكن التراجع عن هذا! @@ -2352,10 +2346,10 @@ إلغاء حظر الأعضاء للجميع؟ حظر الأعضاء للجميع؟ سيتم عرض رسائل من هؤلاء الأعضاء! - لا يمكن قراءة عبارة المرور في Keystore، يُرجى إدخالها يدويًا. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطورين. + لا يمكن قراءة عبارة المرور في Keystore، يُرجى إدخالها يدويًا. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطوِّرين. سيتم إزالة الأعضاء من المجموعة - لا يمكن التراجع عن هذا! المشرفين - لا يمكن قراءة عبارة المرور في Keystore. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطورين. + لا يمكن قراءة عبارة المرور في Keystore. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطوِّرين. موافقة الانتظار ضبّط مُشغلي الخادم سياسة الخصوصية وشروط الاستخدام. @@ -2515,7 +2509,7 @@ افتح الرابط النظيف افتح الرابط الكامل أزل تتبع الروابط - رابط مُرحل SimpleX + عنوان مُرحل SimpleX خطأ في وضع علامة \"مقروءة\" البصمة في عنوان الخادم الوجهة لا تتطابق مع الشهادة: %1$s. البصمة في عنوان خادم التحويل لا تتطابق مع الشهادة: %1$s. @@ -2542,4 +2536,127 @@ فيديوهات رسائل صوتية إذا انضممت إلى قنوات أو أنشأتها، فستتوقف عن العمل نهائيًا. + %1$d/%2$d مُرحلات نشطة + %1$d/%2$d مُرحلات نشطة، %3$d فشلت + %1$d/%2$d مُرحلات متصلة + %1$d/%2$d مُرحلات متصلة، %3$d خطأ + %1$d مشترك + %1$d مشترك + وافقت + نشط + احظر المشترك للكل؟ + ألغِ + مُرحلات الدردشة + مُرحلات الدردشة + احذف القناة + احذف القناة؟ + حُذفت + حُذفت القناة + احذف المُرحل + إذاعة + إلغاء إنشاء القناة؟ + اختبر المُرحل لاسترداد اسمه.]]> + %1$s !]]> + قناة + قناة + قناة + اسم القناة بالكامل + رابط القناة + أعضاء القناة + اسم القناة + يُخزّن ملف تعريف القناة على أجهزة المشتركين وعلى مُرحلات الدردشة. + حُدِّث ملف تعريف القناة + ستُحذف القناة لجميع المشتركين - لا يمكن التراجع عن هذا الإجراء! + ستُحذف القناة من عِندك - لا يمكن التراجع عن هذا الإجراء! + ستبدأ القناة بالعمل مع %1$d من أصل %2$d من المُرحلات. أتود المتابعة؟ + مُرحل الدردشة + مُرحلات الدردشة + مُرحلات الدردشة توجّه الرسائل في القنوات التي تنشئها. + مُرحلات الدردشة توجّه الرسائل في القنوات في القناة. + تحقق من عنوان المُرحل وحاول مرة أخرى. + تحقق من اسم المُرحل وحاول مرة أخرى. + اضبط المُرحلات + اتصل + متصل + يتصل + أنشئ قناة عامة + أنشئ قناة عامة + أنشئ قناة عامة (تجريبي) + ينشئ قناة + %d أحداث القناة + فك ترميز الرابط + تم الإسقاط (%1$d محاولات) + حرّر ملف تعريف القناة + فعّل مُرحل دردشة واحد على الأقل لإنشاء قناة. + أدخل اسم المُرحل… + خطأ في إضافة المُرحل + خطأ في إنشاء القناة + خطأ في فتح القناة + خطأ: %s + خطأ في حفظ ملف تعريف القناة + فشل + فشل + احصل على الرابط + عنوان المُرحل غير صالح! + اسم المُرحل غير صالح! + مدعو + انضم للقناة + غادِر القناة + مغادرة القناة؟ + الرابط + رسالة خطأ + جديد + مُرحل دردشة جديد + لا مُرحلات دردشة + لا مُرحلات دردشة مفعّلة. + ليس كل المُرحلات متصلة + افتح قناة + افتح قناة جديدة + المالك + المالكون + عنوان المُرحل مسبق الضبط + اسم المُرحل مسبق الضبط + تابِع + مُرحل + مُرحل + عنوان المُرحل + عنوان المُرحل + فشل اتصال المُرحل + رابط المُرحل + فشل اختبار المُرحل + أزِل المشترك + إزالة المشترك؟ + احفظ وأرسل إشعار للمشتركين في القناة + احفظ ملف تعريف القناة + يتطلب الخادم تفويضًا للاتصال بالمُرحل، يُرجى التحقق من كلمة المرور. + تحذير من الخادم + شارك عنوان المُرحل + مشترك + المشتركون + يستخدم المشتركون رابط المُرحل للاتصال بالقناة.\nاُستخدم عنوان المُرحل لإعداد هذا المُرحل للقناة. + ستُزيل المشترك من القناة - لا يمكن التراجع عن هذا الإجراء! + انقر انضم للقناة + فشل الاختبار عند الخطوة %s. + اختبر المُرحل + أُزيل التطبيق هذه الرسالة بعد %1$d محاولات لاستلامها. + عنوان مُرحل الدردشة هذا لا يمكن استخدامه للاتصال. + إلغاء حظر المشترك للجميع؟ + ملف القناة التعريفي حُدِّث + استخدم للقنوات الجديدة + استخدم مُرحل + تحقق + عبر %1$s + تسجيل الصوت غير مدعوم على منصتك + انتظر + رد الانتظار + أنت + أنت مشترك + بإمكانك مشاركة رابط أو رمز QR - وسيتمكن أي شخص من الانضمام إلى القناة. + لقد اتصلت بالقناة عبر رابط المُرحل هذا. + قناتك + قناتك + سيتم مشاركة ملف تعريفك %1$s مع مُرحلات القناة والمشتركين.\nيمكن للمُرحلات الوصول إلى رسائل القناة. + عنوان مُرحلك + اسم مُرحلك + ستتوقف عن تلقي الرسائل من هذه القناة، وسيتم الاحتفاظ بسجل الدردشة. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index 03a8bcfdc5..d708736821 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -1038,7 +1038,7 @@ SimpleX Лого SimpleX Екип SMP сървъри - Сподели адреса с контактите\? + Сподели адреса с контактите? Сподели линк Сподели с контактите Спри споделянето diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index 5c8f73bf93..a34a4ca7fc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -2508,4 +2508,4 @@ Suprimir missatges Els missatges de membre s\'eliminaran; això no es pot desfer! Eliminar membre i els seus missatges - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 43afc3be04..c7697c35ba 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -877,7 +877,7 @@ Předvolby skupiny Přímé zprávy Mazání všem - zapnuty + zapnuto povoleno vám vypnuty Mizící zprávy zakázány. @@ -930,7 +930,7 @@ moderované moderovaný %s Smazat zprávu člena\? - moderovaný + Moderovat Kontaktujte prosím správce skupiny. jste pozorovatel pozorovatel @@ -1168,7 +1168,7 @@ BARVY MOTIVU Přizpůsobit motiv Aktualizace profilu bude zaslána vašim kontaktům. - Sdílet adresu s kontakty\? + Sdílet adresu s kontakty? Přestat sdílet adresu\? Vytvořit adresu, aby se s vámi lidé mohli spojit. Uložit nastavení SimpleX adresy @@ -1543,7 +1543,7 @@ blokováno %s kontakt %1$s změnen na %2$s Vytvořeno v - Blok všem + Blokovat všem Blokovat člena všem? Chyba blokování člena všem Vylepšené doručovaní zpráv @@ -2054,7 +2054,7 @@ Žádné servery pro soukromé směrování chatů. Žádné servery pro příjem souborů. Žádné servery pro příjem zpráv. - Všechny chaty budou ze seznamu odebrány %s, a seznam bude smazán + Všechny chaty budou ze seznamu %s odebrány, a seznam bude smazán Pro sociální sítě Vzdálené telefony %s.]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index c66d351601..43340f85dc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -176,7 +176,7 @@ Sie sind zu der Gruppe eingeladen Beitreten als %s verbinde … - Zum Starten eines neuen Chats tippen + Tippen, um einen neuen Chat zu starten Chatten Sie mit den Entwicklern Sie haben keine Chats @@ -547,7 +547,7 @@ Privatsphäre App-Bildschirm schützen Bilder automatisch akzeptieren - Link-Vorschau senden + Linkvorschau senden App-Datensicherung MEINE DATEN @@ -1040,7 +1040,7 @@ Für die Anzeige das Passwort im Suchfeld eingeben Privates Profil erzeugen! Stummschalten - Zum Aktivieren des Profils tippen. + Tippen, um das Profil zu aktivieren. Stummschaltung aufheben Bei Inaktivität stummgeschaltet! Schützen Sie Ihre Chat-Profile mit einem Passwort! @@ -1191,20 +1191,20 @@ Sie werden Ihre damit verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. Design anpassen INTERFACE-FARBEN - Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. + Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre SimpleX-Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre SimpleX-Kontakte gesendet. Alle Ihre Kontakte bleiben verbunden. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können. SimpleX-Adresse erstellen - Mit Kontakten teilen + Mit SimpleX-Kontakten teilen Ihre Kontakte bleiben weiterhin verbunden. Automatisch akzeptieren Geben Sie eine Begrüßungsmeldung ein … (optional) Freunde einladen Lassen Sie uns über SimpleX Chat schreiben - Profil-Aktualisierung wird an Ihre Kontakte gesendet. + Profil-Aktualisierung wird an Ihre SimpleX-Kontakte gesendet. SimpleX-Adress-Einstellungen speichern Einstellungen speichern\? - Die Adresse mit Kontakten teilen\? + Die Adresse mit SimpleX-Kontakten teilen? Teilen beenden Das Teilen der Adresse beenden\? Keine Adresse erstellt @@ -1607,7 +1607,7 @@ Kontakt hinzufügen Zum Scannen tippen Behalten - Zum Link einfügen tippen + Tippen, um den Link einzufügen Suchen oder SimpleX-Link einfügen Der Chat wurde gestoppt. Wenn diese Datenbank bereits auf einem anderen Gerät von Ihnen verwendet wurde, sollten Sie diese dorthin zurück übertragen, bevor Sie den Chat starten. Chat starten? @@ -1928,7 +1928,7 @@ Bitte überprüfen Sie, ob sich das Mobiltelefon und die Desktop-App im gleichen lokalen Netzwerk befinden, und die Desktop-Firewall die Verbindung erlaubt. \nBitte teilen Sie weitere mögliche Probleme den Entwicklern mit. Nachricht wurde nicht gesendet - Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt. + Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt. Bitte versuchen Sie es später erneut. Fehler beim privaten Routing Die Nachricht kann später zugestellt werden, wenn das Mitglied aktiv wird. @@ -2001,7 +2001,7 @@ Dateispeicherort öffnen andere Die Server-Adresse ist nicht mit den Netzwerkeinstellungen kompatibel: %1$s. - Link scannen / einfügen + Link einfügen / Scannen Alle Server neu verbinden Server neu verbinden? Alle Server neu verbinden? @@ -2307,7 +2307,7 @@ Schutz der Privatsphäre Ihrer Kunden. Zur Verbindung aufgefordert Bitte verkleinern Sie die Nachrichten-Größe oder entfernen Sie Medien und versenden Sie diese erneut. - Nur Chat-Eigentümer können die Präferenzen ändern. + Präferenzen können nur von Chat-Eigentümern geändert werden. Bitte verkleinern Sie die Nachrichten-Größe und versenden Sie diese erneut. Die Rolle wird auf %s geändert. Im Chat wird Jeder darüber informiert. Sie werden von diesem Chat keine Nachrichten mehr erhalten. Der Nachrichtenverlauf wird beibehalten. @@ -2455,7 +2455,7 @@ Verbindungs-Link wird nicht unterstützt Verkürzter Link Vollständiger Link - SimpleX-Kanal-Link + SimpleX-Kanallink Für diesen Link wird eine neuere App-Version benötigt. Bitte aktualisieren Sie die App oder bitten Sie Ihren Kontakt einen kompatiblen Link zu senden. Alle Server Aus @@ -2633,25 +2633,25 @@ Fehlgeschlagen Kanäle, welche Sie erstellt haben oder denen Sie beigetreten sind, werden dauerhaft deaktiviert. %1$d/%2$d Relais aktiv - %1$d/%2$d Relais aktiv, %3$d Fehlgeschlagen + %1$d/%2$d Relais aktiv, %3$d fehlgeschlagen %1$d/%2$d Relais verbunden %1$d/%2$d Relais verbunden, %3$d Fehler %1$d Abonnent %1$d Abonnenten Angenommen Aktiv - Abonnenten für alle blockieren? + Abonnent für alle blockieren? Broadcast Abbrechen Kanalerstellung abbrechen? - Relais testen, um dessen Namen zu ermitteln.]]> + Relais testen, um dessen Namen abzurufen.]]> %1$s!]]> Kanal Kanal Kanal - Kanal-Link + Kanallink Kanal-Mitglieder - Kanal-Name + Kanalname Der Kanal wird für alle Abonnenten gelöscht. Dies kann nicht rückgängig gemacht werden! Der Kanal wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! Der Kanal wird mit %1$d von %2$d Relais gestartet. Fortfahren? @@ -2667,9 +2667,9 @@ Verbinden verbunden verbinden - Kanal erstellen - Kanal erstellen - Kanal erstellen (BETA) + Öffentlichen Kanal erstellen + Öffentlichen Kanal erstellen + Öffentlichen Kanal erstellen (BETA) Kanal wird erstellt Link dekodieren Kanal löschen @@ -2738,8 +2738,87 @@ Sie haben sich über diesen Relais‑Link mit dem Kanal verbunden. Ihr Kanal Ihr Kanal - Ihr Profil %1$s wird mit den Kanal‑Relais und -Abonnenten geteilt. + Ihr Profil %1$s wird mit den Kanal‑Relais und -Abonnenten geteilt.\nRelais können auf Kanalnachrichten zugreifen. Ihre Relais-Adresse Ihr Relais-Name Sie werden keine Nachrichten mehr aus diesem Kanal erhalten. Der Chatverlauf bleibt erhalten. + Vollständiger Name des Kanals: + Das Kanalprofil wird auf den Geräten der Abonnenten und auf den Chat‑Relais gespeichert. + Kanalprofil wurde aktualisiert + %d Kanalereignisse + Kanal gelöscht + Verworfen (%1$d Versuche) + Fehler: %s + Fehler beim Speichern des Kanalprofils + Übertragungsfehler + Speichern und Abonnenten des Kanals informieren + Kanalprofil speichern + Die App hat diese Nachricht nach %1$d Empfangsversuchen entfernt. + Kanalprofil aktualisiert + %1$d/%2$d Relais aktiv, %3$d Fehler + %1$d/%2$d Relais aktiv, %3$d entfernt + %1$d/%2$d Relais verbunden, %3$d fehlgeschlagen + %1$d/%2$d Relais verbunden, %3$d entfernt + %1$d Relais fehlgeschlagen + %1$d Relais nicht aktiv + %1$d Relais entfernt + Das Hinzufügen von Relais wird zu einem späteren Zeitpunkt unterstützt. + Alle Relais fehlgeschlagen + Alle Relais entfernt + Broadcast nicht möglich + Der Kanal hat keine aktiven Relais. Bitte später erneut versuchen. + Der Kanal ist vorübergehend nicht erreichbar + Inaktiv + Keine aktiven Relais + Vom Betreiber entfernt + Warte auf das Hinzufügen von Relais durch den Eigentümer des Kanals. + Geschäftliche Adresse + Kanallink + Kontaktadresse + Deaktivieren + Aktivieren + Linkvorschau aktivieren? + Fehler + Fehler beim Teilen des Kanals + (vom Eigentümer) + Gruppen-Link + Linksignatur erfolgreich verifiziert. + Netzwerk-Fehler + Einmal-Link + Relay‑Status: + Das Senden einer Link-Vorschau kann Ihre IP‑Adresse an die Website übermitteln. Sie können dies später in den Datenschutzeinstellungen ändern. + Kanal teilen… + Per Chat teilen + ⚠️ Signaturüberprüfung fehlgeschlagen: %s. + (signiert) + Zum Öffnen tippen + Die Verbindung hat das Limit für nicht zugestellte Nachrichten erreicht + Kanal-Präferenzen + Kanal-Präferenzen können nur von Kanal-Eigentümern geändert werden. + Ein Link für eine einzelne Person zum Verbinden. + Kanäle + Über einen Link oder QR-Code verbinden + Mit jemandem verbinden + Erstellen Sie Ihre öffentliche Adresse + Freunde einladen – jetzt noch einfacher 👋 + Damit Sie jeder erreichen kann + Jemanden privat einladen + Damit sich jemand mit Ihnen verbinden kann + Neuer Einmal-Link + Non‑Profit‑Governance + - Opt‑in zum Senden von Linkvorschauen.\n- Hyperlink‑Phishing verhindern.\n- Link‑Tracking entfernen. + Oder den QR‑Code persönlich oder per Videoanruf zeigen. + Oder diesen QR‑Code verwenden – ausgedruckt oder online. + Volle Kontrolle: Sie können Ihre eigenen Relais betreiben. + Privatsphäre: für Besitzer und Abonnenten. + Öffentliche Kanäle – freies Kommunizieren 🚀 + Zuverlässigkeit: mehrere Relais pro Kanal. + Sichere Web-Links + Sicherheit: Eigentümer besitzen die Kanalschlüssel. + Den Link über einen beliebigen Messenger versenden – es ist sicher. Bitte in SimpleX einfügen. + Mit jemandem sprechen + Für ein dauerhaftes SimpleX-Netzwerk. + Diese Adresse in Ihrem Social‑Media‑Profil, auf Ihrer Webseite oder in Ihrer E‑Mail‑Signatur verwenden. + Wir haben das Verbinden für neue Nutzer vereinfacht. + Ihre öffentliche Adresse diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index c233d8eabc..4e372dc635 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -358,7 +358,7 @@ Los miembros pueden enviar mensajes de voz. en modo incógnito mediante dirección de contacto ¡Error al crear perfil! - No se pudo cargar el chat + Fallo en la carga del chat Fallo en la carga de chats Enlace completo Error al eliminar contacto @@ -385,7 +385,7 @@ Colgar Archivos y multimedia ¡Grupo no encontrado! - perfil de grupo actualizado + perfil del grupo actualizado Error al crear enlace de grupo Error al eliminar enlace de grupo activado para el contacto @@ -819,7 +819,7 @@ Cambiar servidor de recepción Totalmente descentralizado. Visible sólo para los miembros. Para conectarte mediante enlace - ¡Prueba no superada! + ¡Prueba del servidor no superada! Algunos servidores no han superado la prueba: Usar servidor Para conexiones nuevas @@ -880,7 +880,7 @@ Has sido invitado a un grupo. Únete para conectar con sus miembros. has expulsado a %1$s Tú: %1$s - Puedes compartir el enlace o el código QR para que cualquiera pueda unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. + Puedes compartir el enlace o código QR. Cualquiera podrá unirse al grupo. Si más tarde lo eliminas, no afectará a los miembros del grupo. Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten. Mis preferencias Con mensaje de bienvenida opcional. @@ -1145,7 +1145,7 @@ ¿Dejar de compartir la dirección\? COLORES DE LA INTERFAZ Puedes crearla más tarde - ¿Compartir la dirección con los contactos\? + ¿Compartir la dirección con los contactos? Compartir con contactos Título Puedes compartir esta dirección con tus contactos para que puedan conectar con %s. @@ -1430,7 +1430,7 @@ Conectar con ordenador Desconectar ¿Bloquear miembro? - %d evento(s) de grupo + %d evento(s) del grupo ¡Nombre no válido! Conectado a móvil Dirección ordenador incorrecta @@ -2223,7 +2223,7 @@ Sólo los propietarios del chat pueden cambiar las preferencias. El miembro será eliminado del chat. ¡No puede deshacerse! El rol cambiará a %s. Se notificará en el chat. - Dejarás de recibir mensajes del chat. El historial del chat se conserva. + Dejarás de recibir mensajes del chat. El historial del chat se conservará. Cómo ayuda a la privacidad Cuando está habilitado más de un operador, ninguno dispone de los metadatos para conocer quién se comunica con quién. Tu perfil de chat será enviado a los miembros de chat @@ -2530,7 +2530,7 @@ Abrir enlace limpio Limpiar enlaces de seguimiento Abrir enlace completo - Enlace de servidor SimpleX + Dirección de servidor SimpleX Error al marcar como leído La huella en la dirección del servidor no coincide con el certificado: %1$s. La huella en la dirección del servidor de destino no coincide con el certificado: %1$s. @@ -2554,4 +2554,130 @@ Buscar mensajes de voz Vídeos Mensajes de voz + %1$d/%2$d servidores activos + %1$d/%2$d servidores activos, %3$d han fallado + %1$d/%2$d servidores conectados + %1$d/%2$d servidores conectados, %3$d errores + aceptado + activo + Test del servidor para recibir su nombre.]]> + El perfil del canal se almacena en los dispositivos de los suscriptores y en los servidores de chat. + El canal comenzará a funcionar con %1$d de %2$d servidores. ¿Continuar? + Servidores de chat + Servidores de chat + Servidores de chat + Servidores de chat + Los servidores de chat reenvían los mensajes en los canales que has creado. + Los servidores de chat reenvían los mensajes a los suscriptores del canal. + Comprueba la dirección del servidor y prueba de nuevo. + Comprueba el nombre del servidor y prueba de nuevo. + Configurar servidores + Conectar + conectado + conectando + Decodificar enlace + eliminado + Eliminar servidor + Activa al menos un servidor de chat para crear un canal. + Introduce el nombre del servidor… + Error al añadir el servidor + La conexión con el servidor ha fallado + ¡El test del servidor ha fallado! + Prueba no superada en el paso %s. + %1$d suscriptor + %1$d suscriptores + ¿Bloquear al suscriptor para todos? + Emisión + Cancelar + ¿Cancelar la creación del canal? + %1$s!]]> + canal + Canal + Canal + Título completo: + Enlace del canal + Miembros canal + Título del canal + perfil del canal actualizado + El canal será eliminado para todos los suscriptores. ¡No puede deshacerse! + El canal será eliminado para tí. ¡No puede deshacerse! + CONEXIÓN FALLIDA + Crear canal público + Crear canal público + Crear canal público (BETA) + Creando canal + %d eventos del canal + Eliminar canal + ¿Eliminar canal? + canal eliminado + caído (%1$d intentos) + Editar perfil del canal + Error al crear el canal + Error al abrir el canal + error:%s + Error al guardar el perfil del canal + fallo + fallo + Recibir el enlace + Si te has unido o has creado canales, dejarán de funcionar permanentemente. + ¡Dirección de servidor no válido! + ¡Nombre de servidor no válido! + Invitado + Unirme al canal + Salir del canal + ¿Salir del canal? + Enlace + Mensaje de error + nuevo + Nuevo servidor de chat + Sin servidores de chat + Ningún servidor de chat activado. + Hay servidores no conectados + Abrir canal + Abrir canal nuevo + PROPIETARIO + Propietarios + Direcciones predefinidas + Nombres predefinidos + Continuar + servidor + SERVIDOR + Dirección servidor + Dirección del servidor + Enlace servidor + Eliminar suscriptor + ¿Eliminar suscriptor? + Guardar y notificar suscriptores + Guardar perfil del canal + El servidor requiere autorización para conectar con el servidor, comprueba la contraseña. + Alerta del servidor + Compartir dirección del servidor + SUSCRIPTOR + Suscriptores + Los suscriptores usan el enlace del servidor para conectarse a los canales.\nLa dirección del servidor se usó para establecer el servidor para el canal. + El suscriptor será eliminado del canal. ¡No puede deshacerse! + Pulsa Unirme al canal + Test servidor + La app ha eliminado el mensaje tras %1$d intentos de recibirlo. + Esto es una dirección de servidor, no puede usarse para conectar. + ¿Desbloquear al suscriptor para todos? + perfil del canal actualizado + Usar para canales nuevos + Usar servidor + Verificar + mediante %1$s + La grabación de voz no es compatible con tu plataforma + Espera + Espera respuesta + tu + eres suscriptor + Puedes compartir un enlace o código QR. Cualquiera podrá unirse al canal. + Te conectaste al canal mediante este enlace de servidor. + Tu canal + Tu canal + El perfil %1$s será compartido con los servidores de canal y los suscriptores.\nLos servidores tienen acceso a los mensajes del canal. + Tu dirección de servidor + Tu nombre del servidor + Dejarás de recibir mensajes de este canal. El historial del chat se conservará. + fallo diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index c6b094ab56..5ea012dcf9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -822,7 +822,7 @@ Arvioi sovellus Skannaa palvelimen QR-koodi Profiilipäivitys lähetetään kontakteillesi. - Jaa osoite kontakteille\? + Jaa osoite kontakteille? Lopeta jakaminen Lopeta osoitteen jakaminen\? Tallenna automaattisen hyväksynnän asetukset diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 42ba84ef8c..677bdeb9b5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -1105,7 +1105,7 @@ Vous pouvez accepter ou refuser les demandes de contacts. COULEURS DE L\'INTERFACE Vos contacts resteront connectés. - Partager l\'adresse avec vos contacts \? + Partager l\'adresse avec vos contacts ? Partager avec vos contacts Entrez un message de bienvenue… (facultatif) Cesser le partage diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 310a52715a..c83aa77b0e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -29,7 +29,7 @@ Elfogadja a kapcsolódási kérést? Elfogadás Elfogadás - Cím hozzáadása a profilhoz, hogy a partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve partnerei számára. + Cím hozzáadása a profilhoz, hogy a SimpleX partnerei megoszthassák másokkal. A profilfrissítés el lesz küldve a SimpleX partnerei számára. További kiemelőszín híváshiba Csoporttagok letiltása @@ -91,7 +91,7 @@ Nem lehet meghívni a partnert! hibás az üzenet azonosítója Partneri kapcsolatkérések automatikus elfogadása - Megjegyzés: NEM fogja tudni helyreállítani, vagy módosítani a jelmondatot abban az esetben, ha elveszíti.]]> + Megjegyzés: NEM fogja tudni helyreállítani vagy módosítani a jelmondatot abban az esetben, ha elveszíti.]]> hívás… További másodlagos szín Hozzáadás egy másik eszközhöz @@ -543,7 +543,7 @@ Kiszolgáló megadása kézzel A fájl akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! Hiba történt a csoporthivatkozás létrehozásakor - A galériából + Galéria Engedélyezés (csoport egyéni beállításainak megtartása) Hiba történt a partner törlésekor A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) @@ -674,7 +674,7 @@ Számítógépek A markdown használata Csevegési profil létrehozása - Védett a kéretlen tartalommal szemben + Védett a kéretlen tartalmakkal szemben Hordozható eszközök leválasztása Különböző nevek, profilképek és átvitelelkülönítés. Elutasítás esetén a kérés küldője NEM kap értesítést. @@ -944,7 +944,7 @@ Biztonsági kód beolvasása a partnere alkalmazásából. Lépjen kapcsolatba a csoport adminisztrátorával. Videó bekapcsolva - Profilnév: + Profil neve: Beillesztés Köszönjük, hogy telepítette a SimpleX Chatet! Csillagozás a GitHubon @@ -979,7 +979,7 @@ %s (jelenlegi) Saját SMP-kiszolgáló Véletlen - Megosztás a partnerekkel + Megosztás a SimpleX partnerekkel Ön Nincsenek csevegései Küldés @@ -995,7 +995,7 @@ Elküldve: %s Jelenlegi profil használata Ez az eszköz - Megosztja a címet a partnereivel? + Megosztja a címet a SimpleX partnereivel? Profiljelszó Téma Eltávolítja a jelmondatot a beállításokból? @@ -1038,7 +1038,7 @@ Kiszolgálók mentése Üdvözlőüzenet mp - A profilfrissítés el lesz küldve a partnerei számára. + A profilfrissítés el lesz küldve a SimpleX partnerei számára. Egyszerűsített inkognitómód Menti az üdvözlőüzenetet? Új csevegési profil létrehozásához indítsa újra az alkalmazást. @@ -1090,7 +1090,7 @@ Menti a beállításokat? Jelkód Ismeretlen hiba - Saját SMP-kiszolgálójának címe + Saját SMP-kiszolgáló címe Csevegési konzol megnyitása Eltávolítás Adatbázis-jelmondat beállítása @@ -1510,7 +1510,7 @@ Vagy QR-kód beolvasása Érvénytelen QR-kód Megtartás - Keresés vagy SimpleX-hivatkozás beillesztése + Keressen vagy adjon meg egy SimpleX-hivatkozást Belső hibák megjelenítése Kritikus hiba Belső hiba @@ -1812,7 +1812,7 @@ Fájlkiszolgáló-hiba: %1$s Fájl állapota Fájl állapota: %s - Másolási hiba + Hiba másolása Ezt a hivatkozást egy másik hordozható eszközön már használták, hozzon létre egy új hivatkozást a számítógépén. Ellenőrizze, hogy a hordozható eszköz és a számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint a számítógép tűzfalában engedélyezve van-e a kapcsolat.\nMinden további problémát osszon meg a fejlesztőkkel. Nem lehet üzenetet küldeni @@ -1823,7 +1823,7 @@ Továbbított üzenet Az üzenet később is kézbesíthető, ha a tag aktívvá válik. Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja. - Hivatkozás beolvasása / beillesztése + Hivatkozás megadása vagy QR-kód beolvasása Konfigurált SMP-kiszolgálók Egyéb SMP-kiszolgálók Egyéb XFTP-kiszolgálók @@ -2097,10 +2097,10 @@ Cím nyilvános megosztása SimpleX-cím megosztása a közösségi médiában. Egyszer használható meghívó megosztása egy baráttal - csak egyetlen partnerrel használható – személyesen vagy bármilyen üzenetváltó-alkalmazáson keresztül megosztható.]]> + csak egyetlen partnerrel használható – személyesen vagy bármilyen üzenetváltó alkalmazáson keresztül megosztható.]]> Beállíthatja a partner nevét, hogy emlékezzen arra, hogy kivel osztotta meg a hivatkozást. Kapcsolatbiztonság - A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó-alkalmazáson keresztül. + A SimpleX-cím és az egyszer használható meghívó biztonságosan megosztható bármilyen üzenetváltó alkalmazáson keresztül. A hivatkozás cseréje elleni védelem érdekében összehasonlíthatja a biztonsági kódokat a partnerével. A közösségi médiához Vagy a privát megosztáshoz @@ -2498,7 +2498,7 @@ Tiszta hivatkozás megnyitása Teljes hivatkozás megnyitása Nyomonkövetési paraméterek eltávolítása a hivatkozásokból - SimpleX-átjátszócím + SimpleX-átjátszó címe Hiba a csevegés olvasottként való megjelölésekor A célkiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s. A továbbító kiszolgáló címében szereplő ujjlenyomat nem egyezik a tanúsítvánnyal: %1$s. @@ -2537,9 +2537,9 @@ Kapcsolódás kapcsolódott kapcsolódás - Csatorna létrehozása - Csatorna létrehozása - Csatorna létrehozása (béta) + Nyilvános csatorna létrehozása + Nyilvános csatorna létrehozása + Nyilvános csatorna létrehozása (BÉTA) Csatorna létrehozása törölve sikertelen @@ -2597,9 +2597,9 @@ Hivatkozás dekódolása A teszt a(z) %s. lépésnél sikertelen volt. A kiszolgáló hitelesítést igényel az átjátszóhoz való kapcsolódáshoz, ellenőrizze a jelszavát. - Érvénytelen átjátszónév! + Érvénytelen az átjátszó neve! Ellenőrizze az átjátszó nevét, és próbálja újra. - Érvénytelen átjátszócím! + Érvénytelen az átjátszó címe! Ellenőrizze az átjátszó címét, és próbálja újra. Hiba az átjátszó hozzáadásakor Csevegési átjátszók @@ -2613,26 +2613,105 @@ %1$d/%2$d átjátszó kapcsolódva ÁTJÁTSZÓ Átjátszóhivatkozás - Átjátszócím + Átjátszó címe a következőn keresztül: %1$s Átjátszó címének megosztása - A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz.\nAz átjátszócím ennek az átjátszónak a beállítására szolgált a csatornához. + A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz.\nAz átjátszó címe ennek az átjátszónak a beállítására szolgált a csatornához. Ön ezen az átjátszóhivatkozáson keresztül kapcsolódott a csatornához. Feliratkozó eltávolítása Az összes feliratkozó számára letiltja a feliratkozót? Hiba a csatorna létrehozásakor Visszavonja a csatorna létrehozását? Engedélyezzen legalább egy csevegési átjátszót a csatorna létrehozásához. - A saját profilja (%1$s) meg lesz osztva a csatorna átjátszóival és feliratkozóival. + A(z) %1$s nevű profilja meg lesz osztva a csatorna átjátszóival és feliratkozóival.\nAz átjátszók hozzáférhetnek a csatornaüzenetekhez. Átjátszók konfigurálása Nem sikerült kapcsolódni az átjátszóhoz Nem minden átjátszó kapcsolódott Folytatás A csatorna %2$d átjátszóból %1$d használatával kezd el működni. Folytatja? - Átjátszócím + Átjátszó címe Ez egy csevegési átjátszó címe, nem használható kapcsolódásra. %1$s nevű csatornához!]]> Hiba a csatorna megnyitásakor Az összes feliratkozó számára feloldja a feliratkozó letiltását? Átjátszó tesztelése a nevének lekéréséhez.]]> + Csatorna teljes neve: + A csatornaprofil a feliratkozók eszközén és a csevegési átjátszókon van tárolva. + csatornaprofil frissítve + %d csatornaesemény + törölt csatorna + hiba: %s + Hiba a csatornaprofil mentésekor + Üzenethiba + Mentés és a csatorna feliratkozóinak értesítése + Csatornaprofil mentése + frissített csatornaprofil + Az alkalmazás %1$d sikertelen letöltési kísérlet után eltávolította ezt az üzenetet. + eltávolítva (%1$d kísérlet) + A csatorna ideiglenesen nem érhető el + A csatornának nincsenek aktív átjátszói. Próbáljon meg később csatlakozni. + nem lehet közvetíteni + az üzemeltető eltávolította + inaktív + Az összes átjátszó el lett távolítva + Nem sikerült kapcsolódni egyetlen átjátszóhoz sem + Nincsenek aktív átjátszók + %1$d átjátszó eltávolítva + %1$d átjátszóhoz nem sikerült kapcsolódni + %1$d átjátszó inaktív + %1$d/%2$d átjátszó aktív, %3$d eltávolítva + %1$d/%2$d átjátszó aktív, %3$d hiba + %1$d/%2$d átjátszó kapcsolódott, %3$d átjátszóhoz nem sikerült kapcsolódni + %1$d/%2$d átjátszó kapcsolódott, %3$d eltávolítva + Az átjátszók hozzáadása később lesz támogatott. + Várakozás a csatorna tulajdonosára az átjátszók hozzáadásához. + Üzleti cím + Csatornahivatkozás + Kapcsolattartási cím + Hiba a csatorna megosztásakor + (a tulajdonostól) + Csoporthivatkozás + Hivatkozás aláírása ellenőrizve. + Egyszer használható meghívó + Csatorna megosztása… + Megosztás egy csevegésen keresztül + ⚠️ Nem sikerült ellenőrizni az aláírást: %s. + (aláírva) + Koppintson ide a megnyitáshoz + Letiltás + Engedélyezés + Engedélyezi a hivatkozáselőnézetet? + Hiba + Hálózati hiba + A hivatkozáselőnézet küldése felfedheti az Ön IP-címét a weboldal számára. Ezt később módosíthatja az adatvédelmi beállításokban. + A kapcsolat elérte a kézbesítetlen üzenetek korlátját + Átjátszóeredmények: + Csatornabeállítások + Csak a csatorna tulajdonosai módosíthatják a csatornabeállításokat. + Saját nyilvános cím + Új egyszer használható meghívó + Csatornák + Saját nyilvános cím létrehozása + Kapcsolódás hivatkozáson vagy QR-kódon keresztül + Egy hivatkozás, ami egyetlen partnerrel való kapcsolat létrehozására szolgál + Kapcsolatba lépés valakivel + Bárki számára, aki el szeretné érni Önt + Partner meghívása privátban + Hagyja, hogy valaki kapcsolatba lépjen Önnel + Vagy mutassa meg a QR-kódot személyesen vagy videóhíváson keresztül. + Vagy használja ezt a QR-kódot – nyomtassa ki vagy mutassa meg online. + Küldje el a hivatkozást bármilyen üzenetváltó alkalmazáson keresztül – ez egy biztonságos módszer – és kérje meg a partnerét, hogy illessze be a SimpleX alkalmazásba. + Beszélgessen valakivel + Használja ezt a címet a közösségi oldalakon használt profiljaiban, weboldalakon vagy az e-mail aláírásában. + Könnyebben hívhatja meg a barátait 👋 + Nonprofit irányítás + - hivatkozások előnézetének küldése.\n- hiperhivatkozásokon keresztüli adathalászat megakadályozása.\n- hivatkozások nyomonkövetési paramétereinek eltávolítása. + Tulajdonjog: saját átjátszókat üzemeltethet. + Adatvédelem: tulajdonosok és előfizetők számára. + Nyilvános csatornák – mondja el szabadon a véleményét 🚀 + Megbízhatóság: több átjátszó is használható csatornánként. + Biztonságos webhivatkozások + Biztonság: a csatornák kulcsait a tulajdonosok őrzik. + A SimpleX hálózat hosszú távú működésének biztosítása érdekében. + Az új felhasználók számára egyszerűbbé tettük a kapcsolatok létrehozását. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index b6b372da7c..201b7c4ea5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1105,11 +1105,11 @@ Indirizzo SimpleX COLORI DELL\'INTERFACCIA I tuoi contatti resteranno connessi. - Aggiungi l\'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L\'aggiornamento del profilo verrà inviato ai tuoi contatti. + Aggiungi l\'indirizzo al tuo profilo, in modo che i tuoi contatti di SimpleX possano condividerlo con altre persone. L\'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX. Crea un indirizzo per consentire alle persone di connettersi con te. Crea indirizzo SimpleX - Condividi con i contatti - Condividere l\'indirizzo con i contatti\? + Condividi con i contatti di SimpleX + Condividere l\'indirizzo con i contatti di SimpleX? Smetti di condividere Inserisci il messaggio di benvenuto… (facoltativo) Ciao! @@ -1144,7 +1144,7 @@ Tema scuro Se non potete incontrarvi di persona, mostra il codice QR in una videochiamata o condividi il link. Parliamo in SimpleX Chat - L\'aggiornamento del profilo verrà inviato ai tuoi contatti. + L\'aggiornamento del profilo verrà inviato ai tuoi contatti di SimpleX. Guida per l\'utente.]]> Menu e avvisi Smettere di condividere l\'indirizzo\? @@ -1943,7 +1943,7 @@ Riconnetti tutti i server L\'indirizzo del server non è compatibile con le impostazioni di rete: %1$s. File - Scansiona / Incolla link + Incolla link / Scansiona Dimensione carattere Totale inviato Messaggio inoltrato @@ -2583,21 +2583,21 @@ Il canale verrà eliminato per tutti gli iscritti, non è reversibile! Il canale verrà eliminato per te, non è reversibile! Il canale sarà operativo con %1$d di %2$d relay. Procedere? - Relay della chat - Relay della chat - Relay della chat - Relay della chat - I relay della chat inoltrano i messaggi nei canali che crei. - I relay della chat inoltrano i messaggi agli iscritti del canale. + Relay di chat + Relay di chat + Relay di chat + Relay di chat + I relay di chat inoltrano i messaggi nei canali che crei. + I relay di chat inoltrano i messaggi agli iscritti del canale. Controlla l\'indirizzo del relay e riprova. Controlla il nome del relay e riprova. Configura i relay Connetti connesso in connessione - Crea canale - Crea canale - Crea canale (BETA) + Crea canale pubblico + Crea canale pubblico + Crea canale pubblico (BETA) Creazione canale Decodifica il link Elimina canale @@ -2605,7 +2605,7 @@ eliminato Elimina relay Modifica profilo canale - Attiva almeno un relay della chat per creare un canale. + Attiva almeno un relay di chat per creare un canale. Inserisci il nome del relay… Errore di aggiunta del relay Errore di creazione del canale @@ -2661,7 +2661,7 @@ Ti sei connesso/a al canale attraverso questo link del relay. Il tuo canale Il tuo canale - Il tuo profilo %1$s verrà condiviso con i relay del canale e gli iscritti. + Il tuo profilo %1$s verrà condiviso con i relay del canale e gli iscritti.\nI relay hanno accesso ai messaggi del canale. L\'indirizzo del tuo relay Il nome del tuo relay Smetterai di ricevere messaggi da questo canale. La cronologia della chat sarà preservata. @@ -2671,4 +2671,83 @@ Tocca Iscriviti al canale Puoi condividere un link o un codice QR, chiunque sarà in grado di iscriversi al canale. Trasmetti + Nome completo del canale: + Il profilo del canale è memorizzato sui dispositivi degli iscritti e sui relay di chat. + profilo del canale aggiornato + %d eventi del canale + canale eliminato + scartato (%1$d tentativi) + errore: %s + Errore di salvataggio del profilo del canale + Errore del messaggio + Salva e avvisa gli iscritti del canale + Salva il profilo del canale + L\'app ha rimosso questo messaggio dopo %1$d tentativi di riceverlo. + profilo del canale aggiornato + %1$d/%2$d relay attivi, %3$d errori + %1$d/%2$d relay attivi, %3$d rimossi + %1$d/%2$d relay connessi, %3$d falliti + %1$d/%2$d relay connessi, %3$d rimossi + %1$d relay falliti + %1$d relay non attivi + %1$d relay rimossi + L\'aggiunta di relay verrà supportata prossimamente. + Tutti i relay falliti + Tutti i relay rimossi + impossibile trasmettere + Il canale non ha relay attivi. Prova a iscriverti più tardi. + Canale non disponibile temporaneamente + inattivo + Nessun relay attivo + rimosso da un operatore + In attesa che il proprietario del canale aggiunga dei relay. + Indirizzo di lavoro + Link del canale + Indirizzo di contatto + Errore nella condivisione del canale + (dal proprietario) + Link del gruppo + Firma del link verificata. + Condividi canale… + Condividi via chat + ⚠️ Verifica della firma fallita: %s. + (firmato) + Tocca per aprire + Link una tantum + Disattiva + Attiva + Attivare le anteprime dei link? + Errore + Errore di rete + Risultati relay: + L\'invio di un\'anteprima del link può rivelare il tuo indirizzo IP al sito. Puoi modificarlo nelle impostazioni di Privacy più tardi. + La connessione ha raggiunto il limite di messaggi non consegnati + Preferenze del canale + Solo i proprietari del canale possono modificarne le preferenze. + Un link per una persona da connettere + Canali + Connetti via link o codice QR + Connettiti con qualcuno + Crea il tuo indirizzo pubblico + Per chiunque debba raggiungerti + Invita qualcuno in modo privato + Lascia che qualcuno si connetta a te + Nuovo link una tantum + O mostra il QR di persona o via videochiamata. + O usa questo QR: stampalo o mostralo online. + Invia il link tramite qualsiasi messenger, è sicuro. Chiedi di incollarlo in SimpleX. + Parla con qualcuno + Usa questo indirizzo nel tuo profilo di social media, sito web o firma email. + Il tuo indirizzo pubblico + È più facile invitare i tuoi amici 👋 + Organizzazione non a scopo di lucro + Per la sostenibilità della rete di SimpleX. + - scegli se inviare anteprime dei link.\n- previeni il phishing dei collegamenti ipertestuali.\n- rimuovi il tracciamento dei link. + Proprietà: puoi gestire i tuoi relay personali. + Privacy: per i proprietari e gli iscritti. + Canali pubblici - parla liberamente 🚀 + Affidabilità: relay multipli per canale. + Link web sicuri + Sicurezza: solo i proprietari hanno le chiavi del canale. + Abbiamo semplificato la connessione per i nuovi utenti. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index fb83b83735..1ef02fd128 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -945,7 +945,7 @@ הצג קוד QR נעילת SimpleX כוכב ב־GitHub - לשתף כתובת עם אנשי קשר\? + לשתף כתובת עם אנשי קשר? עצור שיתוף הגדרת קוד גישה נעילת SimpleX diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 4c5b279ba7..a5c9f98421 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -1097,7 +1097,7 @@ システム認証の代わりに設定します。 プロフィールを非表示にできます! リレー サーバーは IP アドレスを保護しますが、通話時間は監視されます。 - アドレスを連絡先と共有しますか\? + アドレスを連絡先と共有しますか? 保留中の通話 データ移行の確認が正しくない %s を提供しました diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index 1e71459df9..aa2e2e46c9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -527,8 +527,7 @@ Nutraukti adreso keitimą Automatiškai priimti kontaktų užklausas Jį įrašius visi duomenys bus pašalinti. - Kiekvienam kontaktui ir grupės nariui bus naudojamas atskiras TCP prisijungimas (ir SOCKS prisijungimo duomenys). -\nTurėkite omenyje: jei turite daug prisijungimų, akumuliatoriaus ir interneto duomenų sąnaudos gali būti žymiai didesnės ir, kartais, prisijungimai gali patirti nesėkmę. + Kiekvienam kontaktui ir grupės nariui bus naudojamas atskiras TCP prisijungimas (ir SOCKS prisijungimo duomenys). \nTurėkite omenyje: jei turite daug prisijungimų, akumuliatoriaus ir interneto duomenų sąnaudos gali būti žymiai didesnės ir, kartais, prisijungimai gali patirti nesėkmę.]]> %1$s nori su jumis susisiekti per Yra įjungtas akumuliatoriaus naudojimo optimizavimas, išjungiantis foninę tarnybą ir periodines užklausas apie naujas žinutes. Nustatymuose galite įjungti ją iš naujo. Visada įjungta diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml index 2843b2cf8d..0f192b284c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml @@ -79,9 +79,9 @@ Nederīgs ziņojuma formāts Tiešraide Moderēts apraksts - E2ee Info E2ee - E2ee Info No Pq - E2ee Info Pq + + + Savienojuma lokālais displeja vārds Displeja vārds Savienojums izveidots Apraksts Jūs kopīgojāt vienreizējo saiti inkognito režīmā @@ -266,7 +266,7 @@ Pievienot ziņu Savienoties Vai sūtīt kontakta pieprasījumu? - Sūtot kontakta pieprasījumu, jūs atklāsiet savu SimpleX lietotājvārdu šim kontaktam. Vai vēlaties turpināt? + Sūtīt pieprasījumu bez ziņas Sūtīt pieprasījumu Nevar nosūtīt ziņu @@ -306,7 +306,7 @@ Galerijas attēla poga Galerijas video poga Paldies, ka instalējāt SimpleX - Jūs varat sazināties ar SimpleX čata dibinātāju + Lai sāktu jaunu čatu, palīdzības virsraksts Čata palīdzības pieskāriena poga Saglabāt neizmantoto uzaicinājuma jautājumu @@ -830,7 +830,7 @@ Pārbaudīt savienojumu Pārbaudīt kodu mobilajā ierīcē Šīs ierīces nosaukums - Šīs ierīces versija + Savienots mobilais tālrunis Savienots ar mobilo tālruni Ievadiet šīs ierīces nosaukumu @@ -843,17 +843,17 @@ Atvienot darbvirsmu Atvienot attālo resursdatoru Atvienot attālos resursdatorus - Attālais resursdators tika atvienots (paziņojums) + Attālais resursdators tika atvienots (virsraksts) Attālā vadība tika atvienota (virsraksts) - Attālais resursdators atvienots no + Attālā vadība atvienota ar iemeslu Attālās vadības savienojums apturēts (apraksts) Remote Ctrl savienojums ir pārtraukts Identity Desc Kopēšanas kļūda Vai atvienoties no darbvirsmas? Vienlaicīgi var darboties tikai viena ierīce - Atveriet mobilajā ierīcē un skenējiet QR kodu + Gaida, kad mobilā ierīce pieslēgsies Nepareiza darbvirsmas adrese Nesaderīga darbvirsmas versija @@ -867,7 +867,7 @@ Savienots ar darbvirsmu Savienota darbvirsma Pārbaudiet kodu ar darbvirsmu - Jauna darbvirsma + Saistītās darbvirsmas Datoru Ierīces Saistīto Datoru Iestatījumi @@ -885,10 +885,10 @@ Nejaušs Ports Atvērt Portu Ugunsmūrī Atvērt Portu Ugunsmūrī Apraksts - Attālā Saimniekdatora Kļūda - Trūkst - Attālā Saimniekdatora Kļūda - Nav Aktīvs - Attālā Saimniekdatora Kļūda - Aizņemts - Attālā Saimniekdatora Kļūda - Noildze + + + + Migrate To Device Imports Neizdevās Migrate To Device Atkārtot Importu Migrate To Device Ievadiet Paroli @@ -941,9 +941,9 @@ Tūlītējas paziņojumi Pakalpojumu paziņojumi Pakalpojumu paziņojumi atslēgti - Lai saglabātu privātumu, Simplex izmanto fona pakalpojumu, nevis uznirstošos paziņojumus, tas patērē mazāk datora akumulatora. - To var atslēgt caur iestatījumiem, paziņojumi joprojām tiek rādīti. - Izslēgt akumulatora optimizāciju + + + Izslēdzot pakalpojumu un periodiskos paziņojumus Periodiskie paziņojumi Periodiskie paziņojumi atslēgti @@ -952,15 +952,15 @@ Izslēgt sistēmas ierobežojumu Atslēgt paziņojumus Sistēmas ierobežots fons - Brīdinājums par sistēmas ierobežotu fonu + Sistēmas ierobežots fons zvanā Sistēmas ierobežots fons zvanā - Brīdinājums par sistēmas ierobežotu fonu zvanā + Ievadiet paroli Ievadiet paroli Datu bāzes inicializācijas kļūda Neizdevās inicializēt datu bāzi. - Xiaomi ignorēt akumulatora optimizāciju + Simplex pakalpojumu paziņojums Simplex pakalpojumu paziņojuma teksts Zvana pakalpojumu paziņojums audio zvanam @@ -1240,12 +1240,12 @@ Kamera nav pieejama Atļauja noraidīta Augstāk minētais, tad prievārds turpinājums - Pievienot kontaktu, lai izveidotu saiti vai savienotu, izmantojot saiti - Izveidot grupu, lai izveidotu jaunu grupu + + Lai savienotu, izmantojot saiti Ja esat saņēmis simplex ielūguma saiti, varat to atvērt pārlūkā - Datorā nolasīt QR kodu no lietotnes, izmantojot QR koda nolasīšanu - Mobilajā ierīcē noklikšķiniet uz atvērt mobilajā lietotnē, tad noklikšķiniet uz savienot lietotnē + + Pieņemt savienojuma pieprasījumu? Ja izvēlēsieties noraidīt, sūtītājs netiks informēts Pieņemt kontaktu @@ -1314,9 +1314,9 @@ Jūs tiksiet savienots, kad grupas saimnieka ierīce būs tiešsaistē Jūs tiksiet savienots, kad jūsu savienojuma pieprasījums tiks pieņemts Jūs tiksiet savienots, kad jūsu kontaktu ierīce būs tiešsaistē - Ja jūs nevarat tikties klātienē, rādiet QR videozvanā vai caur citu kanālu + Jūsu čata profils tiks nosūtīts jūsu kontaktam - Ja jūs nevarat tikties klātienē, skenējiet QR videozvanā vai lūdziet ielūguma saiti + Kopīgot ielūguma saiti Ielīmējiet saiti, ko saņēmāt, lai savienotos ar savu kontaktu Uzzināt vairāk @@ -1330,19 +1330,19 @@ Jūs varat kopīgot savu adresi Jūs nezaudēsiet savus kontaktus, ja izdzēsīsiet adresi Kopīgot vienreizēju saiti ar draugu - Vienreizēju saiti var izmantot tikai ar vienu kontaktu + Jūs varat iestatīt savienojuma nosaukumu, lai atcerētos Savienojuma drošība Simplex adrese un vienreizējās saites ir drošas kopīgošanai Lai pasargātu no jūsu saites aizvietošanas, salīdziniet kodus Jūs varat pieņemt vai noraidīt savienojumu - Lasiet vairāk lietotāja rokasgrāmatā ar saiti + Adrese vai vienreizēja saite Savienoties caur saiti Savienoties Ielīmēt Šī virkne nav savienojuma saite - Jūs varat arī savienoties, noklikšķinot uz saites + Jauna saruna Jauns Pievienot kontaktu cilni @@ -1373,13 +1373,13 @@ Tīkla sesijas režīms sesija Tīkla sesijas režīms serveris Tīkla sesijas režīms entitāte - Tīkla sesijas režīms lietotājs. + Tīkla sesijas režīms sesija. Tīkla sesijas režīms serveris. - Tīkla sesijas režīms entitāte. + Atjaunināt tīkla sesijas režīmu? - Atspējot sīpolu viesus, ja nav atbalsta - Socks proxy iestatījumu ierobežojumi + + Tīkla smp proxy režīms privātā maršrutēšana Tīkla smp proxy režīms vienmēr Tīkla smp proxy režīms nezināms @@ -1569,19 +1569,19 @@ Kā darbojas SimpleX Lai aizsargātu privātumu, SimpleX izmanto ID rindām Tikai klientu ierīces glabā kontaktu grupas un e2e šifrētas ziņas - Visas ziņas un faili ir e2e šifrēti - Lasiet vairāk GitHub krātuvē. + + Izmantot čatu Ievada paziņojumu režīms Ievada paziņojumu režīma apakšvirsraksts Ievada paziņojumu režīms izslēgts Ievada paziņojumu režīms periodisks Ievada paziņojumu režīms pakalpojums - Ievada paziņojumu režīms izslēgts + Ievada paziņojumu režīms izslēgts, īss apraksts - Ievada paziņojumu režīms periodisks + Ievada paziņojumu režīms periodisks apraksts īsi - Ievada paziņojumu režīms pakalpojums + Ievada paziņojumu režīms pakalpojuma apraksts īsi Ievada paziņojumu režīms akumulators Iestatīt datu bāzes paroli @@ -1752,7 +1752,7 @@ Atslēgu glabātuve tiek droši glabāta Iestatījumi tiek glabāti parastā tekstā Šifrēts ar nejaušu frāzi - Nav iespējams atgūt frāzi + Atslēgu glabātuve ļauj saņemt ntfs Frāze tiks saglabāta iestatījumos Jums katru reizi jāievada frāze @@ -1991,8 +1991,8 @@ Operatora nosacījumi pieņemti Operatora nosacījumi pieņemti aktivizētiem operatoriem Jūsu serveri - Operatoru nosacījumi pieņemti - Operatoru nosacījumi tiks pieņemti + + Operators Operatora serveri Operators @@ -2002,17 +2002,17 @@ Operatora izmantošanas slēdzis Izmantot operatora x serverus Operatora nosacījumi neizdevās ielādēt - Operatora nosacījumi pieņemti dažiem - Operatora tie paši nosacījumi tiks piemēroti - Operatora tie paši nosacījumi tiks piemēroti operatoriem - Operatora nosacījumi tiks piemēroti - Operatora nosacījumi tiks pieņemti dažiem - Operatoru nosacījumi arī tiks piemēroti + + + + + + Skatīt nosacījumus Pieņemt nosacījumus Operatora lietošanas nosacījumi Operatora atjaunotie nosacījumi - Operatora, lai izmantotu, pieņemiet nosacījumus + Operatora izmantošana ziņām Operatora izmantošana ziņu saņemšanai Operatora izmantošana ziņu privātai maršrutēšanai @@ -2327,9 +2327,9 @@ Atsauces Atsauces uz jums sarunās. Ziņojumi - Attālinātā hosta kļūda: slikts stāvoklis - Attālinātā hosta kļūda: slikta versija - Attālinātā hosta kļūda: atslēgts + + + Attālinātā kontrole: neaktīva Attālinātā kontrole: slikts stāvoklis Attālinātā kontrole: aizņemta @@ -2341,22 +2341,22 @@ Šī funkcija ir izstrādē. Savienojiet plānu, lai savienotos ar sevi Šis ir jūsu personīgais vienreizējais saite - Jūs jau savienojaties ar %1$s + %1$s]]> Jūs jau savienojaties Jūs jau savienojaties, izmantojot šo vienreizējo saiti Šis ir jūsu personīgais simplex adrese Atkārtot savienojuma pieprasījumu Jūs jau esat pieprasījis savienojumu, izmantojot šo adresi Pievienojieties savai grupai - Šis ir jūsu saite grupai %1$s + %1$s]]> Atkārtot pievienošanās pieprasījumu Grupa jau pastāv Čats jau pastāv - Jūs jau pievienojaties grupai %1$s + %1$s]]> Jūs jau pievienojaties grupai Jūs jau pievienojaties grupai, izmantojot šo saiti - Jūs jau esat grupā %1$s - Jūs jau esat savienots ar %1$s + %1$s]]> + %1$s]]> Savienojiet, izmantojot saiti Aģenta kritiska kļūda Notikusi kritiska kļūda aģentā. @@ -2401,19 +2401,19 @@ Migrēt no ierīces, pabeigt migrāciju Migrēt no ierīces, vai dzēst arhīvu? Migrēt no ierīces, augšupielādētais arhīvs tiks dzēsts - Migrēt no ierīces, izvēlieties migrēt no citas ierīces + Migrēt no ierīces, vai kopīgot šo faila saiti Migrēt no ierīces, dzēst datu bāzi no ierīces Migrēt no ierīces, sarunas uzsākšana vairākās ierīcēs nav atbalstīta Migrēt no ierīces, uzsākt sarunu Migrēt no ierīces, migrācija pabeigta - Migrēt no ierīces, nedrīkstat uzsākt datu bāzi divās ierīcēs - Migrēt no ierīces, izmantošana divās ierīcēs pārtrauc šifrēšanu + + Migrēt no ierīces, pārbaudīt datu bāzes paroli Migrēt no ierīces, pārbaudīt paroli Migrēt no ierīces, apstipriniet, ka atceraties paroli Migrēt no ierīces, pārbaudiet savienojumu un mēģiniet vēlreiz - Migrēt no ierīces, arhīvs tiks dzēsts + Kļūda, migrējot no ierīces, pārbaudot paroli Tīkla veids: nav tīkla savienojuma Tīkla veids: mobilais diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 655b1cedbd..4ab2b10904 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -1111,7 +1111,7 @@ Delen met contacten Uw contacten blijven verbonden. Profiel update wordt naar uw contacten verzonden. - Adres delen met contacten\? + Adres delen met contacten? Stop met delen Stop met het delen van adres\? Nodig vrienden uit diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 281a734ed3..cc8b369386 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -1115,7 +1115,7 @@ Dowiedz się więcej Przestać udostępniać adres\? Automatycznie akceptuj - Udostępnić adres kontaktom\? + Udostępnić adres kontaktom? Wpisz wiadomość powitalną… (opcjonalne) Aktualizacja profilu zostanie wysłana do Twoich kontaktów. Zapisać ustawienia\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index c0bbe4d6bb..133f65edb2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -87,8 +87,7 @@ Aceitar solicitações de contato automaticamente Aparência O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis. - Uma conexão TCP separada (e credencial SOCKS) será usada para cada contato e membro do grupo. -\nAtenção: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar. + para cada contato e membro do grupo. \nAtenção: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar.]]> Bom para bateria. O aplicativo procura por mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]> chamda encerrada %1$s Converse com os desenvolvedores @@ -1150,7 +1149,7 @@ Salvar configurações de aceitação automática Abrindo banco de dados… Alterar perfis de conversa - Compartilhar endereço com os contatos\? + Compartilhar endereço com os contatos? Seus contatos continuarão conectados. Todos os dados do aplicativo serão excluídos. A senha do aplicativo é substituída por uma senha de auto-destruição. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index 2f19492237..11ca93be3b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -525,8 +525,7 @@ Negru Blocați membrul pentru toți? Atât tu, cât și contactul tău puteți șterge definitiv mesajele trimise. (24 de ore) - Folosește mai multă baterie! -\nServiciul în fundal rulează mereu – notificările sunt afișate imediat ce mesajele sunt disponibile. + Folosește mai multă baterie! \nServiciul în fundal rulează mereu – notificările sunt afișate imediat ce mesajele sunt disponibile.]]> Nu se poate trimite mesajul Șterge Confirmați fișiere de la servere necunoscute. @@ -2498,4 +2497,4 @@ Link de releu SimpleX Grup Eroare la marcarea conversației cu membrul ca fiind citită - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 5445c57055..067174ba0d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -67,7 +67,7 @@ Ошибка в ссылке контакта Пожалуйста, проверьте, что Вы использовали правильную ссылку, или попросите Ваш контакт отправить Вам новую. Ошибка соединения (AUTH) - Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой - пожалуйста, сообщите нам об этом.\nЧтобы установить соединение, попросите Ваш контакт создать ещё одну ссылку и проверьте Ваше соединение с сетью. + Возможно, Ваш контакт удалил ссылку, или она уже была использована. Если это не так, то это может быть ошибкой — пожалуйста, сообщите нам об этом.\nЧтобы установить соединение, попросите Ваш контакт создать ещё одну ссылку и проверьте Ваше соединение с сетью. Ошибка при принятии запроса на соединение Отправитель мог удалить запрос на соединение. Ошибка при удалении контакта @@ -88,7 +88,7 @@ Мгновенные уведомления! Мгновенные уведомления выключены! SimpleX выполняется в фоне вместо уведомлений через сервер.]]> - Он может быть выключен через Настройки – Вы продолжите получать уведомления о сообщениях пока приложение запущено.]]> + Он может быть выключен через Настройки — Вы продолжите получать уведомления о сообщениях пока приложение запущено.]]> Разрешите это в следующем окне, чтобы получать уведомления мгновенно.]]> Оптимизация батареи включена, поэтому сервис уведомлений выключен. Вы можете снова включить его через Настройки. Периодические уведомления @@ -157,7 +157,7 @@ Спрятать Разрешить Удалить сообщение? - Сообщение будет удалено – это действие нельзя отменить! + Сообщение будет удалено — это действие нельзя отменить! Сообщение будет помечено на удаление. Получатель(и) сможет(смогут) посмотреть это сообщение. Удалить для меня Для всех @@ -218,7 +218,7 @@ Уведомления Удалить контакт? - Контакт и все сообщения будут удалены - это действие нельзя отменить! + Контакт и все сообщения будут удалены — это действие нельзя отменить! Удалить контакт Имя контакта… Соединение с сервером установлено @@ -278,7 +278,7 @@ Отклонить Очистить чат? - Все сообщения будут удалены - это действие нельзя отменить! Сообщения будут удалены только для Вас. + Все сообщения будут удалены — это действие нельзя отменить! Сообщения будут удалены только для Вас. Очистить Очистить чат Очистить @@ -392,7 +392,7 @@ Использовать SOCKS-прокси? Соединяться с серверами через SOCKS-прокси через порт %d? Прокси должен быть запущен до включения этой опции. Использовать прямое соединение с Интернет? - Если Вы подтвердите, серверы смогут видеть Ваш IP-адрес, а провайдер - с какими серверами Вы соединяетесь. + Если Вы подтвердите, серверы смогут видеть Ваш IP-адрес, а провайдер — с какими серверами Вы соединяетесь. Использовать .onion хосты Когда возможно Нет @@ -540,16 +540,13 @@ ошибка ID сообщения повторное сообщение Пропущенные сообщения - Это может произойти, когда: -\n1. Клиент отправителя удалил неотправленные сообщения через 2 дня, или сервер – через 30 дней. -\n2. Расшифровка сообщения была невозможна, когда Вы или Ваш контакт использовали старую копию базы данных. -\n3. Соединение компроментировано. + Это может произойти, когда: \n1. Клиент отправителя удалил неотправленные сообщения через 2 дня, или сервер — через 30 дней. \n2. Расшифровка сообщения была невозможна, когда Вы или Ваш контакт использовали старую копию базы данных. \n3. Соединение компроментировано. Конфиденциальность Конфиденциальность Защитить экран приложения Автоприём изображений - Отправлять картинки ссылок + Отправлять превью ссылок Резервная копия данных ВЫ @@ -630,10 +627,10 @@ Сменить пароль Пожалуйста, введите правильный пароль. База данных НЕ зашифрована. Установите пароль, чтобы защитить Ваши данные. - Android Keystore используется для безопасного хранения пароля - это позволяет стабильно получать уведомления в фоновом режиме. + Android Keystore используется для безопасного хранения пароля — это позволяет стабильно получать уведомления в фоновом режиме. База данных зашифрована случайным паролем, Вы можете его поменять. Внимание: Вы не сможете восстановить или поменять пароль, если потеряете его.]]> - Пароль базы данных будет безопасно сохранён в Android Keystore после запуска чата или изменения пароля - это позволит стабильно получать уведомления. + Пароль базы данных будет безопасно сохранён в Android Keystore после запуска чата или изменения пароля — это позволит стабильно получать уведомления. Пароль не сохранён на устройстве — Вы будете должны ввести его при каждом запуске чата. Зашифровать базу данных? Сменить пароль базы данных? @@ -757,8 +754,8 @@ Вы: %1$s Удалить группу Удалить группу? - Группа будет удалена для всех членов - это действие нельзя отменить! - Группа будет удалена для Вас - это действие нельзя отменить! + Группа будет удалена для всех членов — это действие нельзя отменить! + Группа будет удалена для Вас — это действие нельзя отменить! Выйти из группы Редактировать профиль группы Ссылка группы @@ -777,7 +774,7 @@ Удалить члена группы Отправить сообщение - Член группы будет удалён - это действие нельзя отменить. + Член группы будет удалён — это действие нельзя отменить. Удалить ЧЛЕН ГРУППЫ Роль @@ -801,7 +798,7 @@ Переключить адрес получения Создать скрытую группу - Группа полностью децентрализована – она видна только членам. + Группа полностью децентрализована — она видна только членам. Введите имя группы: Полное имя: Ваш профиль чата будет отправлен членам группы @@ -887,7 +884,7 @@ Когда приложение запущено Периодически Мгновенно - Больше расход батареи! Приложение постоянно запущено в фоне - уведомления будут показаны сразу же.]]> + Больше расход батареи! Приложение постоянно запущено в фоне — уведомления будут показаны сразу же.]]> Меньше расход батареи. Приложение проверяет сообщения каждые 10 минут. Вы можете пропустить звонки и срочные сообщения.]]> Как это влияет на потребление энергии LIVE @@ -930,7 +927,7 @@ %dд %d нед. %d дней - Чтобы подтвердить безопасность end-to-end шифрования с Вашим контактом сравните (или сканируйте) код на ваших устройствах. + Чтобы подтвердить безопасность сквозного шифрования с Вашим контактом сравните (или сканируйте) код на ваших устройствах. %s подтверждён %s не подтверждён Код безопасности @@ -982,7 +979,7 @@ Сообщения Серверы для новых соединений Вашего текущего профиля чата Профили - Все чаты и сообщения будут удалены - это нельзя отменить! + Все чаты и сообщения будут удалены — это нельзя отменить! Сборка приложения: %s Версия приложения: v%s для каждого контакта и члена группы. \nОбратите внимание: если у Вас много контактов, потребление батареи и трафика может быть значительно выше, и некоторые соединения могут не работать.]]> @@ -998,14 +995,14 @@ У Вас уже есть профиль с таким именем. Пожалуйста, выберите другое имя. Ошибка выбора профиля! По профилю чата или по соединению (БЕТА) - Благодаря пользователям – добавьте переводы через Weblate! + Благодаря пользователям — добавьте переводы через Weblate! Разные имена, аватары и транспортные сессии. Итальянский интерфейс Черновик сообщения Много профилей чата Сохранить последний черновик, вместе с вложениями. Защищённые имена файлов - Благодаря пользователям – добавьте переводы через Weblate! + Благодаря пользователям — добавьте переводы через Weblate! Чтобы защитить Ваш часовой пояс, файлы картинок и голосовых сообщений используют UTC. Французский интерфейс Дополнительные улучшения скоро! @@ -1058,9 +1055,9 @@ Сохранить приветственное сообщение\? Установите приветственное сообщение для новых членов группы. Нажмите на профиль, чтобы переключиться на него. - Благодаря пользователям - добавьте переводы через Weblate! + Благодаря пользователям — добавьте переводы через Weblate! Вы всё равно получите звонки и уведомления в профилях без звука, когда они активные. - Вы можете скрыть или отключить уведомления профиля - нажмите и удерживайте профиль, чтобы открыть меню. + Вы можете скрыть или отключить уведомления профиля — нажмите и удерживайте профиль, чтобы открыть меню. Изображение будет принято когда Ваш контакт его загрузит. Файл будет принят когда Ваш контакт загрузит его. Обновление базы данных @@ -1167,13 +1164,13 @@ Быстрые и не нужно ждать, когда отправитель онлайн! Польский интерфейс Установите код вместо системной аутентификации. - Благодаря пользователям – добавьте переводы через Weblate! + Благодаря пользователям — добавьте переводы через Weblate! Видео и файлы до 1гб Код доступа в приложение Файл будет удалён с серверов. Создание файла %d минут(ы) - Пожалуйста, запомните или сохраните его - восстановить потерянный код доступа невозможно! + Пожалуйста, запомните или сохраните его — восстановить потерянный код доступа невозможно! Отозвать файл Остановить приём файла\? Вы можете включить Блокировку SimpleX через Настройки. @@ -1231,14 +1228,14 @@ Запретить реакции на сообщения. секунд ЦВЕТА ИНТЕРФЕЙСА - Поделиться адресом с контактами\? + Поделиться адресом с контактами? Обновлённый профиль будет отправлен Вашим контактам. Об адресе SimpleX Узнать больше Если Вы не можете встретиться лично, покажите QR-код во время видеозвонка или поделитесь ссылкой. Чтобы соединиться с Вами, Ваш контакт может отсканировать QR-код или использовать ссылку в приложении. Вы не потеряете контакты, если позже удалите Ваш адрес. - Вы можете поделиться своим адресом в виде ссылки или QR-кода - любой может соединиться с Вами. + Вы можете поделиться своим адресом в виде ссылки или QR-кода — любой может соединиться с Вами. Руководстве пользователя.]]> Ваши контакты сохранятся. Настроить тему @@ -1249,7 +1246,7 @@ Поделиться с контактами Прекратить делиться адресом\? Автоприём - Введите приветственное сообщение... (по желанию) + Введите приветственное сообщение… (по желанию) Сохранить настройки\? Прекратить делиться Продолжить @@ -1293,7 +1290,7 @@ минут месяцев Выбрать - Благодаря пользователям – добавьте переводы через Weblate! + Благодаря пользователям — добавьте переводы через Weblate! недель 1 минута 30 секунд @@ -1435,7 +1432,7 @@ Расход батареи приложением / Без ограничений в настройках приложения.]]> База данных будет зашифрована, и пароль сохранён в настройках. Шифруйте сохранённые файлы и медиа - Обратите внимание: соединение с серверами файлов и сообщений устанавливаются через SOCKS-прокси. Звонки и картинки ссылок используют прямое соединение.]]> + Обратите внимание: соединение с серверами файлов и сообщений устанавливаются через SOCKS-прокси. Звонки и превью ссылок используют прямое соединение.]]> Шифровать локальные файлы Приложение для компьютера! 6 новых языков интерфейса @@ -1451,7 +1448,7 @@ Установить пароль базы данных Установить пароль базы данных Открыть директорию базы данных - Арабский, болгарский, финский, иврит, тайский и украинский - благодаря пользователям и Weblate. + Арабский, болгарский, финский, иврит, тайский и украинский — благодаря пользователям и Weblate. Создайте новый профиль в приложении для компьютера. 💻 Пароль будет сохранён в настройках как простой текст после того, как вы его измените или перезапустите приложение. Установите режим Инкогнито при соединении. @@ -1604,7 +1601,7 @@ Создать группу: создать новую группу.]]> Не отправлять историю новым членам группы. Отправить до 100 последних сообщений новым членам группы. - Все сообщения будут удалены - это нельзя отменить! + Все сообщения будут удалены — это нельзя отменить! Камера недоступна Код доступа в приложение Добавить контакт: создать новую ссылку-приглашение или подключиться через полученную ссылку.]]> @@ -1695,13 +1692,13 @@ Ошибка при блокировании члена группы для всех Разблокировать члена группы для всех? Вы заблокировали %s - end-to-end шифрованием с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> - Чат защищён end-to-end шифрованием. - Чат защищён квантово-устойчивым end-to-end шифрованием. + сквозным шифрованием с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> + Чат защищён сквозным шифрованием. + Чат защищён квантово-устойчивым сквозным шифрованием. Открыть экран миграции Миграция с другого устройства Установить пароль - стандартное end-to-end шифрование + стандартное сквозное шифрование Приветственное сообщение слишком длинное Сообщение слишком большое Повторить загрузку @@ -1764,7 +1761,7 @@ Обратите внимание: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений.]]> Внимание: архив будет удален.]]> Подтвердите настройки сети - квантово-устойчивым end-to-end шифрованием с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> + квантово-устойчивым сквозным шифрованием с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]> Мигрировать сюда Мигрировать на другое устройство Мигрируйте на другое устройство через QR-код. @@ -1844,8 +1841,8 @@ Улучшенная доставка сообщений Уменьшенный расход батареи. Версия сервера несовместима с настройками сети. - Неверный ключ или неизвестное соединение - скорее всего, это соединение удалено. - Превышено количество сообщений - предыдущие сообщения не доставлены. + Неверный ключ или неизвестное соединение — скорее всего, это соединение удалено. + Превышено количество сообщений — предыдущие сообщения не доставлены. Ошибка сервера получателя: %1$s Ошибка: %1$s Пересылающий сервер: %1$s @@ -1853,7 +1850,7 @@ Пересылающий сервер: %1$s \nОшибка: %2$s Предупреждение доставки сообщения - Ошибка сети - сообщение не было отправлено после многократных попыток. + Ошибка сети — сообщение не было отправлено после многократных попыток. Адрес сервера несовместим с настройками сети. информация сервера об очереди: %1$s \n @@ -1945,7 +1942,7 @@ Пересылающий сервер %1$s не смог подключиться к серверу назначения %2$s. Попробуйте позже. Версия пересылающего сервера несовместима с настройками сети: %1$s. Версия сервера назначения %1$s несовместима с пересылающим сервером %2$s. - Неверный ключ или неизвестный адрес блока файла - скорее всего, файл удален. + Неверный ключ или неизвестный адрес блока файла — скорее всего, файл удален. Выбранные настройки чата запрещают это сообщение. Ошибка файла Сканировать QR-код/ Вставить ссылку @@ -1966,7 +1963,7 @@ Приём сообщений В ожидании Загружено - Статистика серверов будет сброшена - это нельзя отменить! + Статистика серверов будет сброшена — это нельзя отменить! Всего отправлено Переподключить SMP-сервер @@ -2109,7 +2106,7 @@ Состояние соединения и серверов. Удаляйте до 20 сообщений за раз. Загрузка обновления, не закрывайте приложение. - Файл не найден - скорее всего, файл был удален или отменен. + Файл не найден — скорее всего, файл был удален или отменен. Адрес пересылающего сервера несовместим с настройками сети: %1$s. написать Сообщение @@ -2157,7 +2154,7 @@ Проверьте правильность ссылки SimpleX. Неверная ссылка БАЗА ДАННЫХ - Ошибка инициализации WebView. Убедитесь, что у вас установлен WebView и его поддерживаемая архитектура – arm64.\nОшибка: %s + Ошибка инициализации WebView. Убедитесь, что у вас установлен WebView и его поддерживаемая архитектура — arm64.\nОшибка: %s Звук отключен Сообщения будут удалены — это нельзя отменить! Ошибка переключения профиля @@ -2252,7 +2249,7 @@ Настройки адреса Добавьте сотрудников в разговор. Бизнес адрес - end-to-end шифрованием, с пост-квантовой безопасностью в прямых разговорах.]]> + сквозным шифрованием, с пост-квантовой безопасностью в прямых разговорах.]]> Приложение всегда выполняется в фоне Проверять сообщения каждые 10 минут Без фонового сервиса @@ -2272,7 +2269,7 @@ Удалить разговор Удалить разговор? Пригласить в разговор - Разговор будет удален для всех участников - это действие нельзя отменить! + Разговор будет удален для всех участников — это действие нельзя отменить! Оператор %s серверы %s.]]> @@ -2284,12 +2281,12 @@ %s.]]> Или импортировать файл архива Доступная панель чата - Разговор будет удален для Вас - это действие нельзя отменить! + Разговор будет удален для Вас — это действие нельзя отменить! Покинуть разговор Только владельцы разговора могут поменять предпочтения. Текст условий использования не может быть показан, вы можете посмотреть их через ссылку: Разговор - Участник будет удалён из разговора - это действие нельзя отменить. + Участник будет удалён из разговора — это действие нельзя отменить. Серверы по умолчанию Роль будет изменена на %s. Все участники разговора получат уведомление. Ваш профиль будет отправлен участникам разговора. @@ -2312,7 +2309,7 @@ Конфиденциальность для ваших покупателей. %1$s.]]> Разговор уже существует! - только с одним контактом - поделитесь при встрече или через любой мессенджер.]]> + только с одним контактом — поделитесь при встрече или через любой мессенджер.]]> Нет серверов для доставки сообщений. Вы можете настроить серверы позже. SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение. @@ -2353,7 +2350,7 @@ Добавить в список Изменить список Сохранить список - Имя списка... + Имя списка… Исправить соединение? Соединение требует повторного согласования шифрования. Исправление @@ -2365,7 +2362,7 @@ Пожаловаться Спам Пожаловаться на спам: увидят только модераторы группы. - Это действие не может быть отмененено - сообщения, отправленные и полученные в этом чате ранее чем выбранное, будут удалены + Это действие не может быть отмененено — сообщения, отправленные и полученные в этом чате ранее чем выбранное, будут удалены Получайте уведомления от упоминаний. Сообщения о нарушениях запрещены в этой группе. Пожаловаться на нарушение: увидят только модераторы группы. @@ -2455,12 +2452,12 @@ Сообщения от этих членов группы будут показаны! Все новые сообщения от этих членов группы будут скрыты! Заблокировать членов группы для всех? - Члены группы будут удалены - это действие нельзя отменить! - Участники будут удалены из разговора - это действие нельзя отменить! + Члены группы будут удалены — это действие нельзя отменить! + Участники будут удалены из разговора — это действие нельзя отменить! модераторы Удалить членов группы? Принять - Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей – не отправлять спам. + Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей — не отправлять спам. Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. Настроить операторов серверов Политика конфиденциальности и условия использования. @@ -2522,7 +2519,7 @@ Добавить сообщение О себе: Нельзя поменять профиль - end-to-end шифрованием.]]> + сквозным шифрованием.]]> только после того как Ваш запрос будет принят.]]> Чат с админами Общайтесь с членами группы до того как принять их. @@ -2560,10 +2557,10 @@ Обновить ссылку группы? Обновить Обновить адрес? - Цель: + Описание: Фоновый таймаут TCP-соединения Отправитель не будет уведомлён. - Член группы удалён - невозможно принять запрос + Член группы удалён — невозможно принять запрос Чтобы использовать другой профиль после попытки соединения, удалите чат и используйте ссылку снова. Приветственное сообщение О себе: @@ -2572,8 +2569,8 @@ Использовать профиль инкогнито 4 новых языков интерфейса Принять запрос на соединение - Бизнес контакт - Каталонский, Индонезийский, Румынский и Вьетнамский - благодаря нашим пользователям! + Бизнес-контакт + Каталонский, Индонезийский, Румынский и Вьетнамский — благодаря нашим пользователям! Создайте Ваш адрес Описание слишком длинное Включите исчезающие сообщения по умолчанию. @@ -2592,7 +2589,7 @@ Обновите Ваш адрес Обновить ссылку группы Приветствуйте Ваши контакты 👋 - Ваш бизнес контакт + Ваш бизнес-контакт Ваш контакт Ваша группа Разрешить файлы и медиа, только если их разрешает Ваш контакт. @@ -2618,7 +2615,7 @@ Удалить сообщения участника Удалить сообщения участника? Удалить сообщения - Сообщения участника будут удалены - это действие не обратимо! + Сообщения участника будут удалены — это действие не обратимо! нет подписки Вы не подключенны к серверу через который Вы получали сообщения от этого контакта (без подписки). Удалить члена группы и удалить сообщения @@ -2634,4 +2631,127 @@ Поиск голосовых сообщений Видео Голосовые сообщения + %1$d подписчик + %1$d подписчиков + Отменить создание канала? + %1$s!]]> + канал + Канал + Канал + Полное имя канала: + Ссылка канала + Участники канала + Имя канала + профиль канала обновлён + Канал будет удалён для всех подписчиков — это действие нельзя отменить! + Канал будет удалён для вас — это действие нельзя отменить! + Настроить ретрансляторы + Соединиться + соединено + соединяется + Создать публичный канал + Создать публичный канал + Создать публичный канал (БЕТА) + Создание канала + %d событий канала + Удалить канал + Удалить канал? + Удалить ретранслятор + Редактировать профиль канала + Введите имя ретранслятора… + Ошибка при добавлении ретранслятора + Ошибка при создании канала + Ошибка при открытии канала + ошибка: %s + Ошибка при сохранении профиля канала + Неверный адрес ретранслятора! + Неверное имя ретранслятора! + приглашён + Присоединиться к каналу + Покинуть канал + Покинуть канал? + Ссылка + %1$d/%2$d ретрансляторов активно + %1$d/%2$d ретрансляторов соединено + %1$d/%2$d ретрансляторов соединено, %3$d ошибок + принят + активен + Заблокировать подписчика для всех? + Вещание + Отмена + Канал начнёт работу с %1$d из %2$d ретрансляторов. Продолжить? + Ретранслятор чатов + Ретрансляторы чатов + Ретрансляторы чатов + Ретрансляторы чатов + Ретрансляторы чатов пересылают сообщения в созданных Вами каналах. + Ретрансляторы чатов пересылают сообщения подписчикам канала. + Проверьте адрес ретранслятора и попробуйте ещё раз. + Проверьте имя ретранслятора и попробуйте ещё раз. + удалён + Настройки канала + Ссылка канала + Профиль канала хранится на устройствах подписчиков и ретрансляторах чатов. + Канал временно недоступен + Отключить + Включить + Ошибка + Бизнес-адрес + не может вещать + Включите хотя бы один ретранслятор чатов для создания канала. + Новый ретранслятор чатов + Нет ретрансляторов чатов + Ретрансляторы чатов не включены. + Это адрес ретранслятора чатов, к нему нельзя подключиться. + %1$d/%2$d ретрансляторов активно, %3$d ошибок + %1$d/%2$d ретрансляторов активно, %3$d не работает + %1$d/%2$d ретрансляторов активно, %3$d удалено + %1$d/%2$d ретрансляторов соединено, %3$d не работают + %1$d/%2$d ретрансляторов соединено, %3$d удалено + %1$d ретрансляторов не работает + %1$d ретрансляторов неактивно + %1$d ретрансляторов удалено + Нет доступных ретрансляторов + В канале нет активных ретрансляторов. Пожалуйста, попробуйте позже. + Включить превью ссылок? + Каналы + удалил(а) канал + Контакт-адрес + Все ретрансляторы удалены + неактивен + Подлинность ссылки подтверждена. + Открыть канал + Открыть новый канал + Владельцы + ВЛАДЕЛЕЦ + Продолжить + ретранслятор + РЕТРАНСЛЯТОР + Адрес ретранслятора + Адрес ретранслятора + Ошибка подключения ретранслятора + Ссылка ретранслятора + Результаты ретранслятора: + удалён оператором + Удалить подписчика + Удалить подписчика? + Сохранить и уведомить подписчиков канала + Сохранить профиль канала + Отправка превью ссылки может привести к раскрытию вашего IP-адреса веб-сайту. Вы можете изменить это позже в настройках конфиденциальности. + Отправьте ссылку через любой мессенджер — это безопасно. Попросите вставить её в SimpleX. + Сервер требует авторизации для подключения к ретранслятору, проверьте пароль. + Предупреждение сервера + Поделиться каналом… + Поделиться адресом ретранслятора + Поделиться в чате + ⚠️ Проверка подписи не удалась: %s. + ПОДПИСЧИК + Подписчики + Подписчик будет удален из канала — это действие нельзя отменить! + Пообщайтесь с кем-нибудь + Нажмите «Присоединиться к каналу» + Нажмите, чтобы открыть + Вы можете поделиться ссылкой или QR-кодом — любой желающий сможет присоединиться к каналу. + Изменить настройки канала могут только владельцы канала. + Одноразовая ссылка diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 411cbde4c4..2fe2000fb3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -909,7 +909,7 @@ แสดง: แสดงตัวเลือกสําหรับนักพัฒนาซอฟต์แวร์ แชร์ลิงก์ - แชร์ที่อยู่กับผู้ติดต่อ\? + แชร์ที่อยู่กับผู้ติดต่อ? แชร์กับผู้ติดต่อ บันทึกการตั้งค่า\? แสดง @@ -1328,4 +1328,4 @@ ในการตอบกลับถึง ไม่มีประวัติ encryptionใช้ได้ - + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index be037e8b87..b189ee581d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -1207,7 +1207,7 @@ 消息回应 该聊天禁用了消息回应。 如果你在打开应用程序时输入自毁密码: - 个人资料更新将被发送给你的联系人。 + 个人资料更新将发送给你的联系人。 记录更新于 禁止消息回应。 已收到于 @@ -1848,7 +1848,7 @@ 尚无直接连接,消息由管理员转发。 其他 SMP 服务器 其他 XFTP 服务器 - 扫描/粘贴链接 + 粘贴链接/扫描 显示百分比 不活跃 缩放 @@ -2581,9 +2581,9 @@ 连接 已连接 正在连接 - 创建频道 - 创建频道 - 创建频道(测试版) + 创建公开频道 + 创建公开频道 + 创建公开频道(测试版) 正在创建频道 解码链接 删除频道 @@ -2651,8 +2651,87 @@ 你通过此中继链接连接至该频道。 你的频道 你的频道 - 你的个人资料 %1$s 将分享给频道中继和订阅者。 + 你的个人资料 %1$s 将分享给频道中继和订阅者。中继可以访问频道消息。 你的中继地址 你的中继名 你会停止收到来自该频道的消息。聊天记录将被保留。 + 完整的频道名: + 频道资料存储在订阅者设备和聊天中继上。 + 频道资料已更新 + %d 个频道事件 + 删除了频道 + 被丢弃 (%1$d 次尝试) + 错误: %s + 保存频道资料出错 + 消息错误 + 保存并通知频道订阅者 + 保存频道资料 + 应用在尝试接收这条消息 %1$d 次后删除了它。 + 更新了频道资料 + %2$d 个中继中的 %1$d 个活跃, %3$d 个错误 + %2$d 个中继中的 %1$d 个活跃, %3$d 个被删除 + %2$d 个中继中的 %1$d 个已连接, %3$d 个失灵 + %2$d 个中继中的 %1$d 个已连接, %3$d 个被删除 + %1$d 个中继失灵 + %1$d 个中继不活跃 + 删除了 %1$d 个中继 + 目前不支持添加中继。 + 所有中继均失灵 + 删除了所有中继 + 无法广播 + 频道无活跃中继。请稍后尝试加入。 + 频道暂时不可用 + 不活跃 + 无活跃中继 + 被运营方删除 + 正等到频道所有者添加中继。 + 营业地址 + 频道链接 + 联系地址 + 分享频道出错 + (来自所有者) + 群链接 + 链接签名已验证。 + 一次性链接 + 分享频道… + 经聊天分享 + ⚠️ 签名验证失败:%s。 + (已签名) + 轻触打开 + 发送链接预览可能会将你的 IP 地址暴露给网站。你可以稍后在“隐私”设置中更改此设置。 + 连接达到了未送达消息的上限 + 禁用 + 启用 + 启用链接预览吗? + 错误 + 网络错误 + 中继结果: + 频道首选项 + 仅频道所有者可改变频道首选项。 + 用于单人进行连接的链接 + 频道 + 通过链接或二维码连接 + 和某人连接 + 创建你的公开地址 + 邀请好友更简单 👋 + 给任何要和你联系的人 + 私下邀请某人 + 让某人和你连接 + 新建一次性链接 + 非盈利治理 + - 可选发送链接预览\n- 防止超链接钓鱼\n- 删除链接跟踪。 + 面对面或通过视频通话展示二维码。 + 或使用此二维码 — 打印或在线展示。 + 所有权:你可以运行自己的中继。 + 隐私:对所有者和订阅者。 + 公开频道 — 畅所欲言 🚀 + 可靠性:一个频道众多中继。 + 安全的 web 链接 + 安全性:所有者持有频道密钥。 + 通过任何通讯应用发送链接 — 这是安全的。请求粘贴到 SimpleX 中。 + 和某人交谈 + 让 SimpleX 网络持续。 + 在社交媒体资料、网站或电子邮件签名中使用该地址。 + 我们让连接对新用户更简单。 + 你的公开地址 From 0ff297b3b7eaf9285f7ee0435a10e1e672d19f5f Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Sat, 25 Apr 2026 10:47:41 +0000 Subject: [PATCH 19/77] simplex-chat-nodejs: bump types and nodejs vesions (#6884) * simplex-chat-nodejs: bump types and nodejs vesions * update README --- packages/simplex-chat-client/types/typescript/package.json | 2 +- packages/simplex-chat-nodejs/README.md | 2 +- packages/simplex-chat-nodejs/package.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/simplex-chat-client/types/typescript/package.json b/packages/simplex-chat-client/types/typescript/package.json index 8d05eb460c..329003a6b8 100644 --- a/packages/simplex-chat-client/types/typescript/package.json +++ b/packages/simplex-chat-client/types/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@simplex-chat/types", - "version": "0.4.0", + "version": "0.5.0", "description": "TypeScript types for SimpleX Chat bot libraries", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/simplex-chat-nodejs/README.md b/packages/simplex-chat-nodejs/README.md index e3363df789..2132c47a79 100644 --- a/packages/simplex-chat-nodejs/README.md +++ b/packages/simplex-chat-nodejs/README.md @@ -14,7 +14,7 @@ Please share your use cases and implementations. ## Quick start: a simple bot ``` -npm i simplex-chat@6.5.0-beta.4.4 +npm i simplex-chat@6.5.0-beta.10 ``` Simple bot that replies with squares of numbers you send to it: diff --git a/packages/simplex-chat-nodejs/package.json b/packages/simplex-chat-nodejs/package.json index 6b340dc1f9..79dd1c3c3c 100644 --- a/packages/simplex-chat-nodejs/package.json +++ b/packages/simplex-chat-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "simplex-chat", - "version": "6.5.0-beta.4.6", + "version": "6.5.0-beta.10", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ @@ -24,7 +24,7 @@ "docs": "typedoc" }, "dependencies": { - "@simplex-chat/types": "^0.4.0", + "@simplex-chat/types": "^0.5.0", "extract-zip": "^2.0.1", "fast-deep-equal": "^3.1.3", "node-addon-api": "^8.5.0" From ea6a09b66e3dfe8e12ab370da8fc39a66b94085e Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Sat, 25 Apr 2026 14:59:42 +0000 Subject: [PATCH 20/77] ui: open external link alerts (#6860) * ui: open external link alerts * update * update * update * update * update * change link, add link to alert, close modals when opening chat * refactor * add string * fix link in terms * open simplex chat links from privacy policy in app --------- Co-authored-by: Evgeny Poberezkin Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../ios/Shared/Views/Helpers/ShareSheet.swift | 39 +++++++++++++++++++ .../Views/NewChat/AddContactLearnMore.swift | 2 +- .../Shared/Views/Onboarding/HowItWorks.swift | 2 +- .../Views/Onboarding/WhatsNewView.swift | 2 +- .../Views/UserSettings/DeveloperView.swift | 18 +++++---- .../Views/UserSettings/IncognitoHelp.swift | 2 +- .../NetworkAndServers/ConditionsWebView.swift | 6 +-- .../NetworkAndServers/NetworkAndServers.swift | 2 +- .../NetworkAndServers/OperatorView.swift | 10 ++--- .../ProtocolServersView.swift | 4 +- .../Views/UserSettings/RTCServers.swift | 4 +- .../Views/UserSettings/SettingsView.swift | 24 +++++++----- .../UserSettings/UserAddressLearnMore.swift | 2 +- .../simplex/common/views/helpers/Utils.kt | 14 +++++++ .../common/views/newchat/ConnectPlan.kt | 1 + .../common/views/onboarding/HowItWorks.kt | 2 +- .../common/views/onboarding/WhatsNewView.kt | 4 +- .../common/views/usersettings/RTCServers.kt | 2 +- .../common/views/usersettings/SettingsView.kt | 8 ++-- .../networkAndServers/NetworkAndServers.kt | 2 +- .../networkAndServers/OperatorView.kt | 16 +++----- .../networkAndServers/ProtocolServersView.kt | 2 +- .../commonMain/resources/MR/base/strings.xml | 1 + 23 files changed, 109 insertions(+), 60 deletions(-) diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 8b982ec0b7..3adf3f6a64 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -86,6 +86,45 @@ func showSheet( } } +func openExternalLink(_ url: URL) { + let s = url.absoluteString + if s.starts(with: "https://simplex.chat/contact#") || (s.starts(with: "https://smp") && s.contains(".simplex.im/a#")) { + ChatModel.shared.appOpenUrl = url + } else { + showAlert( + title: NSLocalizedString("Open external link?", comment: "alert title"), + message: s, + buttonTitle: NSLocalizedString("Open", comment: "alert button"), + buttonAction: { UIApplication.shared.open(url) }, + cancelButton: true + ) + } +} + +struct ExternalLink: View { + let destination: URL + let label: Label + + init(destination: URL, @ViewBuilder label: () -> Label) { + self.destination = destination + self.label = label() + } + + init(_ titleKey: LocalizedStringKey, destination: URL) where Label == Text { + self.destination = destination + self.label = Text(titleKey) + } + + init(_ title: S, destination: URL) where Label == Text { + self.destination = destination + self.label = Text(title) + } + + var body: some View { + Button { openExternalLink(destination) } label: { label } + } +} + let okAlertAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default) let cancelAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) diff --git a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift index 3a64a955c5..6add190b88 100644 --- a/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift +++ b/apps/ios/Shared/Views/NewChat/AddContactLearnMore.swift @@ -26,7 +26,7 @@ struct AddContactLearnMore: View { VStack(alignment: .leading, spacing: 18) { Text("To connect, your contact can scan QR code or use the link in the app.") Text("If you can't meet in person, show QR code in a video call, or share the link.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).") + ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/readme.html#connect-to-friends")!) } .frame(maxWidth: .infinity, alignment: .leading) .listRowBackground(Color.clear) diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index 263b55a42d..c881146ef5 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -28,7 +28,7 @@ struct HowItWorks: View { Text("Only client devices store user profiles, contacts, groups, and messages.") Text("All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.") if !onboarding { - Text("Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme).") + ExternalLink("Read more in our GitHub repository.", destination: URL(string: "https://github.com/simplex-chat/simplex-chat#readme")!) } } .padding(.bottom) diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index b7249f42ea..8572022ceb 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -791,7 +791,7 @@ struct WhatsNewView: View { } } if let post = v.post { - Link(destination: post) { + ExternalLink(destination: post) { HStack { Text("Read more") Image(systemName: "arrow.up.right.circle") diff --git a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift index 184b03e679..a504b00116 100644 --- a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift +++ b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift @@ -22,14 +22,16 @@ struct DeveloperView: View { VStack { List { Section { - ZStack(alignment: .leading) { - Image(colorScheme == .dark ? "github_light" : "github") - .resizable() - .frame(width: 24, height: 24) - .opacity(0.5) - .colorMultiply(theme.colors.secondary) - Text("Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)") - .padding(.leading, 36) + ExternalLink(destination: URL(string: "https://github.com/simplex-chat/simplex-chat")!) { + ZStack(alignment: .leading) { + Image(colorScheme == .dark ? "github_light" : "github") + .resizable() + .frame(width: 24, height: 24) + .opacity(0.5) + .colorMultiply(theme.colors.secondary) + Text("Install SimpleX Chat for terminal") + .padding(.leading, 36) + } } NavigationLink { TerminalView() diff --git a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift index d9862aaac8..f74516c2c8 100644 --- a/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift +++ b/apps/ios/Shared/Views/UserSettings/IncognitoHelp.swift @@ -23,7 +23,7 @@ struct IncognitoHelp: View { Text("Incognito mode protects your privacy by using a new random profile for each contact.") Text("It allows having many anonymous connections without any shared data between them in a single chat profile.") Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).") + ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode")!) } .listRowBackground(Color.clear) .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift index 6f76e69182..5abbbf8d2e 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift @@ -71,11 +71,7 @@ struct ConditionsWebView: UIViewRepresentable { switch navigationAction.navigationType { case .linkActivated: decisionHandler(.cancel) - if url.absoluteString.starts(with: "https://simplex.chat/contact#") { - ChatModel.shared.appOpenUrl = url - } else { - UIApplication.shared.open(url) - } + openExternalLink(url) default: decisionHandler(.allow) } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index 74b7374654..c1f2470669 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -332,7 +332,7 @@ struct UsageConditionsView: View { @ViewBuilder private func conditionsDiffButton(_ font: Font? = nil) -> some View { let commit = ChatModel.shared.conditions.currentConditions.conditionsCommit if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") { - Link(destination: commitUrl) { + ExternalLink(destination: commitUrl) { HStack { Text("Open changes") Image(systemName: "arrow.up.right.circle") diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index 9d068d3b26..b54fa396a7 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -364,11 +364,11 @@ struct OperatorInfoView: View { Text(d) } } - Link(serverOperator.info.website.absoluteString, destination: serverOperator.info.website) + ExternalLink(serverOperator.info.website.absoluteString, destination: serverOperator.info.website) } if let selfhost = serverOperator.info.selfhost { Section { - Link(selfhost.text, destination: selfhost.link) + ExternalLink(selfhost.text, destination: selfhost.link) } } } @@ -432,7 +432,7 @@ struct ConditionsTextView: View { private func conditionsLinkView(_ conditionsLink: String) -> some View { VStack(alignment: .leading, spacing: 20) { Text("Current conditions text couldn't be loaded, you can review conditions via this link:") - Link(destination: URL(string: conditionsLink)!) { + ExternalLink(destination: URL(string: conditionsLink)!) { Text(conditionsLink) .multilineTextAlignment(.leading) } @@ -591,11 +591,11 @@ func conditionsLinkButton() -> some View { let commit = ChatModel.shared.conditions.currentConditions.conditionsCommit let mdUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/\(commit)/PRIVACY.md") ?? conditionsURL return Menu { - Link(destination: mdUrl) { + ExternalLink(destination: mdUrl) { Label("Open conditions", systemImage: "doc") } if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") { - Link(destination: commitUrl) { + ExternalLink(destination: commitUrl) { Label("Open changes", systemImage: "ellipsis") } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift index e57df4c5dc..b059be7cb0 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift @@ -223,9 +223,7 @@ struct YourServersView: View { func howToButton() -> some View { Button { - DispatchQueue.main.async { - UIApplication.shared.open(howToUrl) - } + openExternalLink(howToUrl) } label: { HStack { Text("How to use your servers") diff --git a/apps/ios/Shared/Views/UserSettings/RTCServers.swift b/apps/ios/Shared/Views/UserSettings/RTCServers.swift index ef891738cc..b045a8ce55 100644 --- a/apps/ios/Shared/Views/UserSettings/RTCServers.swift +++ b/apps/ios/Shared/Views/UserSettings/RTCServers.swift @@ -139,9 +139,7 @@ struct RTCServers: View { func howToButton() -> some View { Button { - DispatchQueue.main.async { - UIApplication.shared.open(howToUrl) - } + openExternalLink(howToUrl) } label: { HStack{ Text("How to") diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 65e34a0ac5..a903329454 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -11,7 +11,7 @@ import SwiftUI import StoreKit import SimpleXChat -let simplexTeamURL = URL(string: "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D")! +let simplexTeamURL = URL(string: "simplex:/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw?h=smp6.simplex.im")! let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String @@ -399,7 +399,9 @@ struct SettingsView: View { } Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) { - settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") } + settingsRow("keyboard", color: theme.colors.secondary) { + ExternalLink("Contribute", destination: URL(string: "https://github.com/simplex-chat/simplex-chat#contribute")!) + } settingsRow("star", color: theme.colors.secondary) { Button("Rate the app") { if let scene = sceneDelegate.windowScene { @@ -407,14 +409,16 @@ struct SettingsView: View { } } } - ZStack(alignment: .leading) { - Image(colorScheme == .dark ? "github_light" : "github") - .resizable() - .frame(width: 24, height: 24) - .opacity(0.5) - .colorMultiply(theme.colors.secondary) - Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)") - .padding(.leading, indent) + ExternalLink(destination: URL(string: "https://github.com/simplex-chat/simplex-chat")!) { + ZStack(alignment: .leading) { + Image(colorScheme == .dark ? "github_light" : "github") + .resizable() + .frame(width: 24, height: 24) + .opacity(0.5) + .colorMultiply(theme.colors.secondary) + Text("Star on GitHub") + .padding(.leading, indent) + } } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift index 6c1ea8deb2..ac6ae05984 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressLearnMore.swift @@ -31,7 +31,7 @@ struct UserAddressLearnMore: View { .padding(.top) Text("SimpleX address and 1-time links are safe to share via any messenger.") Text("To protect against your link being replaced, you can compare contact security codes.") - Text("Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).") + ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses")!) .padding(.top) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index c4821d1a20..424d500085 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -537,6 +537,20 @@ fun UriHandler.openUriCatching(uri: String) { } } +fun UriHandler.openExternalLink(uri: String) { + val uriHandler = this + if (uri.startsWith("https://simplex.chat/contact#") || (uri.startsWith("https://smp") && ".simplex.im/a#" in uri)) { + uriHandler.openVerifiedSimplexUri(uri) + } else { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.open_external_link_title), + text = uri, + confirmText = generalGetString(MR.strings.open_verb), + onConfirm = { uriHandler.openUriCatching(uri) } + ) + } +} + fun IntSize.Companion.Saver(): Saver = Saver( save = { it.width to it.height }, restore = { IntSize(it.first, it.second) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index b38fbf9f51..cafad97574 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -604,6 +604,7 @@ fun showPrepareContactAlert( confirmText = generalGetString(MR.strings.connect_plan_open_new_chat), onConfirm = { AlertManager.privacySensitive.hideAlert() + ModalManager.closeAllModalsEverywhere() withBGApi { val chat = chatModel.controller.apiPrepareContact(rhId, connectionLink, contactShortLinkData) if (chat != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index aff02e90f5..2b92d35e72 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -67,7 +67,7 @@ fun ReadableTextWithLink(stringResId: StringResource, link: String, textAlign: T newStyles } val uriHandler = LocalUriHandler.current - Text(AnnotatedString(annotated.text, newStyles), modifier = Modifier.padding(padding).clickable { if (simplexLink) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openUriCatching(link) }, textAlign = textAlign, lineHeight = 22.sp) + Text(AnnotatedString(annotated.text, newStyles), modifier = Modifier.padding(padding).clickable { if (simplexLink) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openExternalLink(link) }, textAlign = textAlign, lineHeight = 22.sp) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 89b2f97ee7..0edeee61af 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -59,7 +59,7 @@ fun ModalData.WhatsNewView(updatedConditions: Boolean = false, viaSettings: Bool Icon( painterResource(MR.images.ic_open_in_new), stringResource(titleId), tint = MaterialTheme.colors.primary, modifier = Modifier - .clickable { if (link.startsWith("simplex:")) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openUriCatching(link) } + .clickable { if (link.startsWith("simplex:")) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openExternalLink(link) } ) } @@ -229,7 +229,7 @@ fun ReadMoreButton(url: String) { interactionSource = remember { MutableInteractionSource() }, indication = null ) { - uriHandler.openUriCatching(url) + uriHandler.openExternalLink(url) } ) Icon(painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.whats_new_read_more), tint = MaterialTheme.colors.primary) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt index 761a74d6e4..0c31b062dd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt @@ -198,7 +198,7 @@ private fun howToButton() { val uriHandler = LocalUriHandler.current Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.clickable { uriHandler.openUriCatching("https://simplex.chat/docs/webrtc.html#configure-mobile-apps") } + modifier = Modifier.clickable { uriHandler.openExternalLink("https://simplex.chat/docs/webrtc.html#configure-mobile-apps") } ) { Text(stringResource(MR.strings.how_to), color = MaterialTheme.colors.primary) Icon( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 7ea656e1e4..c826b1dc51 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -75,7 +75,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, close: ( } val simplexTeamUri = - "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D" + "simplex:/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw?h=smp6.simplex.im" @Composable fun SettingsLayout( @@ -207,7 +207,7 @@ fun ChatLockItem( } @Composable private fun ContributeItem(uriHandler: UriHandler) { - SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat#contribute") }) { + SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat#contribute") }) { Icon( painterResource(MR.images.ic_keyboard), contentDescription = "GitHub", @@ -235,7 +235,7 @@ fun ChatLockItem( } @Composable private fun StarOnGithubItem(uriHandler: UriHandler) { - SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { + SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat") }) { Icon( painter = painterResource(MR.images.ic_github), contentDescription = "GitHub", @@ -268,7 +268,7 @@ fun ChatLockItem( } @Composable fun InstallTerminalAppItem(uriHandler: UriHandler) { - SectionItemView({ uriHandler.openUriCatching("https://github.com/simplex-chat/simplex-chat") }) { + SectionItemView({ uriHandler.openExternalLink("https://github.com/simplex-chat/simplex-chat") }) { Icon( painter = painterResource(MR.images.ic_github), contentDescription = "GitHub", diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index bbd2a0af49..c70243f584 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -769,7 +769,7 @@ fun UsageConditionsView( .clip(shape = CircleShape) .clickable { val commitUrl = "https://github.com/simplex-chat/simplex-chat/commit/$commit" - uriHandler.openUriCatching(commitUrl) + uriHandler.openExternalLink(commitUrl) } .padding(horizontal = 6.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt index 1449e0cd0d..9e11b9a932 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/OperatorView.kt @@ -500,7 +500,7 @@ fun OperatorInfoView(serverOperator: ServerOperator) { Text(d) } val website = serverOperator.info.website - Text(website, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(website) }) + Text(website, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openExternalLink(website) }) } } } @@ -511,7 +511,7 @@ fun OperatorInfoView(serverOperator: ServerOperator) { SectionView { SectionItemView { val (text, link) = selfhost - Text(text, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(link) }) + Text(text, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openExternalLink(link) }) } } } @@ -787,7 +787,7 @@ private fun ConditionsLinkView(conditionsLink: String) { SectionItemView { val uriHandler = LocalUriHandler.current Text(stringResource(MR.strings.operator_conditions_failed_to_load), color = MaterialTheme.colors.onBackground) - Text(conditionsLink, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openUriCatching(conditionsLink) }) + Text(conditionsLink, color = MaterialTheme.colors.primary, modifier = Modifier.clickable { uriHandler.openExternalLink(conditionsLink) }) } } @@ -821,13 +821,13 @@ fun ConditionsLinkButton() { val commit = chatModel.conditions.value.currentConditions.conditionsCommit ItemAction(stringResource(MR.strings.operator_open_conditions), painterResource(MR.images.ic_draft), onClick = { val mdUrl = "https://github.com/simplex-chat/simplex-chat/blob/$commit/PRIVACY.md" - uriHandler.openUriCatching(mdUrl) showMenu.value = false + uriHandler.openExternalLink(mdUrl) }) ItemAction(stringResource(MR.strings.operator_open_changes), painterResource(MR.images.ic_more_horiz), onClick = { val commitUrl = "https://github.com/simplex-chat/simplex-chat/commit/$commit" - uriHandler.openUriCatching(commitUrl) showMenu.value = false + uriHandler.openExternalLink(commitUrl) }) } IconButton({ showMenu.value = true }) { @@ -838,11 +838,7 @@ fun ConditionsLinkButton() { private fun internalUriHandler(parentUriHandler: UriHandler): UriHandler = object: UriHandler { override fun openUri(uri: String) { - if (uri.startsWith("https://simplex.chat/contact#")) { - openVerifiedSimplexUri(uri) - } else { - parentUriHandler.openUriCatching(uri) - } + parentUriHandler.openExternalLink(uri) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt index b232c7994e..3be2456b72 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/ProtocolServersView.kt @@ -335,7 +335,7 @@ private fun HowToButton() { SettingsActionItem( painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.how_to_use_your_servers), - { uriHandler.openUriCatching("https://simplex.chat/docs/server.html") }, + { uriHandler.openExternalLink("https://simplex.chat/docs/server.html") }, textColor = MaterialTheme.colors.primary, iconColor = MaterialTheme.colors.primary ) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index a9b2045b97..0be4ee4d2e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1376,6 +1376,7 @@ Open SimpleX Chat to accept call Enable calls from lock screen via Settings. Open + Open external link? e2e encrypted From 67b2aff1873a2fa46010eda9322c17e5361cf3c6 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 25 Apr 2026 17:53:05 +0100 Subject: [PATCH 21/77] website: revert HU translation change (#6887) Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- website/langs/hu.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/langs/hu.json b/website/langs/hu.json index f1c58a2cde..1d0d51fc03 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -316,16 +316,16 @@ "navbar-token": "Token", "navbar-old-site": "Régi oldal", "docs-dropdown-15": "Összeállítások ellenőrzése és reprodukálása", - "why-p2": "Senki sem követte nyomon a beszélgetéseidet. Senki sem készített térképet arról, hogy merre jártál. A magánéleted nem csak egy funkció volt, hanem az életmódod.", - "why-p3": "Aztán felléptünk az internetre, és minden platform kért belőled egy darabot — neved, telefonszámod, barátaid. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött — telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód.", + "why-p2": "Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk.", + "why-p3": "Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot — nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött — telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód.", "why-p4": "De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá.", - "why-p5": "Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja a magánéletedet, de mégis nyilvántartást vezet minden látogatóról. Nem vagy vendég. Otthon vagy. Nincs az a hatalom, amely beléphetne ide — te vagy a szuverén.", - "why-p6": "A beszélgetéseid hozzád tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogatsz. Ez egy olyan hely, amelyet te hozol létre magadnak. És senki sem veheti el tőled, függetlenül attól, hogy privát vagy nyilvános.", + "why-p5": "Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide — Ön itt szuverén.", + "why-p6": "A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános.", "why-p7": "A legrégebbi emberi szabadság — beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének — olyan infrastruktúrán alapul, amely nem tudja elárulni.", - "why-p8": "Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, ki vagy. Így a te hatalmad soha nem kerülhet idegen kezekbe.", - "why-tagline": "Légy szabad a saját hálózatodban.", + "why-p8": "Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe.", + "why-tagline": "Legyen szabad a saját hálózatában.", "why-footer-link": "Miért készítjük", - "why-p1": "Fiók nélkül születtél.", + "why-p1": "Fiók nélkül születtünk.", "file": "Fájl", "file-desc": "Fájlok biztonságos küldése végpontok közötti titkosítással – felhasználói fiókok és nyomon követés nélkül.", "file-noscript": "A fájlátvitelhez JavaScript szükséges.", From 504ef253cbb53c165a8747a5facee9b810c28f2c Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 25 Apr 2026 20:49:26 +0100 Subject: [PATCH 22/77] core, ui: item about no e2ee in public channels (#6886) * core, ui: item about no e2ee in public channels * fix, refactor * all tests * update bot api types --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- apps/ios/Shared/Views/Chat/ChatItemView.swift | 10 ++++++-- apps/ios/SimpleXChat/ChatTypes.swift | 11 +++++++-- .../chat/simplex/common/model/ChatModel.kt | 9 ++++--- .../common/views/chat/item/ChatItemView.kt | 24 ++++++++----------- .../commonMain/resources/MR/base/strings.xml | 1 + bots/api/TYPES.md | 1 + .../types/typescript/src/types.ts | 1 + src/Simplex/Chat/Library/Commands.hs | 4 ++-- src/Simplex/Chat/Library/Internal.hs | 6 ++--- src/Simplex/Chat/Library/Subscriber.hs | 8 +++---- src/Simplex/Chat/Messages/CIContent.hs | 20 ++++++++++++---- tests/ChatTests/Groups.hs | 3 ++- 12 files changed, 63 insertions(+), 35 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index d0ff1934ba..1839651daa 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -172,8 +172,8 @@ struct ChatItemContentView: View { case .rcvBlocked: deletedItemView() case let .sndDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo)) case let .rcvDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo)) - case .sndGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) - case .rcvGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) + case let .sndGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo)) + case let .rcvGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo)) case .chatBanner: EmptyView() case let .invalidJSON(json): CIInvalidJSONView(json: json) } @@ -257,6 +257,12 @@ struct ChatItemContentView: View { e2eeInfoText("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.") } + private func groupE2EEInfoText(_ info: E2EEInfo) -> Text { + info.public == true + ? e2eeInfoText("Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages.") + : e2eeInfoNoPQText() + } + private func e2eeInfoText(_ s: LocalizedStringKey) -> Text { Text(s) .font(.caption) diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 83b8d61ea1..1dd2e5dd3f 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -4092,8 +4092,8 @@ public enum CIContent: Decodable, ItemContent, Hashable { case .rcvBlocked: return NSLocalizedString("blocked by admin", comment: "blocked chat item") case let .sndDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo) case let .rcvDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo) - case .sndGroupE2EEInfo: return e2eeInfoNoPQStr - case .rcvGroupE2EEInfo: return e2eeInfoNoPQStr + case let .sndGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo) + case let .rcvGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo) case .chatBanner: return "" case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item") } @@ -4105,6 +4105,12 @@ public enum CIContent: Decodable, ItemContent, Hashable { : e2eeInfoNoPQStr } + private func groupE2EEInfoStr(_ e2eeInfo: E2EEInfo) -> String { + e2eeInfo.public == true + ? NSLocalizedString("Messages in this channel are not end-to-end encrypted. Chat relays can see these messages.", comment: "E2EE info chat item") + : e2eeInfoNoPQStr + } + private var e2eeInfoNoPQStr: String { NSLocalizedString("This chat is protected by end-to-end encryption.", comment: "E2EE info chat item") } @@ -5319,6 +5325,7 @@ public enum CIGroupInvitationStatus: String, Decodable, Hashable { public struct E2EEInfo: Decodable, Hashable { public var pqEnabled: Bool? + public var `public`: Bool? } public enum RcvDirectEvent: Decodable, Hashable { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index bc81c958e7..ef3aa19267 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -3800,8 +3800,8 @@ sealed class CIContent: ItemContent { is RcvBlocked -> generalGetString(MR.strings.blocked_by_admin_item_description) is SndDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) is RcvDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) - is SndGroupE2EEInfo -> e2eeInfoNoPQStr - is RcvGroupE2EEInfo -> e2eeInfoNoPQStr + is SndGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo) + is RcvGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo) is ChatBanner -> "" is InvalidJSON -> "invalid data" } @@ -3838,6 +3838,9 @@ sealed class CIContent: ItemContent { private val e2eeInfoNoPQStr: String = generalGetString(MR.strings.e2ee_info_no_pq_short) + fun groupE2EEInfoStr(e2EEInfo: E2EEInfo): String = + if (e2EEInfo.public == true) generalGetString(MR.strings.e2ee_info_no_e2ee) else e2eeInfoNoPQStr + fun featureText(feature: Feature, enabled: String, param: Int?, role: GroupMemberRole? = null): String = (if (feature.hasParam) { "${feature.text}: ${timeText(param)}" @@ -4364,7 +4367,7 @@ enum class CIGroupInvitationStatus { } @Serializable -class E2EEInfo (val pqEnabled: Boolean?) {} +class E2EEInfo (val pqEnabled: Boolean?, val public: Boolean? = null) {} object MsgContentSerializer : KSerializer { override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 14afccee54..15b0a12822 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -680,21 +680,17 @@ fun ChatItemView( } @Composable - fun E2EEInfoNoPQText() { - e2eeInfoText(MR.strings.e2ee_info_no_pq) + fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) { + e2eeInfoText(when (e2EEInfo.pqEnabled) { + true -> MR.strings.e2ee_info_pq + false -> MR.strings.e2ee_info_no_pq + null -> MR.strings.e2ee_info_e2ee + }) } @Composable - fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) { - if (e2EEInfo.pqEnabled != null) { - if (e2EEInfo.pqEnabled) { - e2eeInfoText(MR.strings.e2ee_info_pq) - } else { - E2EEInfoNoPQText() - } - } else { - e2eeInfoText(MR.strings.e2ee_info_e2ee) - } + fun GroupE2EEInfoText(e2EEInfo: E2EEInfo) { + e2eeInfoText(if (e2EEInfo.public == true) MR.strings.e2ee_info_no_e2ee else MR.strings.e2ee_info_no_pq) } if (cItem.meta.itemDeleted != null && (!revealed.value || cItem.isDeletedContent)) { @@ -794,8 +790,8 @@ fun ChatItemView( is CIContent.RcvBlocked -> DeletedItem() is CIContent.SndDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) is CIContent.RcvDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) - is CIContent.SndGroupE2EEInfo -> E2EEInfoNoPQText() - is CIContent.RcvGroupE2EEInfo -> E2EEInfoNoPQText() + is CIContent.SndGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo) + is CIContent.RcvGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo) is CIContent.ChatBanner -> Spacer(modifier = Modifier.size(0.dp)) is CIContent.InvalidJSON -> { CIInvalidJSONView(c.json) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 0be4ee4d2e..4c7241fb5f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -74,6 +74,7 @@ end-to-end encryption.]]> end-to-end encryption with perfect forward secrecy, repudiation and break-in recovery.]]> quantum resistant e2e encryption with perfect forward secrecy, repudiation and break-in recovery.]]> + not end-to-end encrypted. Chat relays can see these messages.]]> This chat is protected by end-to-end encryption. This chat is protected by quantum resistant end-to-end encryption. diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index ccef82eca9..93dbd560eb 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -1847,6 +1847,7 @@ connFullLink + ((' ' + connShortLink) if connShortLink is not None else '') # Py ## E2EInfo **Record type**: +- public: bool? - pqEnabled: bool? diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 7cc9205ff4..59329d3c0d 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -2094,6 +2094,7 @@ export interface DroppedMsg { } export interface E2EInfo { + public?: boolean pqEnabled?: boolean } diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 543014a346..5b6eda579d 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2008,7 +2008,7 @@ processChatCommand vr nm = \case let cd = CDDirectRcv ct createItem sharedMsgId content = createChatItem user cd False content sharedMsgId Nothing cInfo = DirectChat ct - void $ createItem Nothing $ CIRcvDirectE2EEInfo $ E2EInfo $ connRequestPQEncryption cReq + void $ createItem Nothing $ CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ connRequestPQEncryption cReq void $ createFeatureEnabledItems_ user ct aci <- mapM (createItem welcomeSharedMsgId . CIRcvMsgContent) message let chat = case aci of @@ -3858,7 +3858,7 @@ processChatCommand vr nm = \case createNewGroupItems user gInfo = do let cd = CDGroupSnd gInfo Nothing createInternalChatItem user cd CIChatBanner (Just epochStart) - createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CISndGroupE2EEInfo $ e2eInfoGroup gInfo) Nothing createGroupFeatureItems user cd CISndGroupFeature gInfo sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM () sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership, businessChat} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index c6203b4b42..3be69bd949 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -1029,7 +1029,7 @@ acceptBusinessJoinRequestAsync createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode let cd = CDGroupSnd gInfo Nothing -- TODO [short links] move to profileContactRequest? - createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CISndGroupE2EEInfo $ e2eInfoGroup gInfo) Nothing createGroupFeatureItems user cd CISndGroupFeature gInfo -- TODO [short links] get updated business chat group and member? (currently not used) pure (gInfo, clientMember) @@ -1485,7 +1485,7 @@ createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (C createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = flip catchAllErrors (const $ pure (ct, conn)) $ case (pqSndEnabled, pqSndEnabled') of (Just b, b') | b' /= b -> createPQItem $ CISndConnEvent (SCEPqEnabled pqSndEnabled') - (Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo $ Just pqSndEnabled') + (Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (e2eInfoEncrypted $ Just pqSndEnabled') _ -> pure (ct, conn) where createPQItem ciContent = do @@ -1500,7 +1500,7 @@ updateContactPQRcv :: User -> Contact -> Connection -> PQEncryption -> CM (Conta updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' = flip catchAllErrors (const $ pure (ct, conn)) $ case (pqRcvEnabled, pqRcvEnabled') of (Just b, b') | b' /= b -> updatePQ $ CIRcvConnEvent (RCEPqEnabled pqRcvEnabled') - (Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo $ Just pqRcvEnabled') + (Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (e2eInfoEncrypted $ Just pqRcvEnabled') _ -> pure (ct, conn) where updatePQ ciContent = do diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 5d99491b01..3fef977f13 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -590,7 +590,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- [incognito] print incognito profile used for this contact incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) toView $ CEvtContactConnected user ct' (fmap fromLocalProfile incognitoProfile) - let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo $ Just pqEnc) Nothing + let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ Just pqEnc) Nothing -- TODO [short links] get contact request by contactRequestId, check encryption (UserContactRequest.pqSupport)? when (directOrUsed ct') $ case (preparedContact ct', contactRequestId' ct') of (Nothing, Nothing) -> do @@ -842,7 +842,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = firstConnectedHost ( do let cd = CDGroupRcv gInfo'' scopeInfo m'' - createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CIRcvGroupE2EEInfo $ e2eInfoGroup gInfo'') Nothing let prepared = preparedGroup gInfo'' unless (isJust prepared) $ createGroupFeatureItems user cd CIRcvGroupFeature gInfo'' memberConnectedChatItem gInfo'' scopeInfo m'' @@ -1356,7 +1356,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = upsertDirectRequestItem cd (requestMsg_, prevSharedMsgId_) Nothing -> do void $ createChatItem user (CDDirectSnd ct) False CIChatBanner Nothing (Just epochStart) - let e2eContent = CIRcvDirectE2EEInfo $ E2EInfo $ Just $ CR.pqSupportToEnc $ reqPQSup + let e2eContent = CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ Just $ CR.pqSupportToEnc $ reqPQSup void $ createChatItem user cd False e2eContent Nothing Nothing void $ createFeatureEnabledItems_ user ct forM_ (autoReply addressSettings) $ \mc -> forM_ welcomeSharedMsgId $ \sharedMsgId -> @@ -2546,7 +2546,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- create item in both scopes let gInfo' = gInfo {membership = membership'} cd = CDGroupRcv gInfo' Nothing m - createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing + createInternalChatItem user cd (CIRcvGroupE2EEInfo $ e2eInfoGroup gInfo') Nothing let prepared = preparedGroup gInfo' unless (isJust prepared) $ createGroupFeatureItems user cd CIRcvGroupFeature gInfo' let welcomeMsgId_ = (\PreparedGroup {welcomeSharedMsgId = mId} -> mId) <$> preparedGroup gInfo' diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index b08cc5a991..2dc751d6bb 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -177,9 +177,16 @@ data CIContent (d :: MsgDirection) where deriving instance Show (CIContent d) -data E2EInfo = E2EInfo {pqEnabled :: Maybe PQEncryption} +-- stored in database, all changed must be backward compatible +data E2EInfo = E2EInfo {public :: Maybe Bool, pqEnabled :: Maybe PQEncryption} deriving (Eq, Show) +e2eInfoEncrypted :: Maybe PQEncryption -> E2EInfo +e2eInfoEncrypted pqEnabled = E2EInfo {public = Nothing, pqEnabled} + +e2eInfoGroup :: GroupInfo -> E2EInfo +e2eInfoGroup g = E2EInfo {public = if useRelays' g then Just True else Nothing, pqEnabled = Just PQEncOff} + ciMsgContent :: CIContent d -> Maybe MsgContent ciMsgContent = \case CISndMsgContent mc -> Just mc @@ -315,9 +322,14 @@ directE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of Nothing -> simpleE2EText groupE2EInfoToText :: E2EInfo -> Text -groupE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of - Just _ -> e2eInfoNoPQText - Nothing -> simpleE2EText +groupE2EInfoToText E2EInfo {pqEnabled, public} = case public of + Just True -> publicGroupNoE2EText + _ -> case pqEnabled of + Just _ -> e2eInfoNoPQText + Nothing -> simpleE2EText + +publicGroupNoE2EText :: Text +publicGroupNoE2EText = "This channel or group is NOT end-to-end encrypted." simpleE2EText :: Text simpleE2EText = "This conversation is protected by end-to-end encryption" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 1a1bc4b82f..3ff884d3d1 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -28,6 +28,7 @@ import Simplex.Chat.Controller (ChatConfig (..), ChatHooks (..), defaultChatHook import Simplex.Chat.Library.Internal (uniqueMsgMentions, updatedMentionNames) import Simplex.Chat.Markdown (parseMaybeMarkdownList) import Simplex.Chat.Messages (CIMention (..), CIMentionMember (..), ChatItemId) +import Simplex.Chat.Messages.CIContent (publicGroupNoE2EText) import Simplex.Chat.Options import Simplex.Chat.Protocol (MsgMention (..), MsgContent (..), msgContentText) import Simplex.Chat.Types @@ -8857,7 +8858,7 @@ testChannelLinkAfterWelcomeUpdate ps = shortLink' `shouldBe` shortLink fullLink' `shouldBe` fullLink memberJoinChannel "team" [bob] [alice] shortLink' fullLink' dan - dan #$> ("/_get chat #1 count=100", chat, groupFeaturesNoE2E <> [(0, "welcome to team"), (0, e2eeInfoNoPQStr), (0, "connected")]) + dan #$> ("/_get chat #1 count=100", chat, groupFeaturesNoE2E <> [(0, "welcome to team"), (0, T.unpack publicGroupNoE2EText), (0, "connected")]) alice #> "#team hi" bob <# "#team> hi" From 63c278818e0fffdc6f1ddf8c5920ebe429b697ae Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sun, 26 Apr 2026 14:37:16 +0100 Subject: [PATCH 23/77] core: support chats in channels, send as owner in support chats (#6870) * core: test support chats in channels, CLI defaults to sending as member in support chat * ui: enable support chats in channels * use correct scope when sending from UI * more readable * remove test output * show member support chat in channels * preference for support chats * ios: types for support preference * mp: support preference types * show support preference in UI * fix ios * make support preference optional in JSON parser * update string * change strings, pass parameters to prefs * refactor kotlin * take support preference into account * refactor core * do not show broadcast placeholder in support scope * move role check, add pref check on update * support preference test (failing) * fix version * fix tests * warning alert when enabling chats with admins * revert on dismiss * update text and icons * query plans --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .../Chat/ComposeMessage/ComposeView.swift | 4 +- .../Views/Chat/Group/GroupChatInfoView.swift | 13 +- .../Chat/Group/GroupMemberInfoView.swift | 14 +- .../Chat/Group/GroupPreferencesView.swift | 30 +++- .../Views/Chat/Group/MemberSupportView.swift | 2 +- .../Shared/Views/NewChat/AddChannelView.swift | 5 +- apps/ios/SimpleX SE/ShareAPI.swift | 2 +- apps/ios/SimpleXChat/ChatTypes.swift | 35 ++++ apps/ios/SimpleXChat/ChatUtils.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 13 ++ .../chat/simplex/common/model/SimpleXAPI.kt | 19 +- .../simplex/common/views/chat/ComposeView.kt | 8 +- .../views/chat/group/GroupChatInfoView.kt | 17 +- .../views/chat/group/GroupMemberInfoView.kt | 18 +- .../views/chat/group/GroupPreferences.kt | 32 +++- .../views/chat/group/MemberSupportView.kt | 5 +- .../common/views/newchat/AddChannelView.kt | 5 +- .../commonMain/resources/MR/base/strings.xml | 9 + bots/api/TYPES.md | 12 ++ bots/src/API/Docs/Types.hs | 2 + .../types/typescript/src/types.ts | 7 + src/Simplex/Chat/Library/Commands.hs | 20 ++- src/Simplex/Chat/Library/Internal.hs | 5 + src/Simplex/Chat/Library/Subscriber.hs | 49 ++++-- src/Simplex/Chat/Messages.hs | 5 + src/Simplex/Chat/Store/Groups.hs | 7 +- .../SQLite/Migrations/chat_query_plans.txt | 5 +- src/Simplex/Chat/Types.hs | 3 - src/Simplex/Chat/Types/Preferences.hs | 47 ++++- tests/ChatClient.hs | 2 +- tests/ChatTests/ChatList.hs | 18 +- tests/ChatTests/Groups.hs | 163 +++++++++++++++++- tests/ChatTests/Utils.hs | 17 +- tests/ProtocolTests.hs | 2 +- 34 files changed, 503 insertions(+), 93 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index fd47ddfacb..334abd76ee 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -515,7 +515,7 @@ struct ComposeView: View { sendMessageView( disableSendButton, placeholder: chat.chatInfo.groupInfo.map { gi in - gi.useRelays && gi.membership.memberRole >= .owner + gi.useRelays && gi.membership.memberRole >= .owner && chat.chatInfo.groupChatScope() == nil ? NSLocalizedString("Broadcast", comment: "compose placeholder for channel owner") : nil } ?? nil @@ -1659,7 +1659,7 @@ struct ComposeView: View { type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, scope: chat.chatInfo.groupChatScope(), - sendAsGroup: chat.chatInfo.groupInfo.map { $0.useRelays && $0.membership.memberRole >= .owner } ?? false, + sendAsGroup: chat.chatInfo.sendAsGroup, live: live, ttl: ttl, composedMessages: msgs diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 9279c53c83..21685fccd1 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -103,6 +103,10 @@ struct GroupChatInfoView: View { } } + let showUserSupportChat = groupInfo.membership.memberActive + && ((groupInfo.fullGroupPreferences.support.on && groupInfo.membership.memberRole < .moderator) + || groupInfo.membership.supportChat != nil) + if groupInfo.useRelays { Section { // TODO [relays] allow other owners to manage channel link (requires protocol changes to share link ownership) @@ -124,6 +128,12 @@ struct GroupChatInfoView: View { if groupInfo.isOwner || members.contains(where: { $0.wrapped.memberRole >= .owner }) { channelMembersButton() } + if groupInfo.membership.memberRole >= .moderator { + memberSupportButton() + } + if showUserSupportChat { + UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) + } } footer: { if !groupInfo.isOwner && groupInfo.groupProfile.publicGroup?.groupLink != nil { Text("You can share a link or a QR code - anybody will be able to join the channel.") @@ -141,8 +151,7 @@ struct GroupChatInfoView: View { if groupInfo.canModerate { GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) } - if groupInfo.membership.memberActive - && (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) { + if showUserSupportChat { UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId) } } header: { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index af7054db01..4dff86f7bb 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -121,13 +121,15 @@ struct GroupMemberInfoView: View { } if connectionLoaded { + let showMemberSupportChat = !openedFromSupportChat + && groupInfo.membership.memberRole >= .moderator + && member.memberRole != .relay + && ((groupInfo.fullGroupPreferences.support.on && member.memberRole < .moderator) + || member.supportChat != nil) if member.memberActive { Section { - if !openedFromSupportChat - && groupInfo.membership.memberRole >= .moderator - && member.memberRole != .relay - && (member.memberRole < .moderator || member.supportChat != nil) { + if showMemberSupportChat { MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId) } if let code = connectionCode, @@ -142,6 +144,10 @@ struct GroupMemberInfoView: View { // synchronizeConnectionButtonForce() // } } + } else if groupInfo.useRelays && member.memberCurrent && showMemberSupportChat { + Section { + MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId) + } } if let contactLink = member.contactLink { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 84e852f5a3..49b9829830 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -46,13 +46,30 @@ struct GroupPreferencesView: View { featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) featureSection(.files, $preferences.files.enable, $preferences.files.role) featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) - featureSection(.reports, $preferences.reports.enable) + featureSection(.reports, $preferences.reports.enable, disabled: true) // enable reports in 7.0 once directory support added featureSection(.history, $preferences.history.enable) + featureSection(.support, $preferences.support.enable, disabled: true) } else { featureSection(.timedMessages, $preferences.timedMessages.enable) featureSection(.fullDelete, $preferences.fullDelete.enable) featureSection(.reactions, $preferences.reactions.enable) featureSection(.history, $preferences.history.enable) + let supportNotice = NSLocalizedString("Chats with admins in public channels have no E2E encryption - use only with trusted chat relays.", comment: "alert message") + featureSection(.support, $preferences.support.enable, notice: supportNotice) + .onChange(of: preferences.support.enable) { enable in + if enable == .on { + showAlert( + NSLocalizedString("Enable chats with admins?", comment: "alert title"), + message: supportNotice, + actions: {[ + UIAlertAction(title: NSLocalizedString("Enable", comment: "alert button"), style: .destructive) { _ in }, + UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) { _ in + preferences.support.enable = .off + } + ]} + ) + } + } } if groupInfo.isOwner { @@ -92,7 +109,7 @@ struct GroupPreferencesView: View { } } - private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding, _ enableForRole: Binding? = nil) -> some View { + private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding, _ enableForRole: Binding? = nil, disabled: Bool = false, notice: String? = nil) -> some View { Section { let color: Color = enableFeature.wrappedValue == .on ? .green : theme.colors.secondary let icon = enableFeature.wrappedValue == .on ? feature.iconFilled : feature.icon @@ -105,7 +122,7 @@ struct GroupPreferencesView: View { settingsRow(icon, color: color) { Toggle(feature.text, isOn: enable) } - .disabled(feature == .reports) // remove in 6.4 + .disabled(disabled) if timedOn { DropdownCustomTimePicker( selection: $preferences.timedMessages.ttl, @@ -144,8 +161,11 @@ struct GroupPreferencesView: View { } } } footer: { - Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner)) - .foregroundColor(theme.colors.secondary) + VStack(alignment: .leading) { + Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner)) + if let notice { Text(notice) } + } + .foregroundColor(theme.colors.secondary) } .onChange(of: enableFeature.wrappedValue) { enabled in if case .off = enabled { diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift index 3dc27c08f6..880933985c 100644 --- a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift +++ b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift @@ -45,7 +45,7 @@ struct MemberSupportView: View { : membersWithChats.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) } if membersWithChats.isEmpty { - Text("No chats with members") + Text(groupInfo.fullGroupPreferences.support.on ? "No chats with members" : "Chats with members are disabled") .foregroundColor(.secondary) } else { List { diff --git a/apps/ios/Shared/Views/NewChat/AddChannelView.swift b/apps/ios/Shared/Views/NewChat/AddChannelView.swift index 4e9a42971c..eae690f5d5 100644 --- a/apps/ios/Shared/Views/NewChat/AddChannelView.swift +++ b/apps/ios/Shared/Views/NewChat/AddChannelView.swift @@ -161,7 +161,10 @@ struct AddChannelView: View { private func createChannel() { focusDisplayName = false profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces) - profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on)) + profile.groupPreferences = GroupPreferences( + history: GroupPreference(enable: .on), + support: GroupPreference(enable: .off) + ) creationInProgress = true Task { do { diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift index f13401d437..52c0405e5e 100644 --- a/apps/ios/SimpleX SE/ShareAPI.swift +++ b/apps/ios/SimpleX SE/ShareAPI.swift @@ -68,7 +68,7 @@ func apiSendMessages( type: chatInfo.chatType, id: chatInfo.apiId, scope: chatInfo.groupChatScope(), - sendAsGroup: chatInfo.groupInfo.map { $0.useRelays && $0.membership.memberRole >= .owner } ?? false, + sendAsGroup: chatInfo.sendAsGroup, live: false, ttl: nil, composedMessages: composedMessages diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 1dd2e5dd3f..aa856c8fc3 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -867,6 +867,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case simplexLinks case reports case history + case support public var id: Self { self } @@ -888,6 +889,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .simplexLinks: true case .reports: false case .history: false + case .support: false } } @@ -902,6 +904,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .simplexLinks: return NSLocalizedString("SimpleX links", comment: "chat feature") case .reports: return NSLocalizedString("Member reports", comment: "chat feature") case .history: return NSLocalizedString("Visible history", comment: "chat feature") + case .support: return NSLocalizedString("Chat with admins", comment: "chat feature") } } @@ -916,6 +919,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .simplexLinks: return "link.circle" case .reports: return "flag" case .history: return "clock" + case .support: return "questionmark.circle" } } @@ -930,6 +934,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .simplexLinks: return "link.circle.fill" case .reports: return "flag.fill" case .history: return "clock.fill" + case .support: return "questionmark.circle.fill" } } @@ -988,6 +993,11 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .on: return "Send up to 100 last messages to new members." case .off: return "Do not send history to new members." } + case .support: + switch enabled { + case .on: return "Allow members to chat with admins." + case .off: return "Prohibit chats with admins." + } } } else { switch self { @@ -1036,6 +1046,11 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .on: return "Up to 100 last messages are sent to new members." case .off: return "History is not sent to new members." } + case .support: + switch enabled { + case .on: return "Members can chat with admins." + case .off: return "Chats with admins are prohibited." + } } } } @@ -1190,6 +1205,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { public var simplexLinks: RoleGroupPreference public var reports: GroupPreference public var history: GroupPreference + public var support: GroupPreference public var commands: [ChatBotCommand] public init( @@ -1202,6 +1218,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { simplexLinks: RoleGroupPreference, reports: GroupPreference, history: GroupPreference, + support: GroupPreference, commands: [ChatBotCommand] ) { self.timedMessages = timedMessages @@ -1213,6 +1230,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { self.simplexLinks = simplexLinks self.reports = reports self.history = history + self.support = support self.commands = commands } @@ -1226,6 +1244,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { simplexLinks: RoleGroupPreference(enable: .on, role: nil), reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on), + support: GroupPreference(enable: .on), commands: [] ) } @@ -1240,6 +1259,7 @@ public struct GroupPreferences: Codable, Hashable { public var simplexLinks: RoleGroupPreference? public var reports: GroupPreference? public var history: GroupPreference? + public var support: GroupPreference? public var commands: [ChatBotCommand]? public init( @@ -1252,6 +1272,7 @@ public struct GroupPreferences: Codable, Hashable { simplexLinks: RoleGroupPreference? = nil, reports: GroupPreference? = nil, history: GroupPreference? = nil, + support: GroupPreference? = nil, commands: [ChatBotCommand]? = nil ) { self.timedMessages = timedMessages @@ -1263,6 +1284,7 @@ public struct GroupPreferences: Codable, Hashable { self.simplexLinks = simplexLinks self.reports = reports self.history = history + self.support = support self.commands = commands } @@ -1276,6 +1298,7 @@ public struct GroupPreferences: Codable, Hashable { simplexLinks: RoleGroupPreference(enable: .on, role: nil), reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on), + support: GroupPreference(enable: .on), commands: nil ) } @@ -1760,6 +1783,18 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable { } } + public var sendAsGroup: Bool { + if let g = groupInfo, g.useRelays && g.membership.memberRole >= .owner { + switch groupChatScope() { + case .none: true + case .memberSupport: false + case .reports: false + } + } else { + false + } + } + public func ntfsEnabled(chatItem: ChatItem) -> Bool { ntfsEnabled(chatItem.meta.userMention) } diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift index 451ac8b4ef..788ac12bae 100644 --- a/apps/ios/SimpleXChat/ChatUtils.swift +++ b/apps/ios/SimpleXChat/ChatUtils.swift @@ -25,6 +25,7 @@ extension ChatLike { case .files: p.files.on(for: groupInfo.membership) case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership) case .history: p.history.on + case .support: p.support.on case .reports: p.reports.on } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index ef3aa19267..a745542602 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1687,6 +1687,18 @@ sealed class ChatInfo: SomeChat, NamedChat { else -> null } + val sendAsGroup: Boolean get() { + val g = (this as? Group)?.groupInfo + return if (g != null && g.useRelays && g.membership.memberRole >= GroupMemberRole.Owner) { + when (groupChatScope()) { + null -> true + is GroupChatScope.MemberSupport -> false + } + } else { + false + } + } + fun ntfsEnabled(ci: ChatItem): Boolean = ntfsEnabled(ci.meta.userMention) @@ -2133,6 +2145,7 @@ data class GroupInfo ( GroupFeature.SimplexLinks -> p.simplexLinks.on(membership) GroupFeature.Reports -> p.reports.on GroupFeature.History -> p.history.on + GroupFeature.Support -> p.support.on } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index f62d4c262d..0e5b57d8c2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5693,7 +5693,8 @@ enum class GroupFeature: Feature { @SerialName("files") Files, @SerialName("simplexLinks") SimplexLinks, @SerialName("reports") Reports, - @SerialName("history") History; + @SerialName("history") History, + @SerialName("support") Support; override val hasParam: Boolean get() = when(this) { TimedMessages -> true @@ -5711,6 +5712,7 @@ enum class GroupFeature: Feature { SimplexLinks -> true Reports -> false History -> false + Support -> false } override val text: String @@ -5724,6 +5726,7 @@ enum class GroupFeature: Feature { SimplexLinks -> generalGetString(MR.strings.simplex_links) Reports -> generalGetString(MR.strings.group_reports_member_reports) History -> generalGetString(MR.strings.recent_history) + Support -> generalGetString(MR.strings.chat_with_admins) } val icon: Painter @@ -5737,6 +5740,7 @@ enum class GroupFeature: Feature { SimplexLinks -> painterResource(MR.images.ic_link) Reports -> painterResource(MR.images.ic_flag) History -> painterResource(MR.images.ic_schedule) + Support -> painterResource(MR.images.ic_help) } @Composable @@ -5750,6 +5754,7 @@ enum class GroupFeature: Feature { SimplexLinks -> painterResource(MR.images.ic_link) Reports -> painterResource(MR.images.ic_flag_filled) History -> painterResource(MR.images.ic_schedule_filled) + Support -> painterResource(MR.images.ic_help_filled) } fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String = @@ -5791,6 +5796,10 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history) } + Support -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_chat_with_admins) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_chat_with_admins) + } } } else { when(this) { @@ -5830,6 +5839,10 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members) } + Support -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(MR.strings.members_can_chat_with_admins) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.chat_with_admins_is_prohibited) + } } } } @@ -5955,6 +5968,7 @@ data class FullGroupPreferences( val simplexLinks: RoleGroupPreference, val reports: GroupPreference, val history: GroupPreference, + val support: GroupPreference, val commands: List, ) { fun toGroupPreferences(): GroupPreferences = @@ -5968,6 +5982,7 @@ data class FullGroupPreferences( simplexLinks = simplexLinks, reports = reports, history = history, + support = support, commands = commands, ) @@ -5982,6 +5997,7 @@ data class FullGroupPreferences( simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), reports = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON), + support = GroupPreference(GroupFeatureEnabled.ON), commands = listOf() ) } @@ -5998,6 +6014,7 @@ data class GroupPreferences( val simplexLinks: RoleGroupPreference? = null, val reports: GroupPreference? = null, val history: GroupPreference? = null, + val support: GroupPreference? = null, val commands: List? = null ) { companion object { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 95f51bf284..480320e33e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -534,7 +534,7 @@ fun ComposeView( type = cInfo.chatType, id = cInfo.apiId, scope = cInfo.groupChatScope(), - sendAsGroup = (cInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false, + sendAsGroup = cInfo.sendAsGroup, live = live, ttl = ttl, composedMessages = listOf(ComposedMessage(file, quoted, mc, mentions)) @@ -665,7 +665,7 @@ fun ComposeView( toChatType = chat.chatInfo.chatType, toChatId = chat.chatInfo.apiId, toScope = chat.chatInfo.groupChatScope(), - sendAsGroup = (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false, + sendAsGroup = chat.chatInfo.sendAsGroup, fromChatType = fromChatInfo.chatType, fromChatId = fromChatInfo.apiId, fromScope = fromChatInfo.groupChatScope(), @@ -1496,7 +1496,7 @@ fun ComposeView( ) is SharedContent.ChatLink -> { val cInfo = chat.chatInfo - val sendAsGroup = (cInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false + val sendAsGroup = cInfo.sendAsGroup withBGApi { val mc = chatModel.controller.apiShareChatMsgContent( chat.remoteHostId, ChatType.Group, shared.groupInfo.groupId, @@ -1693,7 +1693,7 @@ fun ComposeView( Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) { AttachmentAndCommandsButtons() val broadcastPlaceholder = (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.let { gi -> - if (gi.useRelays && gi.membership.memberRole >= GroupMemberRole.Owner) generalGetString(MR.strings.compose_view_broadcast) + if (gi.useRelays && gi.membership.memberRole >= GroupMemberRole.Owner && chat.chatInfo.groupChatScope() == null) generalGetString(MR.strings.compose_view_broadcast) else null } SendMsgView_(disableSendButton = disableSendButton, placeholder = broadcastPlaceholder) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 156ad2cd97..2c3a6c713b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -546,6 +546,10 @@ fun ModalData.GroupChatInfoLayout( var anyTopSectionRowShow = false val channelLink = groupInfo.groupProfile.publicGroup?.groupLink + val showUserSupportChat = groupInfo.membership.memberActive && + ((groupInfo.fullGroupPreferences.support.on && groupInfo.membership.memberRole < GroupMemberRole.Moderator) + || groupInfo.membership.supportChat != null) + if (groupInfo.useRelays) { SectionView { if (groupInfo.isOwner && groupLink != null) { @@ -564,6 +568,14 @@ fun ModalData.GroupChatInfoLayout( anyTopSectionRowShow = true ChannelMembersButton(chat.remoteHostId, groupInfo, showMemberInfo) } + if (groupInfo.membership.memberRole >= GroupMemberRole.Moderator) { + anyTopSectionRowShow = true + MemberSupportButton(chat, openMemberSupport) + } + if (showUserSupportChat) { + anyTopSectionRowShow = true + UserSupportChatButton(chat, groupInfo, scrollToItemId) + } } if (!groupInfo.isOwner && channelLink != null) { SectionTextFooter(stringResource(MR.strings.you_can_share_channel_link_anybody_will_be_able_to_connect)) @@ -590,10 +602,7 @@ fun ModalData.GroupChatInfoLayout( } } } - if ( - groupInfo.membership.memberActive && - (groupInfo.membership.memberRole < GroupMemberRole.Moderator || groupInfo.membership.supportChat != null) - ) { + if (showUserSupportChat) { anyTopSectionRowShow = true UserSupportChatButton(chat, groupInfo, scrollToItemId) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index fc5d697f4f..1bc6f038f3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -483,14 +483,15 @@ fun GroupMemberInfoLayout( SectionSpacer() } + val showMemberSupportChat = !openedFromSupportChat && + groupInfo.membership.memberRole >= GroupMemberRole.Moderator && + member.memberRole != GroupMemberRole.Relay && + ((groupInfo.fullGroupPreferences.support.on && member.memberRole < GroupMemberRole.Moderator) + || member.supportChat != null) + if (member.memberActive) { SectionView { - if ( - !openedFromSupportChat && - groupInfo.membership.memberRole >= GroupMemberRole.Moderator && - member.memberRole != GroupMemberRole.Relay && - (member.memberRole < GroupMemberRole.Moderator || member.supportChat != null) - ) { + if (showMemberSupportChat) { SupportChatButton() } if (connectionCode != null && !(groupInfo.useRelays && member.memberRole == GroupMemberRole.Relay)) { @@ -504,6 +505,11 @@ fun GroupMemberInfoLayout( // } } SectionDividerSpaced() + } else if (groupInfo.useRelays && member.memberCurrent && showMemberSupportChat) { + SectionView { + SupportChatButton() + } + SectionDividerSpaced() } if (member.contactLink != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 902b4c9828..b84d4a4730 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -155,7 +155,7 @@ private fun GroupPreferencesLayout( } @Composable fun ReportsPreference() { val enableReports = remember(preferences) { mutableStateOf(preferences.reports.enable) } - FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> + FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated, disabled = true) { enable, _ -> // enable reports in 7.0 once directory support added applyPrefs(preferences.copy(reports = GroupPreference(enable = enable))) } } @@ -165,6 +165,16 @@ private fun GroupPreferencesLayout( applyPrefs(preferences.copy(history = GroupPreference(enable = enable))) } } + @Composable fun SupportPreference(disabled: Boolean = false, notice: String? = null, onEnable: ((() -> Unit) -> Unit)? = null) { + val enableSupport = remember(preferences) { mutableStateOf(preferences.support.enable) } + FeatureSection(GroupFeature.Support, enableSupport, null, groupInfo, preferences, onTTLUpdated, disabled = disabled, notice = notice) { enable, _ -> + applyPrefs(preferences.copy(support = GroupPreference(enable = enable))) + if (enable == GroupFeatureEnabled.ON) onEnable?.invoke { + enableSupport.value = GroupFeatureEnabled.OFF + applyPrefs(preferences.copy(support = GroupPreference(enable = GroupFeatureEnabled.OFF))) + } + } + } ColumnWithScrollBar { val titleId = if (groupInfo.useRelays) MR.strings.channel_preferences else if (groupInfo.businessChat == null) MR.strings.group_preferences @@ -192,6 +202,8 @@ private fun GroupPreferencesLayout( ReportsPreference() SectionDividerSpaced(true, maxBottomPadding = false) HistoryPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + SupportPreference(disabled = true) } else { TimedMessagesPreference() SectionDividerSpaced(true, maxBottomPadding = false) @@ -200,6 +212,17 @@ private fun GroupPreferencesLayout( ReactionsPreference() SectionDividerSpaced(true, maxBottomPadding = false) HistoryPreference() + SectionDividerSpaced(true, maxBottomPadding = false) + SupportPreference(notice = generalGetString(MR.strings.chat_with_admins_relay_note), onEnable = { revert -> + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.enable_chats_with_admins_question), + text = generalGetString(MR.strings.chat_with_admins_relay_note), + confirmText = generalGetString(MR.strings.enable_chats_with_admins), + destructive = true, + onDismiss = revert, + onDismissRequest = revert, + ) + }) } if (groupInfo.isOwner) { SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) @@ -233,6 +256,8 @@ private fun FeatureSection( groupInfo: GroupInfo, preferences: FullGroupPreferences, onTTLUpdated: (Int?) -> Unit, + disabled: Boolean = false, + notice: String? = null, onSelected: (GroupFeatureEnabled, GroupMemberRole?) -> Unit ) { SectionView { @@ -245,7 +270,7 @@ private fun FeatureSection( feature.text, icon, iconTint, - disabled = feature == GroupFeature.Reports, // remove in 6.4 + disabled = disabled, checked = enableFeature.value == GroupFeatureEnabled.ON, ) { checked -> onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value) @@ -293,6 +318,9 @@ private fun FeatureSection( } } SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner)) + if (notice != null) { + SectionTextFooter(notice) + } } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt index c3cf954ab6..3d76c845ad 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt @@ -116,7 +116,10 @@ private fun ModalData.MemberSupportViewLayout( if (membersWithChats.isEmpty()) { item { Box(Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING), contentAlignment = Alignment.Center) { - Text(generalGetString(MR.strings.no_support_chats), color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center) + Text( + generalGetString(if (groupInfo.fullGroupPreferences.support.on) MR.strings.no_support_chats else MR.strings.support_chats_disabled), + color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center + ) } } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt index e60fcfc921..77b5cd73b5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddChannelView.kt @@ -110,7 +110,10 @@ fun AddChannelView(chatModel: ChatModel, close: () -> Unit, closeAll: () -> Unit fullName = "", shortDescr = null, image = profileImage.value, - groupPreferences = GroupPreferences(history = GroupPreference(GroupFeatureEnabled.ON)) + groupPreferences = GroupPreferences( + history = GroupPreference(GroupFeatureEnabled.ON), + support = GroupPreference(GroupFeatureEnabled.OFF) + ) ) creationInProgress.value = true withBGApi { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 4c7241fb5f..4bd9d9b96f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2337,6 +2337,14 @@ History is not sent to new members. Members can report messsages to moderators. Reporting messages is prohibited in this group. + Chat with admins + Allow members to chat with admins. + Prohibit chats with admins. + Members can chat with admins. + Chats with admins are prohibited. + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + Enable chats with admins? + Enable Delete after %d sec %ds @@ -2373,6 +2381,7 @@ Chats with members No chats with members + Chats with members are disabled Delete chat Delete chat with member? diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 93dbd560eb..ceb38939b1 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -171,6 +171,7 @@ This file is generated automatically. - [SrvError](#srverror) - [StoreError](#storeerror) - [SubscriptionStatus](#subscriptionstatus) +- [SupportGroupPreference](#supportgrouppreference) - [SwitchPhase](#switchphase) - [TimedMessagesGroupPreference](#timedmessagesgrouppreference) - [TimedMessagesPreference](#timedmessagespreference) @@ -2109,6 +2110,7 @@ Phone: - simplexLinks: [RoleGroupPreference](#rolegrouppreference) - reports: [GroupPreference](#grouppreference) - history: [GroupPreference](#grouppreference) +- support: [SupportGroupPreference](#supportgrouppreference) - sessions: [RoleGroupPreference](#rolegrouppreference) - comments: [CommentsGroupPreference](#commentsgrouppreference) - commands: [[ChatBotCommand](#chatbotcommand)] @@ -2200,6 +2202,7 @@ MemberSupport: - "simplexLinks" - "reports" - "history" +- "support" - "sessions" - "comments" @@ -2434,6 +2437,7 @@ NoRelays: - simplexLinks: [RoleGroupPreference](#rolegrouppreference)? - reports: [GroupPreference](#grouppreference)? - history: [GroupPreference](#grouppreference)? +- support: [SupportGroupPreference](#supportgrouppreference)? - sessions: [RoleGroupPreference](#rolegrouppreference)? - comments: [CommentsGroupPreference](#commentsgrouppreference)? - commands: [[ChatBotCommand](#chatbotcommand)]? @@ -3899,6 +3903,14 @@ NoSub: - type: "noSub" +--- + +## SupportGroupPreference + +**Record type**: +- enable: [GroupFeatureEnabled](#groupfeatureenabled) + + --- ## SwitchPhase diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index 50adf6f7e5..933858c5cc 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -354,6 +354,7 @@ chatTypesDocsData = (sti @SrvError, STUnion, "SrvErr", [], "", ""), (sti @StoreError, STUnion, "SE", [], "", ""), (sti @SubscriptionStatus, STUnion, "SS", [], "", ""), + (sti @SupportGroupPreference, STRecord, "", [], "", ""), (sti @SwitchPhase, STEnum, "SP", [], "", ""), (sti @TimedMessagesGroupPreference, STRecord, "", [], "", ""), (sti @TimedMessagesPreference, STRecord, "", [], "", ""), @@ -563,6 +564,7 @@ deriving instance Generic SndGroupEvent deriving instance Generic SrvError deriving instance Generic StoreError deriving instance Generic SubscriptionStatus +deriving instance Generic SupportGroupPreference deriving instance Generic SwitchPhase deriving instance Generic TimedMessagesGroupPreference deriving instance Generic TimedMessagesPreference diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 59329d3c0d..eed9d5edc1 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -2443,6 +2443,7 @@ export interface FullGroupPreferences { simplexLinks: RoleGroupPreference reports: GroupPreference history: GroupPreference + support: SupportGroupPreference sessions: RoleGroupPreference comments: CommentsGroupPreference commands: ChatBotCommand[] @@ -2516,6 +2517,7 @@ export enum GroupFeature { SimplexLinks = "simplexLinks", Reports = "reports", History = "history", + Support = "support", Sessions = "sessions", Comments = "comments", } @@ -2715,6 +2717,7 @@ export interface GroupPreferences { simplexLinks?: RoleGroupPreference reports?: GroupPreference history?: GroupPreference + support?: SupportGroupPreference sessions?: RoleGroupPreference comments?: CommentsGroupPreference commands?: ChatBotCommand[] @@ -4637,6 +4640,10 @@ export namespace SubscriptionStatus { } } +export interface SupportGroupPreference { + enable: GroupFeatureEnabled +} + export enum SwitchPhase { Started = "started", Confirmed = "confirmed", diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 5b6eda579d..31e6533ad3 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -625,7 +625,10 @@ processChatCommand vr nm = \case mapM_ assertNoMentions cms withContactLock "sendMessage" chatId $ sendContactContentMessages user chatId live itemTTL (L.map composedMessageReq cms) - SRGroup chatId gsScope asGroup -> + SRGroup chatId gsScope asGroup -> do + case gsScope of + Just (GCSMemberSupport _) -> when asGroup $ throwCmdError "cannot send as group in support scope" + Nothing -> pure () withGroupLock "sendMessage" chatId $ do (gInfo, cmrs) <- withFastStore $ \db -> do g <- getGroupInfo db vr user chatId @@ -2375,7 +2378,7 @@ processChatCommand vr nm = \case forM scope_ $ \(GSNMemberSupport mName_) -> GCSMemberSupport <$> mapM (getGroupMemberIdByName db user gId) mName_ (gInfo, cScope_,) <$> liftIO (getMessageMentions db user gId msg) - let sendRef = SRGroup (groupId' gInfo) cScope_ (sendAsGroup' gInfo) + let sendRef = SRGroup (groupId' gInfo) cScope_ (sendAsGroup' gInfo cScope_) processChatCommand vr nm $ APISendMessages sendRef False Nothing [ComposedMessage Nothing Nothing mc mentions] SNLocal -> do folderId <- withFastStore (`getUserNoteFolderId` user) @@ -3128,7 +3131,7 @@ processChatCommand vr nm = \case qiId <- getGroupChatItemIdByText db user gId cName quotedMsg (gInfo, qiId,) <$> liftIO (getMessageMentions db user gId msg) let mc = MCText msg - processChatCommand vr nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions] + processChatCommand vr nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo Nothing)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions] ClearNoteFolder -> withUser $ \user -> do folderId <- withFastStore (`getUserNoteFolderId` user) processChatCommand vr nm $ APIClearChat (ChatRef CTLocal folderId Nothing) @@ -3404,7 +3407,7 @@ processChatCommand vr nm = \case _ -> throwCmdError "not supported" pure $ ChatRef cType chatId Nothing getSendAsGroup :: User -> ChatRef -> CM ShowGroupAsSender - getSendAsGroup user' (ChatRef CTGroup chatId _) = sendAsGroup' <$> withFastStore (\db -> getGroupInfo db vr user' chatId) + getSendAsGroup user' (ChatRef CTGroup chatId scope) = (`sendAsGroup'` scope) <$> withFastStore (\db -> getGroupInfo db vr user' chatId) getSendAsGroup _ _ = pure False getChatRefAndMentions :: User -> ChatName -> Text -> CM (ChatRef, Map MemberName GroupMemberId) getChatRefAndMentions user cName msg = do @@ -4490,7 +4493,7 @@ processChatCommand vr nm = \case ChatRef CTDirect cId _ -> a $ SRDirect cId ChatRef CTGroup gId scope -> do gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId - a $ SRGroup gId scope (sendAsGroup' gInfo) + a $ SRGroup gId scope (sendAsGroup' gInfo scope) _ -> throwCmdError "not supported" getSharedMsgId :: CM SharedMsgId getSharedMsgId = do @@ -5020,7 +5023,7 @@ chatCommandP = ("/help" <|> "/h") $> ChatHelp HSMain, ("/group" <|> "/g") *> (NewGroup <$> incognitoP <* A.space <* char_ '#' <*> groupProfile), "/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP), - ("/public group" <|> "/pg") *> (NewPublicGroup <$> incognitoP <* " relays=" <*> strP <* A.space <* char_ '#' <*> groupProfile), + ("/public group" <|> "/pg") *> (NewPublicGroup <$> incognitoP <* " relays=" <*> strP <* A.space <* char_ '#' <*> channelProfile), "/_public group " *> (APINewPublicGroup <$> A.decimal <*> incognitoOnOffP <*> _strP <* A.space <*> jsonP), "/_get relays #" *> (APIGetGroupRelays <$> A.decimal), ("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)), @@ -5150,6 +5153,7 @@ chatCommandP = "/set disappear @" *> (SetContactTimedMessages <$> displayNameP <*> optional (A.space *> timedMessagesEnabledP)), "/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))), "/set reports #" *> (SetGroupFeature (AGFNR SGFReports) <$> displayNameP <*> _strP), + "/set support #" *> (SetGroupFeature (AGFNR SGFSupport) <$> displayNameP <*> (A.space *> strP)), "/set links #" *> (SetGroupFeatureRole (AGFR SGFSimplexLinks) <$> displayNameP <*> _strP <*> optional memberRole), "/set admission review #" *> (SetGroupMemberAdmissionReview <$> displayNameP <*> (A.space *> memberCriteriaP)), ("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito, @@ -5287,6 +5291,10 @@ chatCommandP = history = Just HistoryGroupPreference {enable = FEOn} } pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, publicGroup = Nothing, groupPreferences, memberAdmission = Nothing} + channelProfile = do + p@GroupProfile {groupPreferences = prefs_} <- groupProfile + let prefs = (fromMaybe emptyGroupPrefs prefs_) {support = Just SupportGroupPreference {enable = FEOff}} :: GroupPreferences + pure p {groupPreferences = Just prefs} memberCriteriaP = ("all" $> Just MCAll) <|> ("off" $> Nothing) shortDescrP = do descr <- A.takeWhile1 isSpace *> (T.dropWhileEnd isSpace <$> textP) <|> pure "" diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 3be69bd949..d7de3a52ad 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -338,12 +338,17 @@ quoteContent mc qmc ciFile_ prohibitedGroupContent :: GroupInfo -> GroupMember -> Maybe GroupChatScopeInfo -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature prohibitedGroupContent gInfo@GroupInfo {membership = mem@GroupMember {memberRole = userRole}} m scopeInfo mc ft file_ sent + | not supportAllowed = Just GFSupport | isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) && not hostApprovalVoice = Just GFVoice | isNothing scopeInfo && not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles | isNothing scopeInfo && isReport mc && (badReportUser || not (groupFeatureAllowed SGFReports gInfo)) = Just GFReports | isNothing scopeInfo && prohibitedSimplexLinks gInfo m mc ft = Just GFSimplexLinks | otherwise = Nothing where + supportAllowed = case scopeInfo of + Just (GCSIMemberSupport scopeMem_) -> + groupFeatureAllowed SGFSupport gInfo || isJust (supportChat $ fromMaybe mem scopeMem_) + Nothing -> True hostApprovalVoice | sent = userRole >= GRAdmin && sendApprovalPhase | otherwise = memberCategory m == GCHostMember && hostApprovalPhase diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 3fef977f13..087914e4f9 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -1535,7 +1535,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = memberCanSend :: Maybe GroupMember -> Maybe MsgScope -> CM (Maybe DeliveryTaskContext) -> CM (Maybe DeliveryTaskContext) memberCanSend Nothing _ a = a -- channel message - was previously checked and allowed by relay memberCanSend (Just m@GroupMember {memberRole}) msgScope a = case msgScope of - Just MSMember {} -> a + Just (MSMember mId) + | sameMemberId mId m || memberRole >= GRModerator -> a + | otherwise -> messageError "member is not allowed to send to this support chat" $> Nothing Nothing | memberRole > GRObserver || memberPending m -> a | otherwise -> messageError "member is not allowed to send messages" $> Nothing @@ -1837,13 +1839,19 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- This patches initial sharedMsgId into chat item when locally deleted chat item -- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete). -- Chat item and update message which created it will have different sharedMsgId in this case... - let timed_ = rcvContactCITimed ct ttl - ts = ciContentTexts content - (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty - ci' <- withStore' $ \db -> do - createChatItemVersion db (chatItemId' ci) brokerTs mc - updateDirectChatItem' db user contactId ci content True live Nothing Nothing - toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci') + if isVoice mc && not (featureAllowed SCFVoice forContact ct) + then do + let ciContent = ciContentNoParse $ CIRcvChatFeatureRejected CFVoice + (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs ciContent Nothing Nothing False M.empty + toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci) + else do + let timed_ = rcvContactCITimed ct ttl + ts = ciContentTexts content + (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty + ci' <- withStore' $ \db -> do + createChatItemVersion db (chatItemId' ci) brokerTs mc + updateDirectChatItem' db user contactId ci content True live Nothing Nothing + toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci') where brokerTs = metaBrokerTs msgMeta content = CIRcvMsgContent mc @@ -2073,15 +2081,22 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = (gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m mc msgScope_ pure (gInfo', CDGroupRcv gInfo' scopeInfo m', mentions', scopeInfo) Nothing -> pure (gInfo, CDChannelRcv gInfo Nothing, mentions, Nothing) - (ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions' - ci' <- withStore' $ \db -> do - createChatItemVersion db (chatItemId' ci) brokerTs mc - updateGroupChatItem db user groupId ci content True live Nothing - ci'' <- case chatDir of - CDGroupRcv gi' _ m' -> blockedMemberCI gi' m' ci' - CDChannelRcv {} -> pure ci' - toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci'') - pure $ Just $ infoToDeliveryContext gInfo' scopeInfo showGroupAsSender + case m_ >>= \m -> prohibitedGroupContent gInfo' m scopeInfo mc ft_ (Nothing :: Maybe String) False of + Just f -> do + let ciContent = ciContentNoParse $ CIRcvGroupFeatureRejected f + (ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs ciContent Nothing timed_ False M.empty + groupMsgToView cInfo ci + pure Nothing + Nothing -> do + (ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions' + ci' <- withStore' $ \db -> do + createChatItemVersion db (chatItemId' ci) brokerTs mc + updateGroupChatItem db user groupId ci content True live Nothing + ci'' <- case chatDir of + CDGroupRcv gi' _ m' -> blockedMemberCI gi' m' ci' + CDChannelRcv {} -> pure ci' + toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci'') + pure $ Just $ infoToDeliveryContext gInfo' scopeInfo showGroupAsSender where content = CIRcvMsgContent mc ts@(_, ft_) = msgContentTexts mc diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index d90429f58e..5800ab5bdd 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -119,6 +119,11 @@ checkChatType x = case testEquality (chatTypeI @c) (chatTypeI @c') of data GroupChatScope = GCSMemberSupport {groupMemberId_ :: Maybe GroupMemberId} -- Nothing means own conversation with support deriving (Eq, Show, Ord) +sendAsGroup' :: GroupInfo -> Maybe GroupChatScope -> Bool +sendAsGroup' gInfo@GroupInfo {membership} scope = case scope of + Nothing -> useRelays' gInfo && memberRole' membership == GROwner + Just (GCSMemberSupport _) -> False + data GroupChatScopeTag = GCSTMemberSupport_ deriving (Eq, Show) diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 3f5414a1d8..93db63ee71 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1555,6 +1555,7 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe (Binary invId, groupLink, minVersion reqChatVRange, maxVersion reqChatVRange, groupId) insertOwner_ currentTs groupId = do let MemberIdRole {memberId, memberRole} = fromMember + VersionRange minV maxV = reqChatVRange (localDisplayName, profileId) <- createNewMemberProfile_ db user fromMemberProfile currentTs indexInGroup <- getUpdateNextIndexInGroup_ db groupId liftIO $ do @@ -1563,11 +1564,13 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe [sql| INSERT INTO group_members ( group_id, index_in_group, member_id, member_role, member_category, member_status, - user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at, + peer_chat_min_version, peer_chat_max_version) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (groupId, indexInGroup, memberId, memberRole, GCHostMember, GSMemAccepted) :. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs) + :. (minV, maxV) ) insertedRowId db diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 366074acc2..99880b23d7 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -523,8 +523,9 @@ SEARCH users USING COVERING INDEX sqlite_autoindex_users_1 (contact_id=?) Query: INSERT INTO group_members ( group_id, index_in_group, member_id, member_role, member_category, member_status, - user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at, + peer_chat_min_version, peer_chat_max_version) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) Plan: SEARCH group_relays USING COVERING INDEX idx_group_relays_group_member_id (group_member_id=?) diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 6fb55c84ce..b4acaedd39 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -494,9 +494,6 @@ data GroupInfo = GroupInfo useRelays' :: GroupInfo -> Bool useRelays' GroupInfo {useRelays} = isTrue useRelays -sendAsGroup' :: GroupInfo -> Bool -sendAsGroup' gInfo@GroupInfo {membership} = useRelays' gInfo && memberRole' membership == GROwner - groupId' :: GroupInfo -> GroupId groupId' GroupInfo {groupId} = groupId diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index c02d2b8433..be189379c9 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -176,6 +176,7 @@ data GroupFeature | GFSimplexLinks | GFReports | GFHistory + | GFSupport | GFSessions | GFComments deriving (Show) @@ -190,6 +191,7 @@ data SGroupFeature (f :: GroupFeature) where SGFSimplexLinks :: SGroupFeature 'GFSimplexLinks SGFReports :: SGroupFeature 'GFReports SGFHistory :: SGroupFeature 'GFHistory + SGFSupport :: SGroupFeature 'GFSupport SGFSessions :: SGroupFeature 'GFSessions SGFComments :: SGroupFeature 'GFComments @@ -218,6 +220,7 @@ groupFeatureNameText = \case GFSimplexLinks -> "SimpleX links" GFReports -> "Member reports" GFHistory -> "Recent history" + GFSupport -> "Chat with admins" GFSessions -> "Chat sessions" GFComments -> "Comments" @@ -248,11 +251,12 @@ allGroupFeatures = AGF SGFFiles, AGF SGFSimplexLinks, AGF SGFReports, - AGF SGFHistory + AGF SGFHistory, + AGF SGFSupport ] groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) -groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, sessions, comments} = case f of +groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, support, sessions, comments} = case f of SGFTimedMessages -> timedMessages SGFDirectMessages -> directMessages SGFFullDelete -> fullDelete @@ -262,6 +266,7 @@ groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reac SGFSimplexLinks -> simplexLinks SGFReports -> reports SGFHistory -> history + SGFSupport -> support SGFSessions -> sessions SGFComments -> comments @@ -276,6 +281,7 @@ toGroupFeature = \case SGFSimplexLinks -> GFSimplexLinks SGFReports -> GFReports SGFHistory -> GFHistory + SGFSupport -> GFSupport SGFSessions -> GFSessions SGFComments -> GFComments @@ -289,7 +295,7 @@ instance GroupPreferenceI (Maybe GroupPreferences) where getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) instance GroupPreferenceI FullGroupPreferences where - getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, sessions, comments} = case f of + getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, support, sessions, comments} = case f of SGFTimedMessages -> timedMessages SGFDirectMessages -> directMessages SGFFullDelete -> fullDelete @@ -299,6 +305,7 @@ instance GroupPreferenceI FullGroupPreferences where SGFSimplexLinks -> simplexLinks SGFReports -> reports SGFHistory -> history + SGFSupport -> support SGFSessions -> sessions SGFComments -> comments {-# INLINE getGroupPreference #-} @@ -314,6 +321,7 @@ data GroupPreferences = GroupPreferences simplexLinks :: Maybe SimplexLinksGroupPreference, reports :: Maybe ReportsGroupPreference, history :: Maybe HistoryGroupPreference, + support :: Maybe SupportGroupPreference, sessions :: Maybe SessionsGroupPreference, comments :: Maybe CommentsGroupPreference, commands :: Maybe [ChatBotCommand] @@ -365,6 +373,7 @@ setGroupPreference_ f pref prefs = SGFSimplexLinks -> prefs {simplexLinks = pref} SGFReports -> prefs {reports = pref} SGFHistory -> prefs {history = pref} + SGFSupport -> prefs {support = pref} SGFSessions -> prefs {sessions = pref} SGFComments -> prefs {comments = pref} @@ -408,6 +417,7 @@ data FullGroupPreferences = FullGroupPreferences simplexLinks :: SimplexLinksGroupPreference, reports :: ReportsGroupPreference, history :: HistoryGroupPreference, + support :: SupportGroupPreference, sessions :: SessionsGroupPreference, comments :: CommentsGroupPreference, commands :: ListDef ChatBotCommand @@ -478,13 +488,14 @@ defaultGroupPrefs = simplexLinks = SimplexLinksGroupPreference {enable = FEOn, role = Nothing}, reports = ReportsGroupPreference {enable = FEOn}, history = HistoryGroupPreference {enable = FEOff}, + support = SupportGroupPreference {enable = FEOn}, sessions = SessionsGroupPreference {enable = FEOff, role = Nothing}, comments = CommentsGroupPreference {enable = FEOff, duration = Nothing}, commands = ListDef [] } emptyGroupPrefs :: GroupPreferences -emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing businessGroupPrefs :: Preferences -> GroupPreferences businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice, files, sessions, commands} = @@ -515,6 +526,7 @@ defaultBusinessGroupPrefs = simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing, reports = Just $ ReportsGroupPreference FEOff, history = Just $ HistoryGroupPreference FEOn, + support = Just $ SupportGroupPreference FEOn, sessions = Just $ SessionsGroupPreference FEOn Nothing, comments = Just $ CommentsGroupPreference FEOff Nothing, commands = Nothing @@ -647,6 +659,10 @@ data HistoryGroupPreference = HistoryGroupPreference {enable :: GroupFeatureEnabled} deriving (Eq, Show) +data SupportGroupPreference = SupportGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show) + data SessionsGroupPreference = SessionsGroupPreference {enable :: GroupFeatureEnabled, role :: Maybe GroupMemberRole} deriving (Eq, Show) @@ -699,6 +715,9 @@ instance HasField "enable" ReportsGroupPreference GroupFeatureEnabled where instance HasField "enable" HistoryGroupPreference GroupFeatureEnabled where hasField p@HistoryGroupPreference {enable} = (\e -> p {enable = e}, enable) +instance HasField "enable" SupportGroupPreference GroupFeatureEnabled where + hasField p@SupportGroupPreference {enable} = (\e -> p {enable = e}, enable) + instance HasField "enable" SessionsGroupPreference GroupFeatureEnabled where hasField p@SessionsGroupPreference {enable} = (\e -> p {enable = e}, enable) @@ -759,6 +778,12 @@ instance GroupFeatureI 'GFHistory where groupPrefParam _ = Nothing groupPrefRole _ = Nothing +instance GroupFeatureI 'GFSupport where + type GroupFeaturePreference 'GFSupport = SupportGroupPreference + sGroupFeature = SGFSupport + groupPrefParam _ = Nothing + groupPrefRole _ = Nothing + instance GroupFeatureI 'GFSessions where type GroupFeaturePreference 'GFSessions = SessionsGroupPreference sGroupFeature = SGFSessions @@ -781,6 +806,8 @@ instance GroupFeatureNoRoleI 'GFReports instance GroupFeatureNoRoleI 'GFHistory +instance GroupFeatureNoRoleI 'GFSupport + instance GroupFeatureNoRoleI 'GFComments instance HasField "role" DirectMessagesGroupPreference (Maybe GroupMemberRole) where @@ -973,6 +1000,7 @@ mergeGroupPreferences groupPreferences = simplexLinks = pref SGFSimplexLinks, reports = pref SGFReports, history = pref SGFHistory, + support = pref SGFSupport, sessions = pref SGFSessions, comments = pref SGFComments, commands = ListDef $ fromMaybe [] $ groupPreferences >>= commands_ @@ -993,6 +1021,7 @@ toGroupPreferences groupPreferences@FullGroupPreferences {commands = ListDef cmd simplexLinks = pref SGFSimplexLinks, reports = pref SGFReports, history = pref SGFHistory, + support = pref SGFSupport, sessions = pref SGFSessions, comments = pref SGFComments, commands = Just cmds @@ -1123,11 +1152,13 @@ $(J.deriveJSON defaultJSON ''ReportsGroupPreference) $(J.deriveJSON defaultJSON ''HistoryGroupPreference) -$(J.deriveToJSON defaultJSON ''SessionsGroupPreference) +$(J.deriveToJSON defaultJSON ''SupportGroupPreference) -instance FromJSON SessionsGroupPreference where - parseJSON v = $(J.mkParseJSON defaultJSON ''SessionsGroupPreference) v - omittedField = Just SessionsGroupPreference {enable = FEOff, role = Nothing} +instance FromJSON SupportGroupPreference where + parseJSON v = $(J.mkParseJSON defaultJSON ''SupportGroupPreference) v + omittedField = Just SupportGroupPreference {enable = FEOn} + +$(J.deriveJSON defaultJSON ''SessionsGroupPreference) $(J.deriveToJSON defaultJSON ''CommentsGroupPreference) diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 714aa0c0ed..279a09e718 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -170,7 +170,7 @@ termSettings :: VirtualTerminalSettings termSettings = VirtualTerminalSettings { virtualType = "xterm", - virtualWindowSize = pure C.Size {height = 20, width = 6000}, + virtualWindowSize = pure C.Size {height = 24, width = 6000}, virtualEvent = retry, virtualInterrupt = retry } diff --git a/tests/ChatTests/ChatList.hs b/tests/ChatTests/ChatList.hs index 14d48dbf60..889915a6e8 100644 --- a/tests/ChatTests/ChatList.hs +++ b/tests/ChatTests/ChatList.hs @@ -200,14 +200,14 @@ testPaginationAllChatTypes = ts7 <- iso8601Show <$> getCurrentTime - getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] - getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on")] + getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] + getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on")] getChats_ alice ("after=" <> ts2 <> " count=2") [(":3", ""), ("@cath", "Audio/video calls: enabled")] - getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", "Recent history: on"), (":3", "")] - getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", "")] + getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", "Chat with admins: on"), (":3", "")] + getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", "")] getChats_ alice ("before=" <> ts4 <> " count=10") [(":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] - getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] - getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Recent history: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] + getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] + getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", "Chat with admins: on"), (":3", ""), ("@cath", "Audio/video calls: enabled"), ("@bob", "hey")] getChats_ alice ("after=" <> ts7 <> " count=10") [] getChats_ alice ("before=" <> ts1 <> " count=10") [] @@ -219,11 +219,11 @@ testPaginationAllChatTypes = alice ##> "/_settings #1 {\"enableNtfs\":\"all\",\"favorite\":true}" alice <## "ok" - getChats_ alice queryFavorite [("#team", "Recent history: on"), ("@bob", "hey")] + getChats_ alice queryFavorite [("#team", "Chat with admins: on"), ("@bob", "hey")] getChats_ alice ("before=" <> ts4 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", "Recent history: on")] + getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", "Chat with admins: on")] getChats_ alice ("after=" <> ts1 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", "Recent history: on")] + getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", "Chat with admins: on")] let queryUnread = "{\"type\": \"filters\", \"favorite\": false, \"unread\": true}" diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 3ff884d3d1..89fb0a2004 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -231,6 +231,8 @@ chatGroupTests = do it "should remove support chat with member when member is removed" testScopedSupportMemberRemoved it "should remove support chat with member when user removes member" testScopedSupportUserRemovesMember it "should remove support chat with member when member leaves" testScopedSupportMemberLeaves + it "should respect support preference in group" testSupportPreferenceGroup + it "should respect support preference in channel" testSupportPreferenceChannel -- TODO [relays] add tests for channels -- TODO - tests with delivery loop over members restored after restart -- TODO - delivery in support scopes inside channels @@ -267,6 +269,7 @@ chatGroupTests = do it "owner should update profile in channel (signed)" testChannelOwnerProfileUpdate it "subscriber should update profile in channel (signed)" testChannelSubscriberProfileUpdate it "should report relay results when one relay deleted its address" testChannelCreateDeletedRelay + it "should deliver support scope messages via relay" testChannelSupportScope describe "channel message operations" $ do it "should update channel message" testChannelMessageUpdate it "should delete channel message" testChannelMessageDelete @@ -457,7 +460,7 @@ testChatPaginationInitial = testChatOpts2 opts aliceProfile bobProfile $ \alice forM_ ([1 .. 10] :: [Int]) $ \n -> bob <# ("#team alice> " <> show n) -- All messages are unread for bob, should return area around unread - bob #$> ("/_get chat #1 initial=2", chat, [(0, "Recent history: on"), (0, "connected"), (0, "1"), (0, "2"), (0, "3")]) + bob #$> ("/_get chat #1 initial=2", chat, [(0, "Chat with admins: on"), (0, "connected"), (0, "1"), (0, "2"), (0, "3")]) -- Read next 2 items let itemIds = intercalate "," $ map groupItemId [1 .. 2] @@ -646,7 +649,7 @@ testGroup2 = ] dan <##> alice -- show last messages - alice ##> "/t #club 20" + alice ##> "/t #club 21" alice -- these strings are expected in any order because of sorting by time and rounding of time for sent <##? ( map (ConsoleString . ("#club " <> )) groupFeatureStrs @@ -1667,6 +1670,7 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile alice <## "SimpleX links: on" alice <## "Member reports: on" alice <## "Recent history: on" + alice <## "Chat with admins: on" bobAddedDan :: HasCallStack => TestCC -> IO () bobAddedDan cc = do cc <## "#team: bob added dan (Daniel) to the group (connecting...)" @@ -8400,6 +8404,120 @@ testScopedSupportMemberLeaves = { markRead = False } +testSupportPreferenceGroup :: HasCallStack => TestParams -> IO () +testSupportPreferenceGroup = + testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do + createGroup3' "team" alice (bob, GRMember) (cath, GRMember) + + threadDelay 1000000 + + -- support enabled by default, bob sends to support + bob #> "#team (support) hello" + alice <# "#team (support: bob) bob> hello" + + -- alice replies + alice #> "#team (support: bob) hi" + bob <# "#team (support) alice> hi" + + -- alice disables support + alice ##> "/set support #team off" + alice <## "updated group preferences:" + alice <## "Chat with admins: off" + concurrentlyN_ + [ do + bob <## "alice updated group #team:" + bob <## "updated group preferences:" + bob <## "Chat with admins: off", + do + cath <## "alice updated group #team:" + cath <## "updated group preferences:" + cath <## "Chat with admins: off" + ] + + threadDelay 500000 + + -- cath can't send support (no existing chat) + cath ##> "#team (support) hey" + cath <## "bad chat command: feature not allowed Chat with admins" + + -- alice can't send to cath's support (no existing chat) + alice ##> "#team (support: cath) hey" + alice <## "bad chat command: feature not allowed Chat with admins" + + -- bob can still send (existing chat) + bob #> "#team (support) still here" + alice <# "#team (support: bob) bob> still here" + + -- alice can still send to bob (existing chat) + alice #> "#team (support: bob) yes" + bob <# "#team (support) alice> yes" + +testSupportPreferenceChannel :: HasCallStack => TestParams -> IO () +testSupportPreferenceChannel ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "relay" relayProfile $ \relay -> + withNewTestChat ps "bob" bobProfile $ \bob -> + withNewTestChat ps "cath" cathProfile $ \cath -> do + (shortLink, fullLink) <- prepareChannel1Relay "team" alice relay + memberJoinChannel "team" [relay] [alice] shortLink fullLink bob + memberJoinChannel "team" [relay] [alice] shortLink fullLink cath + + threadDelay 1000000 + + alice ##> "/set support #team on" + alice <## "updated group preferences:" + alice <## "Chat with admins: on" + toggledSupport relay "alice" "team" "on" + concurrentlyN_ + [ toggledSupport bob "alice" "team" "on", + toggledSupport cath "alice" "team" "on" + ] + + -- support enabled by default, bob sends to support + bob #> "#team (support) hello" + relay <# "#team (support: bob) bob> hello" + alice <# "#team (support: bob) bob> hello [>>]" + + -- alice replies + alice #> "#team (support: bob) hi" + relay <# "#team (support: bob) alice> hi" + bob <# "#team (support) alice> hi [>>]" + + -- alice disables support + + threadDelay 1000000 + + alice ##> "/set support #team off" + alice <## "updated group preferences:" + alice <## "Chat with admins: off" + toggledSupport relay "alice" "team" "off" + concurrentlyN_ + [ toggledSupport bob "alice" "team" "off", + toggledSupport cath "alice" "team" "off" + ] + + threadDelay 500000 + + -- cath can't send support (no existing chat) + cath ##> "#team (support) hey" + cath <## "bad chat command: feature not allowed Chat with admins" + alice ##> "#team (support: cath) hey too" + alice <## "bad chat command: feature not allowed Chat with admins" + + -- bob can still send (existing chat) + bob #> "#team (support) still here" + concurrentlyN_ + [ relay <# "#team (support: bob) bob> still here", + alice <# "#team (support: bob) bob> still here [>>]" + ] + + -- alice can still send to bob (existing chat) + alice #> "#team (support: bob) yes" + concurrentlyN_ + [ relay <# "#team (support: bob) alice> yes", + bob <# "#team (support) alice> yes [>>]" + ] + testChannels1RelayDeliver :: HasCallStack => TestParams -> IO () testChannels1RelayDeliver ps = withNewTestChat ps "alice" aliceProfile $ \alice -> do @@ -8858,7 +8976,7 @@ testChannelLinkAfterWelcomeUpdate ps = shortLink' `shouldBe` shortLink fullLink' `shouldBe` fullLink memberJoinChannel "team" [bob] [alice] shortLink' fullLink' dan - dan #$> ("/_get chat #1 count=100", chat, groupFeaturesNoE2E <> [(0, "welcome to team"), (0, T.unpack publicGroupNoE2EText), (0, "connected")]) + dan #$> ("/_get chat #1 count=100", chat, channelFeaturesNoE2E <> [(0, "welcome to team"), (0, T.unpack publicGroupNoE2EText), (0, "connected")]) alice #> "#team hi" bob <# "#team> hi" @@ -9568,6 +9686,45 @@ testChannelCreateDeletedRelay ps = -- bob's agent reports AUTH error when the queue is gone — drain it. void $ getTermLine bob +testChannelSupportScope :: HasCallStack => TestParams -> IO () +testChannelSupportScope ps = + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "relay" relayProfile $ \relay -> + withNewTestChat ps "cath" cathProfile $ \cath -> + withNewTestChat ps "dan" danProfile $ \dan -> do + (shortLink, fullLink) <- prepareChannel1Relay "team" alice relay + memberJoinChannel "team" [relay] [alice] shortLink fullLink cath + memberJoinChannel "team" [relay] [alice] shortLink fullLink dan + + threadDelay 1000000 + + alice ##> "/set support #team on" + alice <## "updated group preferences:" + alice <## "Chat with admins: on" + toggledSupport relay "alice" "team" "on" + concurrentlyN_ + [ toggledSupport cath "alice" "team" "on", + toggledSupport dan "alice" "team" "on" + ] + + -- owner sends to cath's support scope, dan doesn't receive + alice #> "#team (support: cath) hello" + relay <# "#team (support: cath) alice> hello" + cath <# "#team (support) alice> hello [>>]" + (dan "#team (support) hi" + relay <# "#team (support: cath) cath> hi" + alice <# "#team (support: cath) cath> hi [>>]" + (dan TestCC -> String -> String -> String -> IO () +toggledSupport c owner channel onOff = do + c <## (owner <> " updated group #" <> channel <> ": (signed)") + c <## "updated group preferences:" + c <## ("Chat with admins: " <> onOff) + testChannelMessageUpdate :: HasCallStack => TestParams -> IO () testChannelMessageUpdate ps = withNewTestChat ps "alice" aliceProfile $ \alice -> diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index ebc9056164..d42e833c39 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -290,7 +290,10 @@ groupFeatures :: [(Int, String)] groupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 0 groupFeaturesNoE2E :: [(Int, String)] -groupFeaturesNoE2E = map (\(a, _, _) -> a) $ ((1, "chat banner"), Nothing, Nothing) : groupFeatures_ 0 +groupFeaturesNoE2E = map (\(a, _, _) -> a) $ ((1, "chat banner"), Nothing, Nothing) : groupFeatures_ 0 False + +channelFeaturesNoE2E :: [(Int, String)] +channelFeaturesNoE2E = map (\(a, _, _) -> a) $ ((1, "chat banner"), Nothing, Nothing) : groupFeatures_ 0 True sndGroupFeatures :: [(Int, String)] sndGroupFeatures = map (\(a, _, _) -> a) $ groupFeatures'' 1 @@ -299,10 +302,10 @@ groupFeatureStrs :: [String] groupFeatureStrs = map (\(a, _, _) -> snd a) $ groupFeatures'' 0 groupFeatures'' :: Int -> [((Int, String), Maybe (Int, String), Maybe String)] -groupFeatures'' dir = ((1, "chat banner"), Nothing, Nothing) : ((dir, e2eeInfoNoPQStr), Nothing, Nothing) : groupFeatures_ dir +groupFeatures'' dir = ((1, "chat banner"), Nothing, Nothing) : ((dir, e2eeInfoNoPQStr), Nothing, Nothing) : groupFeatures_ dir False -groupFeatures_ :: Int -> [((Int, String), Maybe (Int, String), Maybe String)] -groupFeatures_ dir = +groupFeatures_ :: Int -> Bool -> [((Int, String), Maybe (Int, String), Maybe String)] +groupFeatures_ dir isChannel = [ ((dir, "Disappearing messages: off"), Nothing, Nothing), ((dir, "Direct messages: on"), Nothing, Nothing), ((dir, "Full deletion: off"), Nothing, Nothing), @@ -311,7 +314,8 @@ groupFeatures_ dir = ((dir, "Files and media: on"), Nothing, Nothing), ((dir, "SimpleX links: on"), Nothing, Nothing), ((dir, "Member reports: on"), Nothing, Nothing), - ((dir, "Recent history: on"), Nothing, Nothing) + ((dir, "Recent history: on"), Nothing, Nothing), + ((dir, "Chat with admins: " <> (if isChannel then "off" else "on")), Nothing, Nothing) ] businessGroupFeatures :: [(Int, String)] @@ -329,7 +333,8 @@ businessGroupFeatures'' dir = ((dir, "Files and media: on"), Nothing, Nothing), ((dir, "SimpleX links: on"), Nothing, Nothing), ((dir, "Member reports: off"), Nothing, Nothing), - ((dir, "Recent history: on"), Nothing, Nothing) + ((dir, "Recent history: on"), Nothing, Nothing), + ((dir, "Chat with admins: on"), Nothing, Nothing) ] itemId :: Int -> String diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 3ba789b988..01399f6bbb 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -101,7 +101,7 @@ testChatPreferences :: Maybe Preferences testChatPreferences = Just Preferences {voice = Just VoicePreference {allow = FAYes}, files = Nothing, fullDelete = Nothing, timedMessages = Nothing, calls = Nothing, reactions = Just ReactionsPreference {allow = FAYes}, sessions = Nothing, commands = Nothing} testGroupPreferences :: Maybe GroupPreferences -testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing, sessions = Nothing, comments = Nothing, commands = Nothing} +testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing, support = Nothing, sessions = Nothing, comments = Nothing, commands = Nothing} testProfile :: Profile testProfile = Profile {displayName = "alice", fullName = "Alice", shortDescr = Nothing, image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), peerType = Nothing, contactLink = Nothing, preferences = testChatPreferences} From 3d04ff9560128aab3988397fdb4e7b327e8e0a46 Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Sun, 26 Apr 2026 15:21:38 +0100 Subject: [PATCH 24/77] ui: different preference texts for channels (#6889) * ios: different preference texts for channels * fix * ios translations * export localizations * restore translations * fix ExternalLink, process localizations * kotlin: different strings for channel preferences * add translations * different strings for channels * export localizations --------- Co-authored-by: Evgeny Poberezkin --- .../Chat/Group/GroupPreferencesView.swift | 6 +- .../ios/Shared/Views/Helpers/ShareSheet.swift | 5 - .../NetworkAndServers/OperatorView.swift | 8 +- .../bg.xcloc/Localized Contents/bg.xliff | 166 ++++++++++++--- .../cs.xcloc/Localized Contents/cs.xliff | 166 ++++++++++++--- .../de.xcloc/Localized Contents/de.xliff | 182 +++++++++++++--- .../en.xcloc/Localized Contents/en.xliff | 194 +++++++++++++++--- .../es.xcloc/Localized Contents/es.xliff | 182 +++++++++++++--- .../fi.xcloc/Localized Contents/fi.xliff | 165 ++++++++++++--- .../fr.xcloc/Localized Contents/fr.xliff | 166 ++++++++++++--- .../hu.xcloc/Localized Contents/hu.xliff | 182 +++++++++++++--- .../it.xcloc/Localized Contents/it.xliff | 182 +++++++++++++--- .../ja.xcloc/Localized Contents/ja.xliff | 165 ++++++++++++--- .../nl.xcloc/Localized Contents/nl.xliff | 166 ++++++++++++--- .../pl.xcloc/Localized Contents/pl.xliff | 166 ++++++++++++--- .../ru.xcloc/Localized Contents/ru.xliff | 166 ++++++++++++--- .../th.xcloc/Localized Contents/th.xliff | 165 ++++++++++++--- .../tr.xcloc/Localized Contents/tr.xliff | 166 ++++++++++++--- .../uk.xcloc/Localized Contents/uk.xliff | 166 ++++++++++++--- .../Localized Contents/zh-Hans.xliff | 166 ++++++++++++--- apps/ios/SimpleXChat/ChatTypes.swift | 78 +++++-- apps/ios/bg.lproj/Localizable.strings | 27 +-- apps/ios/cs.lproj/Localizable.strings | 27 +-- apps/ios/de.lproj/Localizable.strings | 75 +++++-- apps/ios/es.lproj/Localizable.strings | 75 +++++-- apps/ios/fi.lproj/Localizable.strings | 21 +- apps/ios/fr.lproj/Localizable.strings | 27 +-- apps/ios/hu.lproj/Localizable.strings | 75 +++++-- apps/ios/it.lproj/Localizable.strings | 75 +++++-- apps/ios/ja.lproj/Localizable.strings | 24 +-- apps/ios/nl.lproj/Localizable.strings | 27 +-- apps/ios/pl.lproj/Localizable.strings | 27 +-- apps/ios/ru.lproj/Localizable.strings | 27 +-- apps/ios/th.lproj/Localizable.strings | 21 +- apps/ios/tr.lproj/Localizable.strings | 27 +-- apps/ios/uk.lproj/Localizable.strings | 27 +-- apps/ios/zh-Hans.lproj/Localizable.strings | 27 +-- .../chat/simplex/common/model/SimpleXAPI.kt | 43 ++-- .../views/chat/group/GroupPreferences.kt | 6 +- .../commonMain/resources/MR/base/strings.xml | 21 ++ .../commonMain/resources/MR/de/strings.xml | 18 ++ .../commonMain/resources/MR/es/strings.xml | 18 ++ .../commonMain/resources/MR/hu/strings.xml | 18 ++ .../commonMain/resources/MR/it/strings.xml | 18 ++ 44 files changed, 2936 insertions(+), 823 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 49b9829830..cc2feef706 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -120,7 +120,7 @@ struct GroupPreferencesView: View { set: { on, _ in enableFeature.wrappedValue = on ? .on : .off } ) settingsRow(icon, color: color) { - Toggle(feature.text, isOn: enable) + Toggle(feature.text(isChannel: groupInfo.isChannel), isOn: enable) } .disabled(disabled) if timedOn { @@ -143,7 +143,7 @@ struct GroupPreferencesView: View { } } else { settingsRow(icon, color: color) { - infoRow(Text(feature.text), enableFeature.wrappedValue.text) + infoRow(Text(feature.text(isChannel: groupInfo.isChannel)), enableFeature.wrappedValue.text) } if timedOn { infoRow("Delete after", timeText(preferences.timedMessages.ttl)) @@ -162,7 +162,7 @@ struct GroupPreferencesView: View { } } footer: { VStack(alignment: .leading) { - Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner)) + Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner, isChannel: groupInfo.isChannel)) if let notice { Text(notice) } } .foregroundColor(theme.colors.secondary) diff --git a/apps/ios/Shared/Views/Helpers/ShareSheet.swift b/apps/ios/Shared/Views/Helpers/ShareSheet.swift index 3adf3f6a64..9f2fc833ba 100644 --- a/apps/ios/Shared/Views/Helpers/ShareSheet.swift +++ b/apps/ios/Shared/Views/Helpers/ShareSheet.swift @@ -115,11 +115,6 @@ struct ExternalLink: View { self.label = Text(titleKey) } - init(_ title: S, destination: URL) where Label == Text { - self.destination = destination - self.label = Text(title) - } - var body: some View { Button { openExternalLink(destination) } label: { label } } diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift index b54fa396a7..26f24f2f0f 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift @@ -364,11 +364,15 @@ struct OperatorInfoView: View { Text(d) } } - ExternalLink(serverOperator.info.website.absoluteString, destination: serverOperator.info.website) + ExternalLink(destination: serverOperator.info.website) { + Text(serverOperator.info.website.absoluteString) + } } if let selfhost = serverOperator.info.selfhost { Section { - ExternalLink(selfhost.text, destination: selfhost.link) + ExternalLink(destination: selfhost.link) { + Text(selfhost.text) + } } } } diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 0c916ee30b..c8c564ada7 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -975,6 +975,10 @@ swipe action Позволи необратимо изтриване на съобщение само ако вашият контакт го рарешава. (24 часа) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Позволи реакции на съобщения само ако вашият контакт ги разрешава. @@ -990,6 +994,10 @@ swipe action Позволи изпращането на лични съобщения до членовете. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Разреши изпращането на изчезващи съобщения. @@ -1000,6 +1008,10 @@ swipe action Позволи споделяне No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Позволи необратимо изтриване на изпратените съобщения. (24 часа) @@ -1834,7 +1846,8 @@ alert subtitle Chat with admins Чат с администраторите - chat toolbar + chat feature +chat toolbar Chat with member @@ -1851,11 +1864,23 @@ alert subtitle Чатове No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Чатове с членовете No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Проверявай за съобщенията на всеки 20 минути. @@ -2343,6 +2368,11 @@ This is your own one-time link! Продължи No comment provided by engineer. + + Contribute + Допринеси + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2998,6 +3028,10 @@ alert button Личните съобщения между членовете са забранени в тази група. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3102,6 +3136,10 @@ alert button Не изпращай история на нови членове. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -3254,6 +3292,10 @@ chat item action Разреши достъпа до камерата No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -4420,6 +4462,10 @@ Error: %2$@ Историята не се изпраща на нови членове. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Как работи SimpleX @@ -4656,9 +4702,9 @@ More improvements are coming soon! Първоначална роля No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Инсталирайте SimpleX Chat за терминал No comment provided by engineer. @@ -5168,6 +5214,10 @@ This is your link for group %@! Членовете на групата могат да добавят реакции към съобщенията. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Членовете на групата могат необратимо да изтриват изпратените съобщения. (24 часа) @@ -5318,6 +5368,14 @@ This is your link for group %@! Съобщенията от %@ ще бъдат показани! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5962,7 +6020,8 @@ Requires compatible VPN. Open Отвори - alert action + alert action +alert button Open Settings @@ -5995,6 +6054,10 @@ Requires compatible VPN. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6441,6 +6504,10 @@ Error: %@ Забрани аудио/видео разговорите. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Забрани необратимото изтриване на съобщения. @@ -6470,6 +6537,10 @@ Error: %@ Забрани изпращането на лични съобщения до членовете. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Забрани изпращането на изчезващи съобщения. @@ -6573,24 +6644,14 @@ Enable in *Network & servers* settings. Прочетете още No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Прочетете повече в Ръководство за потребителя. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Прочетете повече в нашето [GitHub хранилище](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Прочетете повече в нашето GitHub хранилище. No comment provided by engineer. @@ -7402,6 +7463,10 @@ chat item action Изпращане до последните 100 съобщения на нови членове. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7964,6 +8029,11 @@ report reason Квадрат, кръг или нещо между тях. No comment provided by engineer. + + Star on GitHub + Звезда в GitHub + No comment provided by engineer. + Start chat Започни чат @@ -8063,6 +8133,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8071,6 +8145,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8786,6 +8896,10 @@ To connect, please ask your contact to create another connection link and check На новите членове се изпращат до последните 100 съобщения. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Актуализация @@ -9763,21 +9877,11 @@ Relays can access channel messages. Вашите настройки No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Допринеси](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Изпратете ни имейл](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Звезда в GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_курсив_ diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 181ccb332c..1a0a53b884 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -960,6 +960,10 @@ swipe action Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí. (24 hodin) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Povolit reakce na zprávy, pokud je váš kontakt povolí. @@ -975,6 +979,10 @@ swipe action Povolit odesílání přímých zpráv členům. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Povolit odesílání mizících zpráv. @@ -985,6 +993,10 @@ swipe action Povolit sdílení No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Povolit nevratné smazání odeslaných zpráv. (24 hodin) @@ -1783,7 +1795,8 @@ alert subtitle Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1798,10 +1811,22 @@ alert subtitle Chaty No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -2241,6 +2266,11 @@ Toto je váš vlastní jednorázový odkaz! Pokračovat No comment provided by engineer. + + Contribute + Přispějte + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2882,6 +2912,10 @@ alert button Přímé zprávy mezi členy jsou v této skupině zakázány. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -2983,6 +3017,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -3130,6 +3168,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -4265,6 +4307,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Jak SimpleX funguje @@ -4493,9 +4539,9 @@ More improvements are coming soon! Počáteční role No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Nainstalujte SimpleX Chat pro terminál No comment provided by engineer. @@ -4990,6 +5036,10 @@ This is your link for group %@! Členové skupin mohou přidávat reakce na zprávy. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Členové skupiny mohou nevratně mazat odeslané zprávy. (24 hodin) @@ -5136,6 +5186,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5764,7 +5822,8 @@ Vyžaduje povolení sítě VPN. Open Otevřít - alert action + alert action +alert button Open Settings @@ -5797,6 +5856,10 @@ Vyžaduje povolení sítě VPN. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6226,6 +6289,10 @@ Error: %@ Zákaz audio/video hovorů. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Zakázat nevratné mazání zpráv. @@ -6254,6 +6321,10 @@ Error: %@ Zakázat odesílání přímých zpráv členům. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Zakázat posílání mizících zpráv. @@ -6355,24 +6426,14 @@ Enable in *Network & servers* settings. Přečíst více No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Více informací v [průvodci uživatele](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Více informací v průvodci uživatele. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Přečtěte si více v našem [GitHub repozitáři](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Přečtěte si více v našem GitHub repozitáři. No comment provided by engineer. @@ -7170,6 +7231,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7723,6 +7788,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + Hvězda na GitHubu + No comment provided by engineer. + Start chat Začít chat @@ -7819,6 +7889,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7827,6 +7901,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8523,6 +8633,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Aktualizovat @@ -9463,21 +9577,11 @@ Relays can access channel messages. Vaše nastavení No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Přispějte](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Pošlete nám e-mail](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Hvězda na GitHubu](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_kurzíva_ diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 32f9235dd1..6a603c8254 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -984,6 +984,10 @@ swipe action Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Erlauben Sie Reaktionen auf Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt. @@ -999,6 +1003,11 @@ swipe action Das Senden von Direktnachrichten an Gruppenmitglieder erlauben. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Das Senden von Direktnachrichten an Abonnenten erlauben. + No comment provided by engineer. + Allow sending disappearing messages. Das Senden von verschwindenden Nachrichten erlauben. @@ -1009,6 +1018,10 @@ swipe action Teilen erlauben No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Unwiederbringliches löschen von gesendeten Nachrichten erlauben. (24 Stunden) @@ -1861,7 +1874,8 @@ alert subtitle Chat with admins Chat mit Administratoren - chat toolbar + chat feature +chat toolbar Chat with member @@ -1878,11 +1892,23 @@ alert subtitle Chats No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Chats mit Mitgliedern No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Alle 20min Nachrichten überprüfen. @@ -2382,6 +2408,11 @@ Das ist Ihr eigener Einmal-Link! Weiter No comment provided by engineer. + + Contribute + Unterstützen Sie uns + No comment provided by engineer. + Conversation deleted! Chat-Inhalte entfernt! @@ -3081,6 +3112,11 @@ alert button In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + Direktnachrichten zwischen Abonnenten sind nicht erlaubt. + No comment provided by engineer. + Disable alert button @@ -3190,6 +3226,11 @@ alert button Den Nachrichtenverlauf nicht an neue Mitglieder senden. No comment provided by engineer. + + Do not send history to new subscribers. + Den Nachrichtenverlauf nicht an neue Abonnenten senden. + No comment provided by engineer. + Do not use credentials with proxy. Verwenden Sie keine Anmeldeinformationen mit einem Proxy. @@ -3355,6 +3396,10 @@ chat item action Kamera-Zugriff aktivieren No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Verschwindende Nachrichten sind per Voreinstellung aktiviert. @@ -4605,6 +4650,11 @@ Fehler: %2$@ Der Nachrichtenverlauf wird nicht an neue Gruppenmitglieder gesendet. No comment provided by engineer. + + History is not sent to new subscribers. + Der Nachrichtenverlauf wird nicht an neue Abonnenten gesendet. + No comment provided by engineer. + How SimpleX works Wie SimpleX funktioniert @@ -4852,9 +4902,9 @@ Weitere Verbesserungen sind bald verfügbar! Anfängliche Rolle No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Installieren Sie [SimpleX Chat als Terminalanwendung](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Installieren Sie SimpleX Chat als Terminalanwendung No comment provided by engineer. @@ -5399,6 +5449,10 @@ Das ist Ihr Link für die Gruppe %@! Gruppenmitglieder können eine Reaktion auf Nachrichten geben. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Gruppenmitglieder können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) @@ -5564,6 +5618,14 @@ Das ist Ihr Link für die Gruppe %@! Die Nachrichten von %@ werden angezeigt! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Nachrichten in diesem Chat werden nie gelöscht. @@ -6261,7 +6323,8 @@ Dies erfordert die Aktivierung eines VPNs. Open Öffnen - alert action + alert action +alert button Open Settings @@ -6298,6 +6361,10 @@ Dies erfordert die Aktivierung eines VPNs. Nutzungsbedingungen öffnen No comment provided by engineer. + + Open external link? + alert title + Open full link Vollständigen Link öffnen @@ -6788,6 +6855,10 @@ Fehler: %@ Audio-/Video-Anrufe nicht erlauben. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Unwiederbringliches löschen von Nachrichten nicht erlauben. @@ -6818,6 +6889,11 @@ Fehler: %@ Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + Das Senden von Direktnachrichten an Abonnenten nicht erlauben. + No comment provided by engineer. + Prohibit sending disappearing messages. Das Senden von verschwindenden Nachrichten nicht erlauben. @@ -6929,24 +7005,14 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Mehr erfahren No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Lesen Sie mehr dazu im Benutzerhandbuch. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen. - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen. - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Erfahren Sie in unserem [GitHub-Repository](https://github.com/simplex-chat/simplex-chat#readme) mehr dazu. + + Read more in our GitHub repository. + Erfahren Sie in unserem GitHub-Repository mehr dazu. No comment provided by engineer. @@ -7836,6 +7902,11 @@ chat item action Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Bis zu 100 der letzten Nachrichten an neue Abonnenten senden. + No comment provided by engineer. + Send your private feedback to groups. Senden Sie Ihr privates Feedback an Gruppen. @@ -8455,6 +8526,11 @@ report reason Quadratisch, kreisförmig oder irgendetwas dazwischen. No comment provided by engineer. + + Star on GitHub + Stern auf GitHub vergeben + No comment provided by engineer. + Start chat Starten Sie den Chat @@ -8560,6 +8636,11 @@ report reason Abonnent No comment provided by engineer. + + Subscriber reports + Abonnenten-Meldungen + chat feature + Subscriber will be removed from channel - this cannot be undone! Abonnent wird aus dem Kanal entfernt. Dies kann nicht rückgängig gemacht werden! @@ -8570,6 +8651,50 @@ report reason Abonnenten No comment provided by engineer. + + Subscribers can add message reactions. + Abonnenten können eine Reaktion auf Nachrichten geben. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Abonnenten können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Abonnenten können Nachrichten an Moderatoren melden. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Abonnenten können SimpleX-Links versenden. + No comment provided by engineer. + + + Subscribers can send direct messages. + Abonnenten können Direktnachrichten versenden. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Abonnenten können verschwindende Nachrichten versenden. + No comment provided by engineer. + + + Subscribers can send files and media. + Abonnenten können Dateien und Medien versenden. + No comment provided by engineer. + + + Subscribers can send voice messages. + Abonnenten können Sprachnachrichten versenden. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9345,6 +9470,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Bis zu 100 der letzten Nachrichten werden an neue Mitglieder gesendet. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Bis zu 100 der letzten Nachrichten werden an neue Abonnenten gesendet. + No comment provided by engineer. + Update Aktualisieren @@ -10397,21 +10527,11 @@ Relais können auf Kanalnachrichten zugreifen. Einstellungen No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Unterstützen Sie uns](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Senden Sie uns eine E-Mail](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Stern auf GitHub vergeben](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_kursiv_ diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 621bb1f128..9ebdad1759 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1001,6 +1001,11 @@ swipe action Allow irreversible message deletion only if your contact allows it to you. (24 hours) No comment provided by engineer. + + Allow members to chat with admins. + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Allow message reactions only if your contact allows them. @@ -1016,6 +1021,11 @@ swipe action Allow sending direct messages to members. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Allow sending disappearing messages. @@ -1026,6 +1036,11 @@ swipe action Allow sharing No comment provided by engineer. + + Allow subscribers to chat with admins. + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Allow to irreversibly delete sent messages. (24 hours) @@ -1882,7 +1897,8 @@ alert subtitle Chat with admins Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1899,11 +1915,26 @@ alert subtitle Chats No comment provided by engineer. + + Chats with admins are prohibited. + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Chats with members No comment provided by engineer. + + Chats with members are disabled + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Check messages every 20 min. @@ -2406,6 +2437,11 @@ This is your own one-time link! Continue No comment provided by engineer. + + Contribute + Contribute + No comment provided by engineer. + Conversation deleted! Conversation deleted! @@ -3106,6 +3142,11 @@ alert button Direct messages between members are prohibited. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable Disable @@ -3216,6 +3257,11 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Do not use credentials with proxy. @@ -3382,6 +3428,11 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + Enable chats with admins? + alert title + Enable disappearing messages by default. Enable disappearing messages by default. @@ -4635,6 +4686,11 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works How SimpleX works @@ -4882,9 +4938,9 @@ More improvements are coming soon! Initial role No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Install SimpleX Chat for terminal No comment provided by engineer. @@ -5432,6 +5488,11 @@ This is your link for group %@! Members can add message reactions. No comment provided by engineer. + + Members can chat with admins. + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Members can irreversibly delete sent messages. (24 hours) @@ -5597,6 +5658,16 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Messages in this chat will never be deleted. @@ -6300,7 +6371,8 @@ Requires compatible VPN. Open Open - alert action + alert action +alert button Open Settings @@ -6337,6 +6409,11 @@ Requires compatible VPN. Open conditions No comment provided by engineer. + + Open external link? + Open external link? + alert title + Open full link Open full link @@ -6833,6 +6910,11 @@ Error: %@ Prohibit audio/video calls. No comment provided by engineer. + + Prohibit chats with admins. + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Prohibit irreversible message deletion. @@ -6863,6 +6945,11 @@ Error: %@ Prohibit sending direct messages to members. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Prohibit sending disappearing messages. @@ -6975,24 +7062,14 @@ Enable in *Network & servers* settings. Read more No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Read more in User Guide. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Read more in our GitHub repository. No comment provided by engineer. @@ -7888,6 +7965,11 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Send your private feedback to groups. @@ -8512,6 +8594,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + Star on GitHub + No comment provided by engineer. + Start chat Start chat @@ -8617,6 +8704,11 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! Subscriber will be removed from channel - this cannot be undone! @@ -8627,6 +8719,51 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9406,6 +9543,11 @@ To connect, please ask your contact to create another connection link and check Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Update @@ -10462,21 +10604,11 @@ Relays can access channel messages. Your settings No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Send us email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italic_ diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 7be640c9b7..07004c5432 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -984,6 +984,10 @@ swipe action Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también lo permite. (24 horas) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Se permiten las reacciones a los mensajes pero sólo si tu contacto también las permite. @@ -999,6 +1003,11 @@ swipe action Se permiten mensajes directos entre miembros. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Se permiten mensajes directos entre suscriptores. + No comment provided by engineer. + Allow sending disappearing messages. Permites el envío de mensajes temporales. @@ -1009,6 +1018,10 @@ swipe action Permitir compartir No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Se permite la eliminación irreversible de mensajes. (24 horas) @@ -1861,7 +1874,8 @@ alert subtitle Chat with admins Chatea con administradores - chat toolbar + chat feature +chat toolbar Chat with member @@ -1878,11 +1892,23 @@ alert subtitle Chats No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Chat con miembros No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Comprobar mensajes cada 20 min. @@ -2382,6 +2408,11 @@ This is your own one-time link! Continuar No comment provided by engineer. + + Contribute + Contribuye + No comment provided by engineer. + Conversation deleted! ¡Conversación eliminada! @@ -3081,6 +3112,11 @@ alert button Los mensajes directos entre miembros del grupo no están permitidos. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + Los mensajes directos entre suscriptores del canal no están permitidos. + No comment provided by engineer. + Disable alert button @@ -3190,6 +3226,11 @@ alert button No se envía el historial a los miembros nuevos. No comment provided by engineer. + + Do not send history to new subscribers. + No se envía el historial a los suscriptores nuevos. + No comment provided by engineer. + Do not use credentials with proxy. No se usan credenciales con proxy. @@ -3355,6 +3396,10 @@ chat item action Permitir acceso a la cámara No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Activa por defecto los mensajes temporales. @@ -4605,6 +4650,11 @@ Error: %2$@ El historial no se envía a miembros nuevos. No comment provided by engineer. + + History is not sent to new subscribers. + El historial no se envía a suscriptores nuevos. + No comment provided by engineer. + How SimpleX works Cómo funciona SimpleX @@ -4852,9 +4902,9 @@ More improvements are coming soon! Rol inicial No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Instalar terminal para SimpleX Chat No comment provided by engineer. @@ -5399,6 +5449,10 @@ This is your link for group %@! Los miembros pueden añadir reacciones a los mensajes. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Los miembros del grupo pueden eliminar mensajes de forma irreversible. (24 horas) @@ -5564,6 +5618,14 @@ This is your link for group %@! ¡Los mensajes nuevos de %@ serán mostrados! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Los mensajes de esta conversación nunca se eliminan. @@ -6261,7 +6323,8 @@ Requiere activación de la VPN. Open Abrir - alert action + alert action +alert button Open Settings @@ -6298,6 +6361,10 @@ Requiere activación de la VPN. Abrir condiciones No comment provided by engineer. + + Open external link? + alert title + Open full link Abrir enlace completo @@ -6788,6 +6855,10 @@ Error: %@ No se permiten llamadas y videollamadas. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. No se permite la eliminación irreversible de mensajes. @@ -6818,6 +6889,11 @@ Error: %@ No se permiten mensajes directos entre miembros. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No se permiten mensajes directos entre suscriptores. + No comment provided by engineer. + Prohibit sending disappearing messages. No se permiten mensajes temporales. @@ -6929,24 +7005,14 @@ Actívalo en ajustes de *Servidores y Redes*. Saber más No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Conoce más en la Guía del Usuario. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Conoce más en nuestro repositorio GitHub. No comment provided by engineer. @@ -7836,6 +7902,11 @@ chat item action Se envían hasta 100 mensajes más recientes a los miembros nuevos. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Se envían hasta 100 mensajes más recientes a los suscriptores nuevos. + No comment provided by engineer. + Send your private feedback to groups. Envía tu comentario privado a los grupos. @@ -8455,6 +8526,11 @@ report reason Cuadrada, circular o cualquier forma intermedia. No comment provided by engineer. + + Star on GitHub + Estrella en GitHub + No comment provided by engineer. + Start chat Iniciar chat @@ -8560,6 +8636,11 @@ report reason Suscriptor No comment provided by engineer. + + Subscriber reports + Informes de suscriptores + chat feature + Subscriber will be removed from channel - this cannot be undone! El suscriptor será eliminado del canal. ¡No puede deshacerse! @@ -8570,6 +8651,50 @@ report reason Suscriptores No comment provided by engineer. + + Subscribers can add message reactions. + Los suscriptores pueden añadir reacciones a los mensajes. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Los suscriptores del canal pueden eliminar mensajes de forma irreversible. (24 horas) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Los suscriptores pueden informar de mensajes a los moderadores. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Los suscriptores del canal pueden enviar enlaces SimpleX. + No comment provided by engineer. + + + Subscribers can send direct messages. + Los suscriptores del canal pueden enviar mensajes directos. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Los suscriptores del canal pueden enviar mensajes temporales. + No comment provided by engineer. + + + Subscribers can send files and media. + Los suscriptores del canal pueden enviar archivos y multimedia. + No comment provided by engineer. + + + Subscribers can send voice messages. + Los suscriptores del canal pueden enviar mensajes de voz. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9345,6 +9470,11 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Hasta 100 últimos mensajes son enviados a los miembros nuevos. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Hasta 100 últimos mensajes son enviados a los suscriptores nuevos. + No comment provided by engineer. + Update Actualizar @@ -10397,21 +10527,11 @@ Los servidores tienen acceso a los mensajes del canal. Configuración No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribuye](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Contacta vía email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Estrella en GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italic_ diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index ad2f500c0e..93e0c2e3a7 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -899,6 +899,10 @@ swipe action Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle. (24 tuntia) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Salli reaktiot viesteihin vain, jos kontaktisi sallii ne. @@ -914,6 +918,10 @@ swipe action Salli yksityisviestien lähettäminen jäsenille. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Salli katoavien viestien lähettäminen. @@ -923,6 +931,10 @@ swipe action Allow sharing No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Salli lähetettyjen viestien peruuttamaton poistaminen. (24 tuntia) @@ -1676,7 +1688,8 @@ alert subtitle Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1691,10 +1704,22 @@ alert subtitle Keskustelut No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -2130,6 +2155,11 @@ This is your own one-time link! Jatka No comment provided by engineer. + + Contribute + Osallistu + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2771,6 +2801,10 @@ alert button Yksityisviestit jäsenten välillä ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -2872,6 +2906,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -3019,6 +3057,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -4151,6 +4193,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Miten SimpleX toimii @@ -4379,9 +4425,9 @@ More improvements are coming soon! Alkuperäinen rooli No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Asenna SimpleX Chat terminaalille No comment provided by engineer. @@ -4876,6 +4922,10 @@ This is your link for group %@! Ryhmän jäsenet voivat lisätä viestireaktioita. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Ryhmän jäsenet voivat poistaa lähetetyt viestit peruuttamattomasti. (24 tuntia) @@ -5022,6 +5072,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5648,7 +5706,8 @@ Edellyttää VPN:n sallimista. Open - alert action + alert action +alert button Open Settings @@ -5681,6 +5740,10 @@ Edellyttää VPN:n sallimista. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6110,6 +6173,10 @@ Error: %@ Estä ääni- ja videopuhelut. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Estä peruuttamaton viestien poistaminen. @@ -6138,6 +6205,10 @@ Error: %@ Estä suorien viestien lähettäminen jäsenille. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Estä katoavien viestien lähettäminen. @@ -6239,23 +6310,14 @@ Enable in *Network & servers* settings. Lue lisää No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Lue lisää Käyttöoppaasta. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Lue lisää GitHub-arkistosta. No comment provided by engineer. @@ -7052,6 +7114,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7604,6 +7670,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + Tähti GitHubissa + No comment provided by engineer. + Start chat Aloita keskustelu @@ -7700,6 +7771,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7708,6 +7783,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8403,6 +8514,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Päivitä @@ -9343,21 +9458,11 @@ Relays can access channel messages. Asetuksesi No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Osallistu](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Lähetä meille sähköpostia](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Tähti GitHubissa](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italic_ diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index e7a5ef470a..1de6a95c2b 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -975,6 +975,10 @@ swipe action Autoriser la suppression irréversible des messages uniquement si votre contact vous l'autorise. (24 heures) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Autoriser les réactions aux messages uniquement si votre contact les autorise. @@ -990,6 +994,10 @@ swipe action Autoriser l'envoi de messages directs aux membres. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Autorise l’envoi de messages éphémères. @@ -1000,6 +1008,10 @@ swipe action Autoriser le partage No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Autoriser la suppression irréversible de messages envoyés. (24 heures) @@ -1826,7 +1838,8 @@ alert subtitle Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1841,10 +1854,22 @@ alert subtitle Discussions No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Consulter les messages toutes les 20 minutes. @@ -2338,6 +2363,11 @@ Il s'agit de votre propre lien unique ! Continuer No comment provided by engineer. + + Contribute + Contribuer + No comment provided by engineer. + Conversation deleted! Conversation supprimée ! @@ -3024,6 +3054,10 @@ alert button Les messages directs entre membres sont interdits dans ce groupe. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3133,6 +3167,10 @@ alert button Ne pas envoyer d'historique aux nouveaux membres. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Ne pas utiliser d'identifiants avec le proxy. @@ -3295,6 +3333,10 @@ chat item action Autoriser l'accès à la caméra No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -4519,6 +4561,10 @@ Erreur : %2$@ L'historique n'est pas envoyé aux nouveaux membres. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Comment SimpleX fonctionne @@ -4761,9 +4807,9 @@ D'autres améliorations sont à venir ! Rôle initial No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Installer SimpleX Chat pour terminal No comment provided by engineer. @@ -5284,6 +5330,10 @@ Voici votre lien pour le groupe %@ ! Les membres du groupe peuvent ajouter des réactions aux messages. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Les membres du groupe peuvent supprimer de manière irréversible les messages envoyés. (24 heures) @@ -5444,6 +5494,14 @@ Voici votre lien pour le groupe %@ ! Les messages de %@ seront affichés ! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -6116,7 +6174,8 @@ Nécessite l'activation d'un VPN. Open Ouvrir - alert action + alert action +alert button Open Settings @@ -6151,6 +6210,10 @@ Nécessite l'activation d'un VPN. Ouvrir les conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6618,6 +6681,10 @@ Erreur : %@ Interdire les appels audio/vidéo. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Interdire la suppression irréversible des messages. @@ -6647,6 +6714,10 @@ Erreur : %@ Interdire l'envoi de messages directs aux membres. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Interdire l’envoi de messages éphémères. @@ -6757,24 +6828,14 @@ Activez-le dans les paramètres *Réseau et serveurs*. En savoir plus No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Pour en savoir plus, consultez le Guide de l'utilisateur. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Pour en savoir plus, consultez notre [dépôt GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Pour en savoir plus, consultez notre dépôt GitHub. No comment provided by engineer. @@ -7620,6 +7681,10 @@ chat item action Envoi des 100 derniers messages aux nouveaux membres. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -8223,6 +8288,11 @@ report reason Carré, circulaire, ou toute autre forme intermédiaire. No comment provided by engineer. + + Star on GitHub + Star sur GitHub + No comment provided by engineer. + Start chat Démarrer le chat @@ -8326,6 +8396,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8334,6 +8408,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9082,6 +9192,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Les 100 derniers messages sont envoyés aux nouveaux membres. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Mise à jour @@ -10099,21 +10213,11 @@ Relays can access channel messages. Vos paramètres No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribuer](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Contact par mail](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Star sur GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italique_ diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index db78c556e3..7e52f853f2 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -984,6 +984,10 @@ swipe action Az üzenetek végleges törlése csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. (24 óra) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. A reakciók hozzáadása az üzenetekhez csak abban az esetben van engedélyezve, ha a partnere is engedélyezi. @@ -999,6 +1003,11 @@ swipe action A közvetlen üzenetek küldése a tagok között engedélyezve van. No comment provided by engineer. + + Allow sending direct messages to subscribers. + A közvetlen üzenetek küldése a feliratkozók között engedélyezve van. + No comment provided by engineer. + Allow sending disappearing messages. Az eltűnő üzenetek küldése engedélyezve van. @@ -1009,6 +1018,10 @@ swipe action Megosztás engedélyezése No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Az elküldött üzenetek végleges törlése engedélyezve van. (24 óra) @@ -1861,7 +1874,8 @@ alert subtitle Chat with admins Csevegés az adminisztrátorokkal - chat toolbar + chat feature +chat toolbar Chat with member @@ -1878,11 +1892,23 @@ alert subtitle Csevegések No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Csevegés a tagokkal No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Üzenetek ellenőrzése 20 percenként. @@ -2382,6 +2408,11 @@ Ez a saját egyszer használható meghívója! Folytatás No comment provided by engineer. + + Contribute + Közreműködés + No comment provided by engineer. + Conversation deleted! Beszélgetés törölve! @@ -3081,6 +3112,11 @@ alert button A tagok közötti közvetlen üzenetek le vannak tiltva. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + A feliratkozók közötti közvetlen üzenetek le vannak tiltva. + No comment provided by engineer. + Disable alert button @@ -3190,6 +3226,11 @@ alert button Az előzmények ne legyenek elküldve az új tagok számára. No comment provided by engineer. + + Do not send history to new subscribers. + Az előzmények ne legyenek elküldve az új feliratkozók számára. + No comment provided by engineer. + Do not use credentials with proxy. Ne használja a hitelesítési adatokat proxyval. @@ -3355,6 +3396,10 @@ chat item action Kamera-hozzáférés engedélyezése No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Eltűnő üzenetek engedélyezése alapértelmezetten. @@ -4605,6 +4650,11 @@ Hiba: %2$@ Az előzmények nem lesznek elküldve az új tagok számára. No comment provided by engineer. + + History is not sent to new subscribers. + Az előzmények nem lesznek elküldve az új feliratkozók számára. + No comment provided by engineer. + How SimpleX works Hogyan működik a SimpleX @@ -4852,9 +4902,9 @@ További fejlesztések hamarosan! Kezdeti szerepkör No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + A SimpleX Chat terminálhoz telepítése No comment provided by engineer. @@ -5399,6 +5449,10 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! A tagok reakciókat adhatnak hozzá az üzenetekhez. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) A tagok véglegesen törölhetik az elküldött üzeneteiket. (24 óra) @@ -5564,6 +5618,14 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! %@ összes üzenete meg fog jelenni! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Az ebben a csevegésben lévő üzenetek soha nem lesznek törölve. @@ -6261,7 +6323,8 @@ VPN engedélyezése szükséges. Open Megnyitás - alert action + alert action +alert button Open Settings @@ -6298,6 +6361,10 @@ VPN engedélyezése szükséges. Feltételek megnyitása No comment provided by engineer. + + Open external link? + alert title + Open full link Teljes hivatkozás megnyitása @@ -6788,6 +6855,10 @@ Hiba: %@ A hívások kezdeményezése le van tiltva. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Az elküldött üzenetek végleges törlése le van tiltva. @@ -6818,6 +6889,11 @@ Hiba: %@ A közvetlen üzenetek küldése a tagok között le van tiltva. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + A közvetlen üzenetek küldése a feliratkozók között le van tiltva. + No comment provided by engineer. + Prohibit sending disappearing messages. Az eltűnő üzenetek küldése le van tiltva. @@ -6929,24 +7005,14 @@ Engedélyezze a *Hálózat és kiszolgálók* menüben. Tudjon meg többet No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + További információ a Használati útmutatóban. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - További információ a [GitHub-tárolónkban](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + További információ a GitHub-tárolónkban. No comment provided by engineer. @@ -7836,6 +7902,11 @@ chat item action Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Legfeljebb az utolsó 100 üzenet elküldése az új feliratkozók számára. + No comment provided by engineer. + Send your private feedback to groups. Küldjön privát visszajelzést a csoportoknak. @@ -8455,6 +8526,11 @@ report reason Négyzet, kör vagy bármi a kettő között. No comment provided by engineer. + + Star on GitHub + Csillagozás a GitHubon + No comment provided by engineer. + Start chat Csevegés elindítása @@ -8560,6 +8636,11 @@ report reason Feliratkozó No comment provided by engineer. + + Subscriber reports + Feliratkozók jelentései + chat feature + Subscriber will be removed from channel - this cannot be undone! A feliratkozó el lesz távolítva a csatornából – ez a művelet nem vonható vissza! @@ -8570,6 +8651,50 @@ report reason Feliratkozók No comment provided by engineer. + + Subscribers can add message reactions. + A feliratkozók reakciókat adhatnak hozzá az üzenetekhez. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + A feliratkozók véglegesen törölhetik az elküldött üzeneteiket. (24 óra) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + A feliratkozók jelenthetik az üzeneteket a moderátorok felé. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + A feliratkozók küldhetnek SimpleX-hivatkozásokat. + No comment provided by engineer. + + + Subscribers can send direct messages. + A feliratkozók küldhetnek egymásnak közvetlen üzeneteket. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + A feliratkozók küldhetnek eltűnő üzeneteket. + No comment provided by engineer. + + + Subscribers can send files and media. + A feliratkozók küldhetnek fájlokat és médiatartalmakat. + No comment provided by engineer. + + + Subscribers can send voice messages. + A feliratkozók küldhetnek hangüzeneteket. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9345,6 +9470,11 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Legfeljebb az utolsó 100 üzenet lesz elküldve az új feliratkozók számára. + No comment provided by engineer. + Update Frissítés @@ -10397,21 +10527,11 @@ Az átjátszók hozzáférhetnek a csatornaüzenetekhez. Beállítások No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Közreműködés](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Küldjön nekünk e-mailt](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_dőlt_ diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index aad0075e35..7cf7b418a8 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -984,6 +984,10 @@ swipe action Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Consenti reazioni ai messaggi solo se il tuo contatto le consente. @@ -999,6 +1003,11 @@ swipe action Permetti l'invio di messaggi diretti ai membri. No comment provided by engineer. + + Allow sending direct messages to subscribers. + Permetti l'invio di messaggi diretti agli iscritti. + No comment provided by engineer. + Allow sending disappearing messages. Permetti l'invio di messaggi a tempo. @@ -1009,6 +1018,10 @@ swipe action Consenti la condivisione No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Permetti di eliminare irreversibilmente i messaggi inviati. (24 ore) @@ -1861,7 +1874,8 @@ alert subtitle Chat with admins Chat con amministratori - chat toolbar + chat feature +chat toolbar Chat with member @@ -1878,11 +1892,23 @@ alert subtitle Chat No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Chat con membri No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Controlla i messaggi ogni 20 min. @@ -2382,6 +2408,11 @@ Questo è il tuo link una tantum! Continua No comment provided by engineer. + + Contribute + Contribuisci + No comment provided by engineer. + Conversation deleted! Conversazione eliminata! @@ -3081,6 +3112,11 @@ alert button I messaggi diretti tra i membri sono vietati in questo gruppo. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + I messaggi diretti tra gli iscritti sono vietati. + No comment provided by engineer. + Disable alert button @@ -3190,6 +3226,11 @@ alert button Non inviare la cronologia ai nuovi membri. No comment provided by engineer. + + Do not send history to new subscribers. + Non inviare la cronologia ai nuovi iscritti. + No comment provided by engineer. + Do not use credentials with proxy. Non usare credenziali con proxy. @@ -3355,6 +3396,10 @@ chat item action Attiva l'accesso alla fotocamera No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Attiva i messaggi a tempo in modo predefinito. @@ -4605,6 +4650,11 @@ Errore: %2$@ La cronologia non viene inviata ai nuovi membri. No comment provided by engineer. + + History is not sent to new subscribers. + La cronologia non viene inviata ai nuovi iscritti. + No comment provided by engineer. + How SimpleX works Come funziona SimpleX @@ -4852,9 +4902,9 @@ Altri miglioramenti sono in arrivo! Ruolo iniziale No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Installa [Simplex Chat per terminale](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Installa Simplex Chat per terminale No comment provided by engineer. @@ -5399,6 +5449,10 @@ Questo è il tuo link per il gruppo %@! I membri del gruppo possono aggiungere reazioni ai messaggi. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) I membri del gruppo possono eliminare irreversibilmente i messaggi inviati. (24 ore) @@ -5564,6 +5618,14 @@ Questo è il tuo link per il gruppo %@! I messaggi da %@ verranno mostrati! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. I messaggi in questa chat non verranno mai eliminati. @@ -6261,7 +6323,8 @@ Richiede l'attivazione della VPN. Open Apri - alert action + alert action +alert button Open Settings @@ -6298,6 +6361,10 @@ Richiede l'attivazione della VPN. Apri le condizioni No comment provided by engineer. + + Open external link? + alert title + Open full link Apri link completo @@ -6788,6 +6855,10 @@ Errore: %@ Proibisci le chiamate audio/video. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Proibisci l'eliminazione irreversibile dei messaggi. @@ -6818,6 +6889,11 @@ Errore: %@ Proibisci l'invio di messaggi diretti ai membri. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + Proibisci l'invio di messaggi diretti agli iscritti. + No comment provided by engineer. + Prohibit sending disappearing messages. Proibisci l'invio di messaggi a tempo. @@ -6929,24 +7005,14 @@ Attivalo nelle impostazioni *Rete e server*. Leggi tutto No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Leggi di più nella Guida utente. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Maggiori informazioni nel nostro [repository GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Maggiori informazioni nel nostro repository GitHub. No comment provided by engineer. @@ -7836,6 +7902,11 @@ chat item action Invia fino a 100 ultimi messaggi ai nuovi membri. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + Invia fino a 100 ultimi messaggi ai nuovi iscritti. + No comment provided by engineer. + Send your private feedback to groups. Invia i tuoi commenti privati ai gruppi. @@ -8455,6 +8526,11 @@ report reason Quadrata, circolare o qualsiasi forma tra le due. No comment provided by engineer. + + Star on GitHub + Dai una stella su GitHub + No comment provided by engineer. + Start chat Avvia chat @@ -8560,6 +8636,11 @@ report reason Iscritto No comment provided by engineer. + + Subscriber reports + Segnalazioni degli iscritti + chat feature + Subscriber will be removed from channel - this cannot be undone! L'iscritto verrà rimosso dal canale, non è reversibile! @@ -8570,6 +8651,50 @@ report reason Iscritti No comment provided by engineer. + + Subscribers can add message reactions. + Gli iscritti al canale possono aggiungere reazioni ai messaggi. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + Gli iscritti al canale possono eliminare irreversibilmente i messaggi inviati. (24 ore) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + Gli iscritti possono segnalare messaggi ai moderatori. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + Gli iscritti al canale possono inviare link di Simplex. + No comment provided by engineer. + + + Subscribers can send direct messages. + Gli iscritti al canale possono inviare messaggi diretti. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + Gli iscritti al canale possono inviare messaggi a tempo. + No comment provided by engineer. + + + Subscribers can send files and media. + Gli iscritti al canale possono inviare file e contenuti multimediali. + No comment provided by engineer. + + + Subscribers can send voice messages. + Gli iscritti al canale possono inviare messaggi vocali. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9345,6 +9470,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Vengono inviati ai nuovi membri fino a 100 ultimi messaggi. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + Vengono inviati ai nuovi iscritti fino a 100 ultimi messaggi. + No comment provided by engineer. + Update Aggiorna @@ -10397,21 +10527,11 @@ I relay hanno accesso ai messaggi del canale. Le tue impostazioni No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Contribuisci](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Inviaci un'email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Dai una stella su GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_corsivo_ diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index ed338a0aac..3498c8757f 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -957,6 +957,10 @@ swipe action 送信相手も永久メッセージ削除を許可する時のみに許可する。(24時間) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. 連絡先が許可している場合にのみ、メッセージへのリアクションを許可します。 @@ -972,6 +976,10 @@ swipe action メンバーへのダイレクトメッセージを許可する。 No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. 消えるメッセージの送信を許可する。 @@ -982,6 +990,10 @@ swipe action 共有を許可 No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) 送信済みメッセージの永久削除を許可する。(24時間) @@ -1754,7 +1766,8 @@ alert subtitle Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1769,10 +1782,22 @@ alert subtitle チャット No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. 20分おきにメッセージを確認する。 @@ -2225,6 +2250,11 @@ This is your own one-time link! 続ける No comment provided by engineer. + + Contribute + 貢献する + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2871,6 +2901,10 @@ alert button このグループではメンバー間のダイレクトメッセージが使用禁止です。 No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -2972,6 +3006,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -3119,6 +3157,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -4252,6 +4294,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works SimpleX の仕組み @@ -4480,9 +4526,9 @@ More improvements are coming soon! 初期の役割 No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + インストール ターミナル用SimpleX Chat No comment provided by engineer. @@ -4977,6 +5023,10 @@ This is your link for group %@! グループメンバーはメッセージへのリアクションを追加できます。 No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) グループのメンバーがメッセージを完全削除することができます。(24時間) @@ -5122,6 +5172,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5753,7 +5811,8 @@ VPN を有効にする必要があります。 Open 開く - alert action + alert action +alert button Open Settings @@ -5786,6 +5845,10 @@ VPN を有効にする必要があります。 Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6216,6 +6279,10 @@ Error: %@ 音声/ビデオ通話を禁止する 。 No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. メッセージの完全削除を使用禁止にする。 @@ -6244,6 +6311,10 @@ Error: %@ メンバー間のダイレクトメッセージを使用禁止にする。 No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. 消えるメッセージを使用禁止にする。 @@ -6345,23 +6416,14 @@ Enable in *Network & servers* settings. 続きを読む No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + 詳しくはユーザーガイドをご覧ください。 No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)をご覧ください。 - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - 詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/readme.html#connect-to-friends)をご覧ください。 - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - 詳しくは[GitHubリポジトリ](https://github.com/simplex-chat/simplex-chat#readme)をご覧ください。 + + Read more in our GitHub repository. + 詳しくはGitHubリポジトリをご覧ください。 No comment provided by engineer. @@ -7156,6 +7218,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7703,6 +7769,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + GitHub でスターを付ける + No comment provided by engineer. + Start chat チャットを開始する @@ -7799,6 +7870,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7807,6 +7882,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8501,6 +8612,10 @@ To connect, please ask your contact to create another connection link and check Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update 更新 @@ -9442,21 +9557,11 @@ Relays can access channel messages. あなたの設定 No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [貢献する](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [メールを送信](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [GitHub でスターを付ける](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_斜体_ diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 1e97b6721c..6c5316a2b1 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -972,6 +972,10 @@ swipe action Sta het definitief verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Sta bericht reacties alleen toe als uw contact dit toestaat. @@ -987,6 +991,10 @@ swipe action Sta het verzenden van directe berichten naar leden toe. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Toestaan dat verdwijnende berichten worden verzonden. @@ -997,6 +1005,10 @@ swipe action Delen toestaan No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Sta toe om verzonden berichten definitief te verwijderen. (24 uur) @@ -1824,7 +1836,8 @@ alert subtitle Chat with admins Chat met beheerders - chat toolbar + chat feature +chat toolbar Chat with member @@ -1840,11 +1853,23 @@ alert subtitle Chats No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Chats met leden No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Controleer uw berichten elke 20 minuten. @@ -2338,6 +2363,11 @@ Dit is uw eigen eenmalige link! Doorgaan No comment provided by engineer. + + Contribute + Bijdragen + No comment provided by engineer. + Conversation deleted! Gesprek verwijderd! @@ -3025,6 +3055,10 @@ alert button Directe berichten tussen leden zijn niet toegestaan. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3134,6 +3168,10 @@ alert button Stuur geen geschiedenis naar nieuwe leden. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Gebruik geen inloggegevens met proxy. @@ -3296,6 +3334,10 @@ chat item action Schakel cameratoegang in No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -4527,6 +4569,10 @@ Fout: %2$@ Geschiedenis wordt niet naar nieuwe leden gestuurd. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Hoe SimpleX werkt @@ -4772,9 +4818,9 @@ Binnenkort meer verbeteringen! Initiële rol No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Installeer SimpleX Chat voor terminal No comment provided by engineer. @@ -5306,6 +5352,10 @@ Dit is jouw link voor groep %@! Groepsleden kunnen bericht reacties toevoegen. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Groepsleden kunnen verzonden berichten onherroepelijk verwijderen. (24 uur) @@ -5468,6 +5518,14 @@ Dit is jouw link voor groep %@! Berichten van %@ worden getoond! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Berichten in deze chat zullen nooit worden verwijderd. @@ -6157,7 +6215,8 @@ Vereist het inschakelen van VPN. Open Open - alert action + alert action +alert button Open Settings @@ -6192,6 +6251,10 @@ Vereist het inschakelen van VPN. Open voorwaarden No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6668,6 +6731,10 @@ Fout: %@ Audio/video gesprekken verbieden. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Verbied het definitief verwijderen van berichten. @@ -6698,6 +6765,10 @@ Fout: %@ Verbied het sturen van directe berichten naar leden. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Verbied het verzenden van verdwijnende berichten. @@ -6808,24 +6879,14 @@ Schakel dit in in *Netwerk en servers*-instellingen. Lees meer No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Lees meer in de Gebruikershandleiding. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Lees meer in onze [GitHub-repository](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Lees meer in onze GitHub-repository. No comment provided by engineer. @@ -7691,6 +7752,10 @@ chat item action Stuur tot 100 laatste berichten naar nieuwe leden. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -8300,6 +8365,11 @@ report reason Vierkant, cirkel of iets daartussenin. No comment provided by engineer. + + Star on GitHub + Star on GitHub + No comment provided by engineer. + Start chat Begin gesprek @@ -8404,6 +8474,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8412,6 +8486,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9167,6 +9277,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Er worden maximaal 100 laatste berichten naar nieuwe leden verzonden. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Update @@ -10190,21 +10304,11 @@ Relays can access channel messages. Uw instellingen No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Bijdragen](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Stuur ons een e-mail](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_cursief_ diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 104505c05c..65a5c638fe 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -976,6 +976,10 @@ swipe action Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Zezwalaj na reakcje wiadomości tylko wtedy, gdy zezwala na to Twój kontakt. @@ -991,6 +995,10 @@ swipe action Zezwalaj na wysyłanie bezpośrednich wiadomości do członków. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Zezwól na wysyłanie znikających wiadomości. @@ -1001,6 +1009,10 @@ swipe action Zezwól na udostępnianie No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Zezwól na nieodwracalne usunięcie wysłanych wiadomości. (24 godziny) @@ -1836,7 +1848,8 @@ alert subtitle Chat with admins Czatuj z administratorami - chat toolbar + chat feature +chat toolbar Chat with member @@ -1853,11 +1866,23 @@ alert subtitle Czaty No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Czaty z członkami No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Sprawdzaj wiadomości co 20 min. @@ -2354,6 +2379,11 @@ To jest twój jednorazowy link! Kontynuuj No comment provided by engineer. + + Contribute + Przyczyń się + No comment provided by engineer. + Conversation deleted! Rozmowa usunięta! @@ -3046,6 +3076,10 @@ alert button Bezpośrednie wiadomości między członkami są zabronione w tej grupie. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3155,6 +3189,10 @@ alert button Nie wysyłaj historii do nowych członków. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Nie używaj danych logowania do proxy. @@ -3318,6 +3356,10 @@ chat item action Włącz dostęp do kamery No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Włącz domyślnie znikające wiadomości. @@ -4562,6 +4604,10 @@ Błąd: %2$@ Historia nie jest wysyłana do nowych członków. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Jak działa SimpleX @@ -4809,9 +4855,9 @@ Wkrótce pojawią się kolejne ulepszenia! Rola początkowa No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Zainstaluj SimpleX Chat na terminal No comment provided by engineer. @@ -5351,6 +5397,10 @@ To jest twój link do grupy %@! Członkowie grupy mogą dodawać reakcje wiadomości. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny) @@ -5515,6 +5565,14 @@ To jest twój link do grupy %@! Wiadomości od %@ zostaną pokazane! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Wiadomości na tym czacie nigdy nie zostaną usunięte. @@ -6208,7 +6266,8 @@ Wymaga włączenia VPN. Open Otwórz - alert action + alert action +alert button Open Settings @@ -6244,6 +6303,10 @@ Wymaga włączenia VPN. Otwórz warunki No comment provided by engineer. + + Open external link? + alert title + Open full link Otwórz pełny link @@ -6728,6 +6791,10 @@ Błąd: %@ Zabroń połączeń audio/wideo. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Zabroń nieodwracalnego usuwania wiadomości. @@ -6758,6 +6825,10 @@ Błąd: %@ Zabroń wysyłania bezpośrednich wiadomości do członków. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Zabroń wysyłania znikających wiadomości. @@ -6869,24 +6940,14 @@ Włącz w ustawianiach *Sieć i serwery* . Przeczytaj więcej No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Przeczytaj więcej w Poradniku Użytkownika. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Przeczytaj więcej na naszym [repozytorium GitHub](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Przeczytaj więcej na naszym repozytorium GitHub. No comment provided by engineer. @@ -7766,6 +7827,10 @@ chat item action Wysyłaj do 100 ostatnich wiadomości do nowych członków. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Wyślij swoją prywatną opinię do grup. @@ -8382,6 +8447,11 @@ report reason Kwadrat, okrąg lub cokolwiek pomiędzy. No comment provided by engineer. + + Star on GitHub + Daj gwiazdkę na GitHub + No comment provided by engineer. + Start chat Rozpocznij czat @@ -8486,6 +8556,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8494,6 +8568,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9261,6 +9371,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Do nowych członków wysyłanych jest do 100 ostatnich wiadomości. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Aktualizuj @@ -10300,21 +10414,11 @@ Relays can access channel messages. Twoje ustawienia No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Przyczyń się](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Wyślij do nas email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Daj gwiazdkę na GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_kursywa_ diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 9f64637aa4..a9f194b609 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -975,6 +975,10 @@ swipe action Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Разрешить реакции на сообщения, только если ваш контакт разрешает их. @@ -990,6 +994,10 @@ swipe action Разрешить личные сообщения членам группы. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Разрешить посылать исчезающие сообщения. @@ -1000,6 +1008,10 @@ swipe action Разрешить поделиться No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Разрешить необратимо удалять отправленные сообщения. (24 часа) @@ -1834,7 +1846,8 @@ alert subtitle Chat with admins Чат с админами - chat toolbar + chat feature +chat toolbar Chat with member @@ -1851,11 +1864,23 @@ alert subtitle Чаты No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Чаты с членами группы No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Проверять сообщения каждые 20 минут. @@ -2351,6 +2376,11 @@ This is your own one-time link! Продолжить No comment provided by engineer. + + Contribute + Внести свой вклад + No comment provided by engineer. + Conversation deleted! Разговор удален! @@ -3041,6 +3071,10 @@ alert button Прямые сообщения между членами запрещены. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3150,6 +3184,10 @@ alert button Не отправлять историю новым членам. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Не использовать учетные данные с прокси. @@ -3313,6 +3351,10 @@ chat item action Включить доступ к камере No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Включите исчезающие сообщения по умолчанию. @@ -4556,6 +4598,10 @@ Error: %2$@ История не отправляется новым членам. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Как SimpleX работает @@ -4800,9 +4846,9 @@ More improvements are coming soon! Роль при вступлении No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - [SimpleX Chat для терминала](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + SimpleX Chat для терминала No comment provided by engineer. @@ -5339,6 +5385,10 @@ This is your link for group %@! Члены могут добавлять реакции на сообщения. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Члены могут необратимо удалять отправленные сообщения. (24 часа) @@ -5503,6 +5553,14 @@ This is your link for group %@! Сообщения от %@ будут показаны! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Сообщения в этом чате никогда не будут удалены. @@ -6196,7 +6254,8 @@ Requires compatible VPN. Open Открыть - alert action + alert action +alert button Open Settings @@ -6232,6 +6291,10 @@ Requires compatible VPN. Открыть условия No comment provided by engineer. + + Open external link? + alert title + Open full link Открыть полную ссылку @@ -6716,6 +6779,10 @@ Error: %@ Запретить аудио/видео звонки. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Запретить необратимое удаление сообщений. @@ -6746,6 +6813,10 @@ Error: %@ Запретить посылать прямые сообщения членам группы. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Запретить посылать исчезающие сообщения. @@ -6857,24 +6928,14 @@ Enable in *Network & servers* settings. Узнать больше No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Дополнительная информация в [Руководстве пользователя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Дополнительная информация в Руководстве пользователя. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Узнайте больше из нашего [GitHub репозитория](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Узнайте больше из нашего GitHub репозитория. No comment provided by engineer. @@ -7748,6 +7809,10 @@ chat item action Отправить до 100 последних сообщений новым членам. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Отправляйте Ваши конфиденциальные предложения группе. @@ -8364,6 +8429,11 @@ report reason Квадрат, круг и все, что между ними. No comment provided by engineer. + + Star on GitHub + Поставить звездочку в GitHub + No comment provided by engineer. + Start chat Запустить чат @@ -8468,6 +8538,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8476,6 +8550,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9243,6 +9353,10 @@ To connect, please ask your contact to create another connection link and check До 100 последних сообщений отправляются новым членам. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Обновить @@ -10281,21 +10395,11 @@ Relays can access channel messages. Настройки No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Внести свой вклад](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Отправить email](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Поставить звездочку в GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_курсив_ diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 985804e8de..8c45cb3e95 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -891,6 +891,10 @@ swipe action อนุญาตให้ลบข้อความแบบถาวรเฉพาะในกรณีที่ผู้ติดต่อของคุณอนุญาตให้คุณเท่านั้น No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. อนุญาตการแสดงปฏิกิริยาต่อข้อความเฉพาะเมื่อผู้ติดต่อของคุณอนุญาตเท่านั้น @@ -906,6 +910,10 @@ swipe action อนุญาตการส่งข้อความโดยตรงไปยังสมาชิก No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. อนุญาตให้ส่งข้อความที่จะหายไปหลังปิดแชท (disappearing message) @@ -915,6 +923,10 @@ swipe action Allow sharing No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) อนุญาตให้ลบข้อความที่ส่งไปแล้วอย่างถาวร @@ -1668,7 +1680,8 @@ alert subtitle Chat with admins - chat toolbar + chat feature +chat toolbar Chat with member @@ -1683,10 +1696,22 @@ alert subtitle แชท No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. No comment provided by engineer. @@ -2121,6 +2146,11 @@ This is your own one-time link! ดำเนินการต่อ No comment provided by engineer. + + Contribute + มีส่วนร่วม + No comment provided by engineer. + Conversation deleted! No comment provided by engineer. @@ -2759,6 +2789,10 @@ alert button ข้อความโดยตรงระหว่างสมาชิกเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -2859,6 +2893,10 @@ alert button Do not send history to new members. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. No comment provided by engineer. @@ -3006,6 +3044,10 @@ chat item action Enable camera access No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. No comment provided by engineer. @@ -4136,6 +4178,10 @@ Error: %2$@ History is not sent to new members. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works วิธีการ SimpleX ทํางานอย่างไร @@ -4363,9 +4409,9 @@ More improvements are coming soon! บทบาทเริ่มต้น No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + ติดตั้ง SimpleX Chat สำหรับเทอร์มินัล No comment provided by engineer. @@ -4859,6 +4905,10 @@ This is your link for group %@! สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้ No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) สมาชิกกลุ่มสามารถลบข้อความที่ส่งแล้วอย่างถาวร @@ -5005,6 +5055,14 @@ This is your link for group %@! Messages from %@ will be shown! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. alert message @@ -5627,7 +5685,8 @@ Requires compatible VPN. Open - alert action + alert action +alert button Open Settings @@ -5660,6 +5719,10 @@ Requires compatible VPN. Open conditions No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6089,6 +6152,10 @@ Error: %@ ห้ามการโทรด้วยเสียง/วิดีโอ No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. ห้ามการลบข้อความที่ย้อนกลับไม่ได้ @@ -6117,6 +6184,10 @@ Error: %@ ห้ามส่งข้อความโดยตรงถึงสมาชิก No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. ห้ามส่งข้อความที่จะหายไปหลังจากเวลาที่กำหนดหลังการอ่าน (disappearing messages) @@ -6218,23 +6289,14 @@ Enable in *Network & servers* settings. อ่านเพิ่มเติม No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + อ่านเพิ่มเติมในคู่มือผู้ใช้ No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/readme.html#connect-to-friends) - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - อ่านเพิ่มเติมใน[พื้นที่เก็บข้อมูล GitHub](https://github.com/simplex-chat/simplex-chat#readme) + + Read more in our GitHub repository. + อ่านเพิ่มเติมในพื้นที่เก็บข้อมูล GitHub No comment provided by engineer. @@ -7029,6 +7091,10 @@ chat item action Send up to 100 last messages to new members. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. No comment provided by engineer. @@ -7577,6 +7643,11 @@ report reason Square, circle, or anything in between. No comment provided by engineer. + + Star on GitHub + ติดดาวบน GitHub + No comment provided by engineer. + Start chat เริ่มแชท @@ -7673,6 +7744,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -7681,6 +7756,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -8375,6 +8486,10 @@ To connect, please ask your contact to create another connection link and check Up to 100 last messages are sent to new members. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update อัปเดต @@ -9311,21 +9426,11 @@ Relays can access channel messages. การตั้งค่าของคุณ No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [มีส่วนร่วม](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [ส่งอีเมลถึงเรา](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [ติดดาวบน GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_ตัวเอียง_ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 9bd287a3dd..ea9291a43a 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -975,6 +975,10 @@ swipe action Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. (24 saat içinde) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Yalnızca kişin mesaj tepkilerine izin veriyorsa sen de ver. @@ -990,6 +994,10 @@ swipe action Üyelere doğrudan mesaj göndermeye izin ver. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Kendiliğinden yok olan mesajlar göndermeye izin ver. @@ -1000,6 +1008,10 @@ swipe action Paylaşıma izin ver No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde) @@ -1834,7 +1846,8 @@ alert subtitle Chat with admins Yöneticilerle sohbet et - chat toolbar + chat feature +chat toolbar Chat with member @@ -1851,11 +1864,23 @@ alert subtitle Sohbetler No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Üyelerle sohbetler No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Her 20 dakikada mesajları kontrol et. @@ -2351,6 +2376,11 @@ Bu senin kendi tek kullanımlık bağlantın! Devam et No comment provided by engineer. + + Contribute + Katkıda bulun + No comment provided by engineer. + Conversation deleted! Sohbet silindi! @@ -3041,6 +3071,10 @@ alert button Bu grupta üyeler arasında direkt mesajlaşma yasaktır. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3150,6 +3184,10 @@ alert button Yeni üyelere geçmişi gönderme. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Kimlik bilgilerini proxy ile kullanmayın. @@ -3313,6 +3351,10 @@ chat item action Kamera erişimini etkinleştir No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Varsayılan olarak kaybolan mesajları etkinleştirin. @@ -4551,6 +4593,10 @@ Hata: %2$@ Yeni üyelere geçmiş gönderilmedi. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works SimpleX nasıl çalışır @@ -4796,9 +4842,9 @@ Daha fazla iyileştirme yakında geliyor! Başlangıç rolü No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - [Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Terminal için SimpleX Chat'i indir No comment provided by engineer. @@ -5335,6 +5381,10 @@ Bu senin grup için bağlantın %@! Grup üyeleri mesaj tepkileri ekleyebilir. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) @@ -5499,6 +5549,14 @@ Bu senin grup için bağlantın %@! %@ den gelen mesajlar gösterilecektir! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Bu sohbetteki mesajlar asla silinmeyecek. @@ -6192,7 +6250,8 @@ VPN'nin etkinleştirilmesi gerekir. Open - alert action + alert action +alert button Open Settings @@ -6228,6 +6287,10 @@ VPN'nin etkinleştirilmesi gerekir. Açık koşullar No comment provided by engineer. + + Open external link? + alert title + Open full link Tam linki aç @@ -6712,6 +6775,10 @@ Hata: %@ Sesli/görüntülü aramaları yasakla. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Geri dönüşsüz mesaj silme işlemini yasakla. @@ -6742,6 +6809,10 @@ Hata: %@ Üyelere doğrudan mesaj göndermeyi yasakla. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Kaybolan mesajların gönderimini yasakla. @@ -6853,24 +6924,14 @@ Enable in *Network & servers* settings. Dahasını oku No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Kullanıcı Rehberinde daha fazlasını okuyun. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - [Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - [GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + GitHub deposunda daha fazlasını okuyun. No comment provided by engineer. @@ -7744,6 +7805,10 @@ chat item action Yeni üyelere 100 adete kadar son mesajları gönderin. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Özel geri bildiriminizi gruplara gönderin. @@ -8360,6 +8425,11 @@ report reason Kare,daire, veya aralarında herhangi bir şey. No comment provided by engineer. + + Star on GitHub + Bize GitHub'da yıldız verin + No comment provided by engineer. + Start chat Sohbeti başlat @@ -8464,6 +8534,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8472,6 +8546,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9238,6 +9348,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Yeni üyelere 100e kadar en son mesajlar gönderildi. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Güncelle @@ -10274,21 +10388,11 @@ Relays can access channel messages. Ayarlarınız No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Katkıda bulun](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Bize e-posta gönder](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Bize GitHub'da yıldız verin](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_italik_ diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index edff3a8a16..1b71eecb96 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -974,6 +974,10 @@ swipe action Дозволяйте безповоротне видалення повідомлень, тільки якщо контакт дозволяє вам це зробити. (24 години) No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. Дозволяйте реакції на повідомлення, тільки якщо ваш контакт дозволяє їх. @@ -989,6 +993,10 @@ swipe action Дозволяє надсилати прямі повідомлення користувачам. No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. Дозволити надсилання зникаючих повідомлень. @@ -999,6 +1007,10 @@ swipe action Дозволити спільний доступ No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) Дозволяє безповоротно видаляти надіслані повідомлення. (24 години) @@ -1830,7 +1842,8 @@ alert subtitle Chat with admins Чат з адміністраторами - chat toolbar + chat feature +chat toolbar Chat with member @@ -1847,11 +1860,23 @@ alert subtitle Чати No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members Чати з учасниками No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. Перевіряйте повідомлення кожні 20 хв. @@ -2346,6 +2371,11 @@ This is your own one-time link! Продовжуйте No comment provided by engineer. + + Contribute + Внесок + No comment provided by engineer. + Conversation deleted! Розмова видалена! @@ -3035,6 +3065,10 @@ alert button У цій групі заборонені прямі повідомлення між учасниками. No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3144,6 +3178,10 @@ alert button Не надсилайте історію новим користувачам. No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. Не використовуйте облікові дані з проксі. @@ -3307,6 +3345,10 @@ chat item action Увімкніть доступ до камери No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. Увімкнути зникаючі повідомлення за замовчуванням. @@ -4543,6 +4585,10 @@ Error: %2$@ Історія не надсилається новим учасникам. No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works Як працює SimpleX @@ -4788,9 +4834,9 @@ More improvements are coming soon! Початкова роль No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + Встановіть SimpleX Chat для терміналу No comment provided by engineer. @@ -5325,6 +5371,10 @@ This is your link for group %@! Учасники групи можуть додавати реакції на повідомлення. No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) Учасники групи можуть безповоротно видаляти надіслані повідомлення. (24 години) @@ -5489,6 +5539,14 @@ This is your link for group %@! Повідомлення від %@ будуть показані! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. Повідомлення в цьому чаті ніколи не будуть видалені. @@ -6180,7 +6238,8 @@ Requires compatible VPN. Open Відкрито - alert action + alert action +alert button Open Settings @@ -6215,6 +6274,10 @@ Requires compatible VPN. Відкриті умови No comment provided by engineer. + + Open external link? + alert title + Open full link alert action @@ -6697,6 +6760,10 @@ Error: %@ Заборонити аудіо/відеодзвінки. No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. Заборонити незворотне видалення повідомлень. @@ -6727,6 +6794,10 @@ Error: %@ Заборонити надсилати прямі повідомлення учасникам. No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. Заборонити надсилання зникаючих повідомлень. @@ -6838,24 +6909,14 @@ Enable in *Network & servers* settings. Читати далі No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + + Read more in User Guide. + Читайте більше в User Guide. No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Читайте більше в нашому [GitHub репозиторії](https://github.com/simplex-chat/simplex-chat#readme). + + Read more in our GitHub repository. + Читайте більше в нашому GitHub репозиторії. No comment provided by engineer. @@ -7728,6 +7789,10 @@ chat item action Надішліть до 100 останніх повідомлень новим користувачам. No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. Надсилайте свої приватні відгуки до груп. @@ -8344,6 +8409,11 @@ report reason Квадрат, коло або щось середнє між ними. No comment provided by engineer. + + Star on GitHub + Зірка на GitHub + No comment provided by engineer. + Start chat Почати чат @@ -8448,6 +8518,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8456,6 +8530,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9219,6 +9329,10 @@ To connect, please ask your contact to create another connection link and check Новим користувачам надсилається до 100 останніх повідомлень. No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update Оновлення @@ -10255,21 +10369,11 @@ Relays can access channel messages. Ваші налаштування No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [Внесок](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [Напишіть нам електронною поштою](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [Зірка на GitHub](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_курсив_ diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 3a95e6aaa0..2d3342b15b 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -976,6 +976,10 @@ swipe action 仅有您的联系人许可后才允许不可撤回消息移除 No comment provided by engineer. + + Allow members to chat with admins. + No comment provided by engineer. + Allow message reactions only if your contact allows them. 只有您的联系人允许时才允许消息回应。 @@ -991,6 +995,10 @@ swipe action 允许向成员发送私信。 No comment provided by engineer. + + Allow sending direct messages to subscribers. + No comment provided by engineer. + Allow sending disappearing messages. 允许发送限时消息。 @@ -1001,6 +1009,10 @@ swipe action 允许共享 No comment provided by engineer. + + Allow subscribers to chat with admins. + No comment provided by engineer. + Allow to irreversibly delete sent messages. (24 hours) 允许不可撤回地删除已发送消息 @@ -1836,7 +1848,8 @@ alert subtitle Chat with admins 和管理员聊天 - chat toolbar + chat feature +chat toolbar Chat with member @@ -1853,11 +1866,23 @@ alert subtitle 聊天 No comment provided by engineer. + + Chats with admins are prohibited. + No comment provided by engineer. + + + Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. + alert message + Chats with members 和成员聊天 No comment provided by engineer. + + Chats with members are disabled + No comment provided by engineer. + Check messages every 20 min. 每 20 分钟检查消息。 @@ -2352,6 +2377,11 @@ This is your own one-time link! 继续 No comment provided by engineer. + + Contribute + 贡献 + No comment provided by engineer. + Conversation deleted! 对话已删除! @@ -3043,6 +3073,10 @@ alert button 此群禁止成员间私信。 No comment provided by engineer. + + Direct messages between subscribers are prohibited. + No comment provided by engineer. + Disable alert button @@ -3152,6 +3186,10 @@ alert button 不给新成员发送历史消息。 No comment provided by engineer. + + Do not send history to new subscribers. + No comment provided by engineer. + Do not use credentials with proxy. 代理不使用身份验证凭据。 @@ -3315,6 +3353,10 @@ chat item action 启用相机访问 No comment provided by engineer. + + Enable chats with admins? + alert title + Enable disappearing messages by default. 默认启用定时消失消息。 @@ -4558,6 +4600,10 @@ Error: %2$@ 未发送历史消息给新成员。 No comment provided by engineer. + + History is not sent to new subscribers. + No comment provided by engineer. + How SimpleX works SimpleX的工作原理 @@ -4804,9 +4850,9 @@ More improvements are coming soon! 初始角色 No comment provided by engineer. - - Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) - 安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat) + + Install SimpleX Chat for terminal + 安装用于终端的 SimpleX Chat No comment provided by engineer. @@ -5345,6 +5391,10 @@ This is your link for group %@! 群组成员可以添加信息回应。 No comment provided by engineer. + + Members can chat with admins. + No comment provided by engineer. + Members can irreversibly delete sent messages. (24 hours) 群组成员可以不可撤回地删除已发送的消息 @@ -5509,6 +5559,14 @@ This is your link for group %@! 将显示来自 %@ 的消息! No comment provided by engineer. + + Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages. + No comment provided by engineer. + + + Messages in this channel are not end-to-end encrypted. Chat relays can see these messages. + E2EE info chat item + Messages in this chat will never be deleted. 此聊天中的消息永远不会被删除。 @@ -6202,7 +6260,8 @@ Requires compatible VPN. Open 打开 - alert action + alert action +alert button Open Settings @@ -6238,6 +6297,10 @@ Requires compatible VPN. 打开条款 No comment provided by engineer. + + Open external link? + alert title + Open full link 打开完整链接 @@ -6722,6 +6785,10 @@ Error: %@ 禁止音频/视频通话。 No comment provided by engineer. + + Prohibit chats with admins. + No comment provided by engineer. + Prohibit irreversible message deletion. 禁止不可撤回消息删除。 @@ -6752,6 +6819,10 @@ Error: %@ 禁止向成员发送私信。 No comment provided by engineer. + + Prohibit sending direct messages to subscribers. + No comment provided by engineer. + Prohibit sending disappearing messages. 禁止发送限时消息。 @@ -6863,24 +6934,14 @@ Enable in *Network & servers* settings. 阅读更多 No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - 阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。 + + Read more in User Guide. + 阅读更多User Guide。 No comment provided by engineer. - - Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses). - 在 [用户指南](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) 中阅读更多内容。 - No comment provided by engineer. - - - Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - 在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。 - No comment provided by engineer. - - - Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - 在我们的 [GitHub 仓库](https://github.com/simplex-chat/simplex-chat#readme) 中阅读更多信息。 + + Read more in our GitHub repository. + 在我们的 GitHub 仓库 中阅读更多信息。 No comment provided by engineer. @@ -7758,6 +7819,10 @@ chat item action 给新成员发送最多 100 条历史消息。 No comment provided by engineer. + + Send up to 100 last messages to new subscribers. + No comment provided by engineer. + Send your private feedback to groups. 向群发送私密反馈。 @@ -8374,6 +8439,11 @@ report reason 方形、圆形、或两者之间的任意形状. No comment provided by engineer. + + Star on GitHub + 在 GitHub 上加星 + No comment provided by engineer. + Start chat 开始聊天 @@ -8477,6 +8547,10 @@ report reason Subscriber No comment provided by engineer. + + Subscriber reports + chat feature + Subscriber will be removed from channel - this cannot be undone! alert message @@ -8485,6 +8559,42 @@ report reason Subscribers No comment provided by engineer. + + Subscribers can add message reactions. + No comment provided by engineer. + + + Subscribers can chat with admins. + No comment provided by engineer. + + + Subscribers can irreversibly delete sent messages. (24 hours) + No comment provided by engineer. + + + Subscribers can report messsages to moderators. + No comment provided by engineer. + + + Subscribers can send SimpleX links. + No comment provided by engineer. + + + Subscribers can send direct messages. + No comment provided by engineer. + + + Subscribers can send disappearing messages. + No comment provided by engineer. + + + Subscribers can send files and media. + No comment provided by engineer. + + + Subscribers can send voice messages. + No comment provided by engineer. + Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel. @@ -9249,6 +9359,10 @@ To connect, please ask your contact to create another connection link and check 给新成员发送了最多 100 条历史消息。 No comment provided by engineer. + + Up to 100 last messages are sent to new subscribers. + No comment provided by engineer. + Update 更新 @@ -10285,21 +10399,11 @@ Relays can access channel messages. 您的设置 No comment provided by engineer. - - [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) - [贡献](https://github.com/simplex-chat/simplex-chat#contribute) - No comment provided by engineer. - [Send us email](mailto:chat@simplex.chat) [给我们发电邮](mailto:chat@simplex.chat) No comment provided by engineer. - - [Star on GitHub](https://github.com/simplex-chat/simplex-chat) - [在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat) - No comment provided by engineer. - \_italic_ \_斜体_ diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index aa856c8fc3..1dfa477c91 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -893,7 +893,9 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { } } - public var text: String { + public var text: String { text(isChannel: false) } + + public func text(isChannel: Bool) -> String { switch self { case .timedMessages: return NSLocalizedString("Disappearing messages", comment: "chat feature") case .directMessages: return NSLocalizedString("Direct messages", comment: "chat feature") @@ -902,7 +904,9 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: return NSLocalizedString("Voice messages", comment: "chat feature") case .files: return NSLocalizedString("Files and media", comment: "chat feature") case .simplexLinks: return NSLocalizedString("SimpleX links", comment: "chat feature") - case .reports: return NSLocalizedString("Member reports", comment: "chat feature") + case .reports: return isChannel + ? NSLocalizedString("Subscriber reports", comment: "chat feature") + : NSLocalizedString("Member reports", comment: "chat feature") case .history: return NSLocalizedString("Visible history", comment: "chat feature") case .support: return NSLocalizedString("Chat with admins", comment: "chat feature") } @@ -945,7 +949,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { } } - public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool) -> LocalizedStringKey { + public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool, isChannel: Bool = false) -> LocalizedStringKey { if canEdit { switch self { case .timedMessages: @@ -955,8 +959,12 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { } case .directMessages: switch enabled { - case .on: return "Allow sending direct messages to members." - case .off: return "Prohibit sending direct messages to members." + case .on: return isChannel + ? "Allow sending direct messages to subscribers." + : "Allow sending direct messages to members." + case .off: return isChannel + ? "Prohibit sending direct messages to subscribers." + : "Prohibit sending direct messages to members." } case .fullDelete: switch enabled { @@ -990,12 +998,18 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { } case .history: switch enabled { - case .on: return "Send up to 100 last messages to new members." - case .off: return "Do not send history to new members." + case .on: return isChannel + ? "Send up to 100 last messages to new subscribers." + : "Send up to 100 last messages to new members." + case .off: return isChannel + ? "Do not send history to new subscribers." + : "Do not send history to new members." } case .support: switch enabled { - case .on: return "Allow members to chat with admins." + case .on: return isChannel + ? "Allow subscribers to chat with admins." + : "Allow members to chat with admins." case .off: return "Prohibit chats with admins." } } @@ -1003,52 +1017,76 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { switch self { case .timedMessages: switch enabled { - case .on: return "Members can send disappearing messages." + case .on: return isChannel + ? "Subscribers can send disappearing messages." + : "Members can send disappearing messages." case .off: return "Disappearing messages are prohibited." } case .directMessages: switch enabled { - case .on: return "Members can send direct messages." - case .off: return "Direct messages between members are prohibited." + case .on: return isChannel + ? "Subscribers can send direct messages." + : "Members can send direct messages." + case .off: return isChannel + ? "Direct messages between subscribers are prohibited." + : "Direct messages between members are prohibited." } case .fullDelete: switch enabled { - case .on: return "Members can irreversibly delete sent messages. (24 hours)" + case .on: return isChannel + ? "Subscribers can irreversibly delete sent messages. (24 hours)" + : "Members can irreversibly delete sent messages. (24 hours)" case .off: return "Irreversible message deletion is prohibited." } case .reactions: switch enabled { - case .on: return "Members can add message reactions." + case .on: return isChannel + ? "Subscribers can add message reactions." + : "Members can add message reactions." case .off: return "Message reactions are prohibited." } case .voice: switch enabled { - case .on: return "Members can send voice messages." + case .on: return isChannel + ? "Subscribers can send voice messages." + : "Members can send voice messages." case .off: return "Voice messages are prohibited." } case .files: switch enabled { - case .on: return "Members can send files and media." + case .on: return isChannel + ? "Subscribers can send files and media." + : "Members can send files and media." case .off: return "Files and media are prohibited." } case .simplexLinks: switch enabled { - case .on: return "Members can send SimpleX links." + case .on: return isChannel + ? "Subscribers can send SimpleX links." + : "Members can send SimpleX links." case .off: return "SimpleX links are prohibited." } case .reports: switch enabled { - case .on: return "Members can report messsages to moderators." + case .on: return isChannel + ? "Subscribers can report messsages to moderators." + : "Members can report messsages to moderators." case .off: return "Reporting messages to moderators is prohibited." } case .history: switch enabled { - case .on: return "Up to 100 last messages are sent to new members." - case .off: return "History is not sent to new members." + case .on: return isChannel + ? "Up to 100 last messages are sent to new subscribers." + : "Up to 100 last messages are sent to new members." + case .off: return isChannel + ? "History is not sent to new subscribers." + : "History is not sent to new members." } case .support: switch enabled { - case .on: return "Members can chat with admins." + case .on: return isChannel + ? "Subscribers can chat with admins." + : "Members can chat with admins." case .off: return "Chats with admins are prohibited." } } diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index e82e9df6c7..f0838bf9df 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(това устройство v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Допринеси](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Изпратете ни имейл](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Звезда в GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Добави контакт**: за създаване на нов линк."; @@ -1381,6 +1375,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Продължи"; +/* No comment provided by engineer. */ +"Contribute" = "Допринеси"; + /* No comment provided by engineer. */ "Copy" = "Копирай"; @@ -2496,7 +2493,7 @@ server test error */ "Initial role" = "Първоначална роля"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Инсталирайте SimpleX Chat за терминал"; /* No comment provided by engineer. */ "Instant" = "Мигновено"; @@ -3097,7 +3094,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Само вашият контакт може да изпраща гласови съобщения."; -/* alert action */ +/* alert action +alert button */ "Open" = "Отвори"; /* new chat action */ @@ -3329,16 +3327,10 @@ new chat action */ "Read more" = "Прочетете още"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Прочетете повече в нашето GitHub хранилище."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Прочетете повече в нашето [GitHub хранилище](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Прочетете повече в Ръководство за потребителя."; /* No comment provided by engineer. */ "Receipts are disabled" = "Потвърждениeто за доставка е деактивирано"; @@ -3866,6 +3858,9 @@ chat item action */ /* chat item text */ "standard end-to-end encryption" = "стандартно криптиране от край до край"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Звезда в GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Започни чат"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 104835b3d3..e63d3c0cc9 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(toto zařízení v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Přispějte](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Pošlete nám e-mail](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Hvězda na GitHubu](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Vytvořit jednorázový odkaz**: pro vytvoření a sdílení nové pozvánky."; @@ -1062,6 +1056,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Pokračovat"; +/* No comment provided by engineer. */ +"Contribute" = "Přispějte"; + /* No comment provided by engineer. */ "Copy" = "Kopírovat"; @@ -1987,7 +1984,7 @@ server test error */ "Initial role" = "Počáteční role"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Nainstalujte SimpleX Chat pro terminál"; /* No comment provided by engineer. */ "Instant" = "Okamžitě"; @@ -2483,7 +2480,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Hlasové zprávy může odesílat pouze váš kontakt."; -/* alert action */ +/* alert action +alert button */ "Open" = "Otevřít"; /* new chat action */ @@ -2652,16 +2650,10 @@ new chat action */ "Read more" = "Přečíst více"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Více informací v [průvodci uživatele](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Přečtěte si více v našem GitHub repozitáři."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Přečtěte si více v našem [GitHub repozitáři](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Více informací v průvodci uživatele."; /* No comment provided by engineer. */ "Receipts are disabled" = "Informace o dodání jsou zakázány"; @@ -3099,6 +3091,9 @@ chat item action */ /* notification title */ "Somebody" = "Někdo"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Hvězda na GitHubu"; + /* No comment provided by engineer. */ "Start chat" = "Začít chat"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 65f8320b5c..739f2e7721 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(Dieses Gerät hat v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Unterstützen Sie uns](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Senden Sie uns eine E-Mail](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Stern auf GitHub vergeben](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Kontakt hinzufügen**: Um einen neuen Einladungslink zu erstellen."; @@ -604,6 +598,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder erlauben."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "Das Senden von Direktnachrichten an Abonnenten erlauben."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Das Senden von verschwindenden Nachrichten erlauben."; @@ -1548,6 +1545,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Weiter"; +/* No comment provided by engineer. */ +"Contribute" = "Unterstützen Sie uns"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Chat-Inhalte entfernt!"; @@ -2004,6 +2004,9 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "In dieser Gruppe sind Direktnachrichten zwischen Mitgliedern nicht erlaubt."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "Direktnachrichten zwischen Abonnenten sind nicht erlaubt."; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Deaktivieren (vorgenommene Einstellungen bleiben erhalten)"; @@ -2061,6 +2064,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "Den Nachrichtenverlauf nicht an neue Mitglieder senden."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "Den Nachrichtenverlauf nicht an neue Abonnenten senden."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "Nachrichten werden nicht direkt versendet, selbst wenn Ihr oder der Zielserver kein privates Routing unterstützt."; @@ -2982,6 +2988,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "Der Nachrichtenverlauf wird nicht an neue Gruppenmitglieder gesendet."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "Der Nachrichtenverlauf wird nicht an neue Abonnenten gesendet."; + /* time unit */ "hours" = "Stunden"; @@ -3145,7 +3154,7 @@ servers warning */ "Initial role" = "Anfängliche Rolle"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installieren Sie [SimpleX Chat als Terminalanwendung](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Installieren Sie SimpleX Chat als Terminalanwendung"; /* No comment provided by engineer. */ "Instant" = "Sofort"; @@ -4082,7 +4091,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Nur Ihr Kontakt kann Sprachnachrichten versenden."; -/* alert action */ +/* alert action +alert button */ "Open" = "Öffnen"; /* No comment provided by engineer. */ @@ -4421,6 +4431,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Das Senden von Direktnachrichten an Gruppenmitglieder nicht erlauben."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "Das Senden von Direktnachrichten an Abonnenten nicht erlauben."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Das Senden von verschwindenden Nachrichten nicht erlauben."; @@ -4491,16 +4504,10 @@ new chat action */ "Read more" = "Mehr erfahren"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lesen Sie mehr dazu im [Benutzerhandbuch](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Erfahren Sie in unserem GitHub-Repository mehr dazu."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) lesen."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Mehr dazu in der [Benutzeranleitung](https://simplex.chat/docs/guide/readme.html#connect-to-friends) lesen."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Erfahren Sie in unserem [GitHub-Repository](https://github.com/simplex-chat/simplex-chat#readme) mehr dazu."; +"Read more in User Guide." = "Lesen Sie mehr dazu im Benutzerhandbuch."; /* No comment provided by engineer. */ "Receipts are disabled" = "Bestätigungen sind deaktiviert"; @@ -5093,6 +5100,9 @@ chat item action */ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Bis zu 100 der letzten Nachrichten an neue Gruppenmitglieder senden."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Bis zu 100 der letzten Nachrichten an neue Abonnenten senden."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Senden Sie Ihr privates Feedback an Gruppen."; @@ -5470,6 +5480,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "Standard-Ende-zu-Ende-Verschlüsselung"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Stern auf GitHub vergeben"; + /* No comment provided by engineer. */ "Start chat" = "Starten Sie den Chat"; @@ -5539,12 +5552,39 @@ report reason */ /* No comment provided by engineer. */ "Subscriber" = "Abonnent"; +/* chat feature */ +"Subscriber reports" = "Abonnenten-Meldungen"; + /* alert message */ "Subscriber will be removed from channel - this cannot be undone!" = "Abonnent wird aus dem Kanal entfernt. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ "Subscribers" = "Abonnenten"; +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "Abonnenten können eine Reaktion auf Nachrichten geben."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "Abonnenten können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "Abonnenten können Nachrichten an Moderatoren melden."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "Abonnenten können Direktnachrichten versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "Abonnenten können verschwindende Nachrichten versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "Abonnenten können Dateien und Medien versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "Abonnenten können SimpleX-Links versenden."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "Abonnenten können Sprachnachrichten versenden."; + /* No comment provided by engineer. */ "Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Abonnenten verbinden sich über den Relais‑Link mit dem Kanal.\nDie Relais-Adresse wurde zur Einrichtung dieses Relais für diesen Kanal verwendet."; @@ -6014,6 +6054,9 @@ server test failure */ /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Bis zu 100 der letzten Nachrichten werden an neue Mitglieder gesendet."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Bis zu 100 der letzten Nachrichten werden an neue Abonnenten gesendet."; + /* No comment provided by engineer. */ "Update" = "Aktualisieren"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 4901d67e23..865a9472ea 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(este dispositivo v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuye](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Contacta vía email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Estrella en GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Añadir contacto**: crea un enlace de invitación nuevo."; @@ -604,6 +598,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "Se permiten mensajes directos entre miembros."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "Se permiten mensajes directos entre suscriptores."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Permites el envío de mensajes temporales."; @@ -1548,6 +1545,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Continuar"; +/* No comment provided by engineer. */ +"Contribute" = "Contribuye"; + /* No comment provided by engineer. */ "Conversation deleted!" = "¡Conversación eliminada!"; @@ -2004,6 +2004,9 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "Los mensajes directos entre miembros del grupo no están permitidos."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "Los mensajes directos entre suscriptores del canal no están permitidos."; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Desactivar (conservando anulaciones)"; @@ -2061,6 +2064,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "No se envía el historial a los miembros nuevos."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "No se envía el historial a los suscriptores nuevos."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado."; @@ -2982,6 +2988,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "El historial no se envía a miembros nuevos."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "El historial no se envía a suscriptores nuevos."; + /* time unit */ "hours" = "horas"; @@ -3145,7 +3154,7 @@ servers warning */ "Initial role" = "Rol inicial"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Instalar terminal para [SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Instalar terminal para SimpleX Chat"; /* No comment provided by engineer. */ "Instant" = "Al instante"; @@ -4082,7 +4091,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sólo tu contacto puede enviar mensajes de voz."; -/* alert action */ +/* alert action +alert button */ "Open" = "Abrir"; /* No comment provided by engineer. */ @@ -4421,6 +4431,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "No se permiten mensajes directos entre miembros."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "No se permiten mensajes directos entre suscriptores."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "No se permiten mensajes temporales."; @@ -4491,16 +4504,10 @@ new chat action */ "Read more" = "Saber más"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Conoce más en nuestro repositorio GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Conoce más en la Guía del Usuario."; /* No comment provided by engineer. */ "Receipts are disabled" = "Las confirmaciones están desactivadas"; @@ -5093,6 +5100,9 @@ chat item action */ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Se envían hasta 100 mensajes más recientes a los miembros nuevos."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Se envían hasta 100 mensajes más recientes a los suscriptores nuevos."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Envía tu comentario privado a los grupos."; @@ -5470,6 +5480,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "cifrado estándar de extremo a extremo"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Estrella en GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Iniciar chat"; @@ -5539,12 +5552,39 @@ report reason */ /* No comment provided by engineer. */ "Subscriber" = "Suscriptor"; +/* chat feature */ +"Subscriber reports" = "Informes de suscriptores"; + /* alert message */ "Subscriber will be removed from channel - this cannot be undone!" = "El suscriptor será eliminado del canal. ¡No puede deshacerse!"; /* No comment provided by engineer. */ "Subscribers" = "Suscriptores"; +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "Los suscriptores pueden añadir reacciones a los mensajes."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "Los suscriptores del canal pueden eliminar mensajes de forma irreversible. (24 horas)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "Los suscriptores pueden informar de mensajes a los moderadores."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "Los suscriptores del canal pueden enviar mensajes directos."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "Los suscriptores del canal pueden enviar mensajes temporales."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "Los suscriptores del canal pueden enviar archivos y multimedia."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "Los suscriptores del canal pueden enviar enlaces SimpleX."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "Los suscriptores del canal pueden enviar mensajes de voz."; + /* No comment provided by engineer. */ "Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Los suscriptores usan el enlace del servidor para conectarse a los canales.\nLa dirección del servidor se usó para establecer el servidor para el canal."; @@ -6014,6 +6054,9 @@ server test failure */ /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Hasta 100 últimos mensajes son enviados a los miembros nuevos."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Hasta 100 últimos mensajes son enviados a los suscriptores nuevos."; + /* No comment provided by engineer. */ "Update" = "Actualizar"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 74f071a6e0..4dfb01aae7 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -13,15 +13,9 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 värillinen!"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Osallistu](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Lähetä meille sähköpostia](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Tähti GitHubissa](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e-salattu** äänipuhelu"; @@ -744,6 +738,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Jatka"; +/* No comment provided by engineer. */ +"Contribute" = "Osallistu"; + /* No comment provided by engineer. */ "Copy" = "Kopioi"; @@ -1660,7 +1657,7 @@ server test error */ "Initial role" = "Alkuperäinen rooli"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Asenna SimpleX Chat terminaalille"; /* No comment provided by engineer. */ "Instant" = "Heti"; @@ -2319,13 +2316,10 @@ new chat action */ "Read more" = "Lue lisää"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; +"Read more in our GitHub repository." = "Lue lisää GitHub-arkistosta."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Lue lisää Käyttöoppaasta."; /* No comment provided by engineer. */ "Receipts are disabled" = "Kuittaukset pois käytöstä"; @@ -2757,6 +2751,9 @@ chat item action */ /* notification title */ "Somebody" = "Joku"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Tähti GitHubissa"; + /* No comment provided by engineer. */ "Start chat" = "Aloita keskustelu"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 279bf9f3a0..27140ac84c 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(cet appareil v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuer](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Contact par mail](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star sur GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Ajouter un contact** : pour créer un nouveau lien d'invitation."; @@ -1375,6 +1369,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Continuer"; +/* No comment provided by engineer. */ +"Contribute" = "Contribuer"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Conversation supprimée !"; @@ -2815,7 +2812,7 @@ servers warning */ "Initial role" = "Rôle initial"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Installer SimpleX Chat pour terminal"; /* No comment provided by engineer. */ "Instant" = "Instantané"; @@ -3572,7 +3569,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Seul votre contact peut envoyer des messages vocaux."; -/* alert action */ +/* alert action +alert button */ "Open" = "Ouvrir"; /* No comment provided by engineer. */ @@ -3891,16 +3889,10 @@ new chat action */ "Read more" = "En savoir plus"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Pour en savoir plus, consultez notre dépôt GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Pour en savoir plus, consultez notre [dépôt GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Pour en savoir plus, consultez le Guide de l'utilisateur."; /* No comment provided by engineer. */ "Receipts are disabled" = "Les accusés de réception sont désactivés"; @@ -4659,6 +4651,9 @@ chat item action */ /* chat item text */ "standard end-to-end encryption" = "chiffrement de bout en bout standard"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Star sur GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Démarrer le chat"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index db3c4c8255..d8365a164f 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(ez az eszköz: v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Közreműködés](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Küldjön nekünk e-mailt](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillagozás a GitHubon](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Partner hozzáadása:** új meghívási hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz."; @@ -604,6 +598,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között engedélyezve van."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "A közvetlen üzenetek küldése a feliratkozók között engedélyezve van."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Az eltűnő üzenetek küldése engedélyezve van."; @@ -1548,6 +1545,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Folytatás"; +/* No comment provided by engineer. */ +"Contribute" = "Közreműködés"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Beszélgetés törölve!"; @@ -2004,6 +2004,9 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "A tagok közötti közvetlen üzenetek le vannak tiltva."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "A feliratkozók közötti közvetlen üzenetek le vannak tiltva."; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Letiltás (egyéni beállítások megtartása)"; @@ -2061,6 +2064,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "Az előzmények ne legyenek elküldve az új tagok számára."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "Az előzmények ne legyenek elküldve az új feliratkozók számára."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; @@ -2982,6 +2988,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "Az előzmények nem lesznek elküldve az új tagok számára."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "Az előzmények nem lesznek elküldve az új feliratkozók számára."; + /* time unit */ "hours" = "óra"; @@ -3145,7 +3154,7 @@ servers warning */ "Initial role" = "Kezdeti szerepkör"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "A SimpleX Chat terminálhoz telepítése"; /* No comment provided by engineer. */ "Instant" = "Azonnali"; @@ -4082,7 +4091,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Csak a partnere küldhet hangüzeneteket."; -/* alert action */ +/* alert action +alert button */ "Open" = "Megnyitás"; /* No comment provided by engineer. */ @@ -4421,6 +4431,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között le van tiltva."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "A közvetlen üzenetek küldése a feliratkozók között le van tiltva."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Az eltűnő üzenetek küldése le van tiltva."; @@ -4491,16 +4504,10 @@ new chat action */ "Read more" = "Tudjon meg többet"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "További információ a GitHub-tárolónkban."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "További információ a [Használati útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "További információ a [GitHub-tárolónkban](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "További információ a Használati útmutatóban."; /* No comment provided by engineer. */ "Receipts are disabled" = "A kézbesítési jelentések le vannak tiltva"; @@ -5093,6 +5100,9 @@ chat item action */ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Legfeljebb az utolsó 100 üzenet elküldése az új tagok számára."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Legfeljebb az utolsó 100 üzenet elküldése az új feliratkozók számára."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Küldjön privát visszajelzést a csoportoknak."; @@ -5470,6 +5480,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "szabványos végpontok közötti titkosítás"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Csillagozás a GitHubon"; + /* No comment provided by engineer. */ "Start chat" = "Csevegés elindítása"; @@ -5539,12 +5552,39 @@ report reason */ /* No comment provided by engineer. */ "Subscriber" = "Feliratkozó"; +/* chat feature */ +"Subscriber reports" = "Feliratkozók jelentései"; + /* alert message */ "Subscriber will be removed from channel - this cannot be undone!" = "A feliratkozó el lesz távolítva a csatornából – ez a művelet nem vonható vissza!"; /* No comment provided by engineer. */ "Subscribers" = "Feliratkozók"; +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "A feliratkozók reakciókat adhatnak hozzá az üzenetekhez."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "A feliratkozók véglegesen törölhetik az elküldött üzeneteiket. (24 óra)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "A feliratkozók jelenthetik az üzeneteket a moderátorok felé."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "A feliratkozók küldhetnek egymásnak közvetlen üzeneteket."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "A feliratkozók küldhetnek eltűnő üzeneteket."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "A feliratkozók küldhetnek fájlokat és médiatartalmakat."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "A feliratkozók küldhetnek SimpleX-hivatkozásokat."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "A feliratkozók küldhetnek hangüzeneteket."; + /* No comment provided by engineer. */ "Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "A feliratkozók az átjátszó hivatkozását használják a csatornához való kapcsolódáshoz.\nAz átjátszó címe ennek az átjátszónak a beállítására szolgált a csatornához."; @@ -6014,6 +6054,9 @@ server test failure */ /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Legfeljebb az utolsó 100 üzenet lesz elküldve az új tagok számára."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Legfeljebb az utolsó 100 üzenet lesz elküldve az új feliratkozók számára."; + /* No comment provided by engineer. */ "Update" = "Frissítés"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 0c1bdccfad..2d1161de80 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(questo dispositivo v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuisci](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Inviaci un'email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Dai una stella su GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Aggiungi contatto**: per creare un nuovo link di invito."; @@ -604,6 +598,9 @@ swipe action */ /* No comment provided by engineer. */ "Allow sending direct messages to members." = "Permetti l'invio di messaggi diretti ai membri."; +/* No comment provided by engineer. */ +"Allow sending direct messages to subscribers." = "Permetti l'invio di messaggi diretti agli iscritti."; + /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Permetti l'invio di messaggi a tempo."; @@ -1548,6 +1545,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Continua"; +/* No comment provided by engineer. */ +"Contribute" = "Contribuisci"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Conversazione eliminata!"; @@ -2004,6 +2004,9 @@ alert button */ /* No comment provided by engineer. */ "Direct messages between members are prohibited." = "I messaggi diretti tra i membri sono vietati in questo gruppo."; +/* No comment provided by engineer. */ +"Direct messages between subscribers are prohibited." = "I messaggi diretti tra gli iscritti sono vietati."; + /* No comment provided by engineer. */ "Disable (keep overrides)" = "Disattiva (mantieni sostituzioni)"; @@ -2061,6 +2064,9 @@ alert button */ /* No comment provided by engineer. */ "Do not send history to new members." = "Non inviare la cronologia ai nuovi membri."; +/* No comment provided by engineer. */ +"Do not send history to new subscribers." = "Non inviare la cronologia ai nuovi iscritti."; + /* No comment provided by engineer. */ "Do NOT send messages directly, even if your or destination server does not support private routing." = "NON inviare messaggi direttamente, anche se il tuo server o quello di destinazione non supporta l'instradamento privato."; @@ -2982,6 +2988,9 @@ servers warning */ /* No comment provided by engineer. */ "History is not sent to new members." = "La cronologia non viene inviata ai nuovi membri."; +/* No comment provided by engineer. */ +"History is not sent to new subscribers." = "La cronologia non viene inviata ai nuovi iscritti."; + /* time unit */ "hours" = "ore"; @@ -3145,7 +3154,7 @@ servers warning */ "Initial role" = "Ruolo iniziale"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installa [Simplex Chat per terminale](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Installa Simplex Chat per terminale"; /* No comment provided by engineer. */ "Instant" = "Istantaneamente"; @@ -4082,7 +4091,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Solo il tuo contatto può inviare messaggi vocali."; -/* alert action */ +/* alert action +alert button */ "Open" = "Apri"; /* No comment provided by engineer. */ @@ -4421,6 +4431,9 @@ new chat action */ /* No comment provided by engineer. */ "Prohibit sending direct messages to members." = "Proibisci l'invio di messaggi diretti ai membri."; +/* No comment provided by engineer. */ +"Prohibit sending direct messages to subscribers." = "Proibisci l'invio di messaggi diretti agli iscritti."; + /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Proibisci l'invio di messaggi a tempo."; @@ -4491,16 +4504,10 @@ new chat action */ "Read more" = "Leggi tutto"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Leggi di più nella [Guida utente](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Maggiori informazioni nel nostro repository GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Maggiori informazioni nella [Guida per l'utente](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Maggiori informazioni nel nostro [repository GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Leggi di più nella Guida utente."; /* No comment provided by engineer. */ "Receipts are disabled" = "Le ricevute sono disattivate"; @@ -5093,6 +5100,9 @@ chat item action */ /* No comment provided by engineer. */ "Send up to 100 last messages to new members." = "Invia fino a 100 ultimi messaggi ai nuovi membri."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new subscribers." = "Invia fino a 100 ultimi messaggi ai nuovi iscritti."; + /* No comment provided by engineer. */ "Send your private feedback to groups." = "Invia i tuoi commenti privati ai gruppi."; @@ -5470,6 +5480,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "crittografia end-to-end standard"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Dai una stella su GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Avvia chat"; @@ -5539,12 +5552,39 @@ report reason */ /* No comment provided by engineer. */ "Subscriber" = "Iscritto"; +/* chat feature */ +"Subscriber reports" = "Segnalazioni degli iscritti"; + /* alert message */ "Subscriber will be removed from channel - this cannot be undone!" = "L'iscritto verrà rimosso dal canale, non è reversibile!"; /* No comment provided by engineer. */ "Subscribers" = "Iscritti"; +/* No comment provided by engineer. */ +"Subscribers can add message reactions." = "Gli iscritti al canale possono aggiungere reazioni ai messaggi."; + +/* No comment provided by engineer. */ +"Subscribers can irreversibly delete sent messages. (24 hours)" = "Gli iscritti al canale possono eliminare irreversibilmente i messaggi inviati. (24 ore)"; + +/* No comment provided by engineer. */ +"Subscribers can report messsages to moderators." = "Gli iscritti possono segnalare messaggi ai moderatori."; + +/* No comment provided by engineer. */ +"Subscribers can send direct messages." = "Gli iscritti al canale possono inviare messaggi diretti."; + +/* No comment provided by engineer. */ +"Subscribers can send disappearing messages." = "Gli iscritti al canale possono inviare messaggi a tempo."; + +/* No comment provided by engineer. */ +"Subscribers can send files and media." = "Gli iscritti al canale possono inviare file e contenuti multimediali."; + +/* No comment provided by engineer. */ +"Subscribers can send SimpleX links." = "Gli iscritti al canale possono inviare link di Simplex."; + +/* No comment provided by engineer. */ +"Subscribers can send voice messages." = "Gli iscritti al canale possono inviare messaggi vocali."; + /* No comment provided by engineer. */ "Subscribers use relay link to connect to the channel.\nRelay address was used to set up this relay for the channel." = "Gli iscritti usano il link del relay per connettersi al canale.\nL'indirizzo del relay è stato usato per impostare questo relay per il canale."; @@ -6014,6 +6054,9 @@ server test failure */ /* No comment provided by engineer. */ "Up to 100 last messages are sent to new members." = "Vengono inviati ai nuovi membri fino a 100 ultimi messaggi."; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new subscribers." = "Vengono inviati ai nuovi iscritti fino a 100 ultimi messaggi."; + /* No comment provided by engineer. */ "Update" = "Aggiorna"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index ace0f7a227..37dd9d92ba 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(このデバイス v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[貢献する](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[メールを送信](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[GitHub でスターを付ける](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。"; @@ -1017,6 +1011,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "続ける"; +/* No comment provided by engineer. */ +"Contribute" = "貢献する"; + /* No comment provided by engineer. */ "Copy" = "コピー"; @@ -1951,7 +1948,7 @@ server test error */ "Initial role" = "初期の役割"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "インストール ターミナル用SimpleX Chat"; /* No comment provided by engineer. */ "Instant" = "即時"; @@ -2453,7 +2450,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "音声メッセージを送れるのはあなたの連絡相手だけです。"; -/* alert action */ +/* alert action +alert button */ "Open" = "開く"; /* new chat action */ @@ -2625,13 +2623,10 @@ new chat action */ "Read more" = "続きを読む"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)をご覧ください。"; +"Read more in our GitHub repository." = "詳しくはGitHubリポジトリをご覧ください。"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/readme.html#connect-to-friends)をご覧ください。"; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "詳しくは[GitHubリポジトリ](https://github.com/simplex-chat/simplex-chat#readme)をご覧ください。"; +"Read more in User Guide." = "詳しくはユーザーガイドをご覧ください。"; /* No comment provided by engineer. */ "received answer…" = "回答を受け取りました…"; @@ -3042,6 +3037,9 @@ chat item action */ /* notification title */ "Somebody" = "誰か"; +/* No comment provided by engineer. */ +"Star on GitHub" = "GitHub でスターを付ける"; + /* No comment provided by engineer. */ "Start chat" = "チャットを開始する"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index b4bc7cfca7..be478b677d 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(dit apparaat v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Bijdragen](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Stuur ons een e-mail](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen."; @@ -1399,6 +1393,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Doorgaan"; +/* No comment provided by engineer. */ +"Contribute" = "Bijdragen"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Gesprek verwijderd!"; @@ -2875,7 +2872,7 @@ servers warning */ "Initial role" = "Initiële rol"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Installeer SimpleX Chat voor terminal"; /* No comment provided by engineer. */ "Instant" = "Direct"; @@ -3731,7 +3728,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Alleen uw contact kan spraak berichten verzenden."; -/* alert action */ +/* alert action +alert button */ "Open" = "Open"; /* No comment provided by engineer. */ @@ -4089,16 +4087,10 @@ new chat action */ "Read more" = "Lees meer"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Lees meer in onze GitHub-repository."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lees meer in onze [GitHub-repository](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Lees meer in de Gebruikershandleiding."; /* No comment provided by engineer. */ "Receipts are disabled" = "Bevestigingen zijn uitgeschakeld"; @@ -4951,6 +4943,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "standaard end-to-end encryptie"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Star on GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Begin gesprek"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index d0962a036a..14c57ae7e0 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(to urządzenie v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Przyczyń się](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Wyślij do nas email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Daj gwiazdkę na GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku."; @@ -1450,6 +1444,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Kontynuuj"; +/* No comment provided by engineer. */ +"Contribute" = "Przyczyń się"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Rozmowa usunięta!"; @@ -2996,7 +2993,7 @@ servers warning */ "Initial role" = "Rola początkowa"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Zainstaluj SimpleX Chat na terminal"; /* No comment provided by engineer. */ "Instant" = "Natychmiastowo"; @@ -3897,7 +3894,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Tylko Twój kontakt może wysyłać wiadomości głosowe."; -/* alert action */ +/* alert action +alert button */ "Open" = "Otwórz"; /* No comment provided by engineer. */ @@ -4285,16 +4283,10 @@ new chat action */ "Read more" = "Przeczytaj więcej"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Przeczytaj więcej na naszym repozytorium GitHub."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Przeczytaj więcej na naszym [repozytorium GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Przeczytaj więcej w Poradniku Użytkownika."; /* No comment provided by engineer. */ "Receipts are disabled" = "Potwierdzenia są wyłączone"; @@ -5219,6 +5211,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "standardowe szyfrowanie end-to-end"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Daj gwiazdkę na GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Rozpocznij czat"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index f06ba61b27..24f0c358cc 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(это устройство v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Внести свой вклад](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Отправить email](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Поставить звездочку в GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Добавить контакт**: создать и поделиться новой ссылкой-приглашением."; @@ -1441,6 +1435,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Продолжить"; +/* No comment provided by engineer. */ +"Contribute" = "Внести свой вклад"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Разговор удален!"; @@ -2969,7 +2966,7 @@ servers warning */ "Initial role" = "Роль при вступлении"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "[SimpleX Chat для терминала](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "SimpleX Chat для терминала"; /* No comment provided by engineer. */ "Instant" = "Мгновенно"; @@ -3861,7 +3858,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Только Ваш контакт может отправлять голосовые сообщения."; -/* alert action */ +/* alert action +alert button */ "Open" = "Открыть"; /* No comment provided by engineer. */ @@ -4249,16 +4247,10 @@ new chat action */ "Read more" = "Узнать больше"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Дополнительная информация в [Руководстве пользователя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Узнайте больше из нашего GitHub репозитория."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Узнать больше в [Руководстве пользователя](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Узнайте больше из нашего [GitHub репозитория](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Дополнительная информация в Руководстве пользователя."; /* No comment provided by engineer. */ "Receipts are disabled" = "Отчёты о доставке выключены"; @@ -5165,6 +5157,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "стандартное end-to-end шифрование"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Поставить звездочку в GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Запустить чат"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 925fbdc9df..827dedd00b 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -13,15 +13,9 @@ /* No comment provided by engineer. */ "!1 colored!" = "!1 มีสี!"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[มีส่วนร่วม](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[ส่งอีเมลถึงเรา](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[ติดดาวบน GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "การโทรเสียงแบบ **encrypted จากต้นจนจบ**"; @@ -717,6 +711,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "ดำเนินการต่อ"; +/* No comment provided by engineer. */ +"Contribute" = "มีส่วนร่วม"; + /* No comment provided by engineer. */ "Copy" = "คัดลอก"; @@ -1609,7 +1606,7 @@ server test error */ "Initial role" = "บทบาทเริ่มต้น"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "ติดตั้ง SimpleX Chat สำหรับเทอร์มินัล"; /* No comment provided by engineer. */ "Instant" = "ทันที"; @@ -2259,13 +2256,10 @@ new chat action */ "Read more" = "อ่านเพิ่มเติม"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)"; +"Read more in our GitHub repository." = "อ่านเพิ่มเติมในพื้นที่เก็บข้อมูล GitHub"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/readme.html#connect-to-friends)"; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "อ่านเพิ่มเติมใน[พื้นที่เก็บข้อมูล GitHub](https://github.com/simplex-chat/simplex-chat#readme)"; +"Read more in User Guide." = "อ่านเพิ่มเติมในคู่มือผู้ใช้"; /* No comment provided by engineer. */ "received answer…" = "ได้รับคำตอบ…"; @@ -2679,6 +2673,9 @@ chat item action */ /* notification title */ "Somebody" = "ใครบางคน"; +/* No comment provided by engineer. */ +"Star on GitHub" = "ติดดาวบน GitHub"; + /* No comment provided by engineer. */ "Start chat" = "เริ่มแชท"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 5da3bf688a..8681e938bf 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(bu cihaz v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Katkıda bulun](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Bize e-posta gönder](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Bize GitHub'da yıldız verin](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Kişi ekle**: yeni bir davet bağlantısı oluşturmak için, ya da aldığın bağlantıyla bağlan."; @@ -1441,6 +1435,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Devam et"; +/* No comment provided by engineer. */ +"Contribute" = "Katkıda bulun"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Sohbet silindi!"; @@ -2953,7 +2950,7 @@ servers warning */ "Initial role" = "Başlangıç rolü"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "[Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Terminal için SimpleX Chat'i indir"; /* No comment provided by engineer. */ "Instant" = "Anında"; @@ -3842,7 +3839,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Sadece karşıdaki kişi sesli mesajlar gönderebilir."; -/* alert action */ +/* alert action +alert button */ "Open" = "Aç"; /* No comment provided by engineer. */ @@ -4230,16 +4228,10 @@ new chat action */ "Read more" = "Dahasını oku"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "GitHub deposunda daha fazlasını okuyun."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "[GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Kullanıcı Rehberinde daha fazlasını okuyun."; /* No comment provided by engineer. */ "Receipts are disabled" = "Alındı onayları devre dışı bırakıldı"; @@ -5146,6 +5138,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "standart uçtan uca şifreleme"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Bize GitHub'da yıldız verin"; + /* No comment provided by engineer. */ "Start chat" = "Sohbeti başlat"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 6b6604016c..06e7b26dae 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(цей пристрій v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Внесок](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[Напишіть нам електронною поштою](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Зірка на GitHub](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**Додати контакт**: створити нове посилання-запрошення."; @@ -1426,6 +1420,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "Продовжуйте"; +/* No comment provided by engineer. */ +"Contribute" = "Внесок"; + /* No comment provided by engineer. */ "Conversation deleted!" = "Розмова видалена!"; @@ -2929,7 +2926,7 @@ servers warning */ "Initial role" = "Початкова роль"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "Встановіть SimpleX Chat для терміналу"; /* No comment provided by engineer. */ "Instant" = "Миттєво"; @@ -3806,7 +3803,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "Тільки ваш контакт може надсилати голосові повідомлення."; -/* alert action */ +/* alert action +alert button */ "Open" = "Відкрито"; /* No comment provided by engineer. */ @@ -4185,16 +4183,10 @@ new chat action */ "Read more" = "Читати далі"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in our GitHub repository." = "Читайте більше в нашому GitHub репозиторії."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)."; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Читайте більше в нашому [GitHub репозиторії](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in User Guide." = "Читайте більше в User Guide."; /* No comment provided by engineer. */ "Receipts are disabled" = "Підтвердження виключені"; @@ -5092,6 +5084,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "стандартне наскрізне шифрування"; +/* No comment provided by engineer. */ +"Star on GitHub" = "Зірка на GitHub"; + /* No comment provided by engineer. */ "Start chat" = "Почати чат"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index ca1eb754ef..3297b7dce0 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -25,15 +25,9 @@ /* No comment provided by engineer. */ "(this device v%@)" = "(此设备 v%@)"; -/* No comment provided by engineer. */ -"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[贡献](https://github.com/simplex-chat/simplex-chat#contribute)"; - /* No comment provided by engineer. */ "[Send us email](mailto:chat@simplex.chat)" = "[给我们发电邮](mailto:chat@simplex.chat)"; -/* No comment provided by engineer. */ -"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat)"; - /* No comment provided by engineer. */ "**Create 1-time link**: to create and share a new invitation link." = "**添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接."; @@ -1444,6 +1438,9 @@ server test step */ /* No comment provided by engineer. */ "Continue" = "继续"; +/* No comment provided by engineer. */ +"Contribute" = "贡献"; + /* No comment provided by engineer. */ "Conversation deleted!" = "对话已删除!"; @@ -2978,7 +2975,7 @@ servers warning */ "Initial role" = "初始角色"; /* No comment provided by engineer. */ -"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat)"; +"Install SimpleX Chat for terminal" = "安装用于终端的 SimpleX Chat"; /* No comment provided by engineer. */ "Instant" = "即时"; @@ -3876,7 +3873,8 @@ new chat action */ /* No comment provided by engineer. */ "Only your contact can send voice messages." = "只有您的联系人可以发送语音消息。"; -/* alert action */ +/* alert action +alert button */ "Open" = "打开"; /* No comment provided by engineer. */ @@ -4261,16 +4259,10 @@ new chat action */ "Read more" = "阅读更多"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。"; +"Read more in our GitHub repository." = "在我们的 GitHub 仓库 中阅读更多信息。"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "在 [用户指南](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) 中阅读更多内容。"; - -/* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。"; - -/* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "在我们的 [GitHub 仓库](https://github.com/simplex-chat/simplex-chat#readme) 中阅读更多信息。"; +"Read more in User Guide." = "阅读更多User Guide。"; /* No comment provided by engineer. */ "Receipts are disabled" = "回执已禁用"; @@ -5189,6 +5181,9 @@ report reason */ /* chat item text */ "standard end-to-end encryption" = "标准端到端加密"; +/* No comment provided by engineer. */ +"Star on GitHub" = "在 GitHub 上加星"; + /* No comment provided by engineer. */ "Start chat" = "开始聊天"; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 0e5b57d8c2..0363c4fc05 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5715,8 +5715,9 @@ enum class GroupFeature: Feature { Support -> false } - override val text: String - get() = when(this) { + override val text: String get() = text(isChannel = false) + + fun text(isChannel: Boolean): String = when(this) { TimedMessages -> generalGetString(MR.strings.timed_messages) DirectMessages -> generalGetString(MR.strings.direct_messages) FullDelete -> generalGetString(MR.strings.full_deletion) @@ -5724,7 +5725,7 @@ enum class GroupFeature: Feature { Voice -> generalGetString(MR.strings.voice_messages) Files -> generalGetString(MR.strings.files_and_media) SimplexLinks -> generalGetString(MR.strings.simplex_links) - Reports -> generalGetString(MR.strings.group_reports_member_reports) + Reports -> generalGetString(if (isChannel) MR.strings.group_reports_subscriber_reports else MR.strings.group_reports_member_reports) History -> generalGetString(MR.strings.recent_history) Support -> generalGetString(MR.strings.chat_with_admins) } @@ -5757,7 +5758,7 @@ enum class GroupFeature: Feature { Support -> painterResource(MR.images.ic_help_filled) } - fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String = + fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean, isChannel: Boolean = false): String = if (canEdit) { when(this) { TimedMessages -> when(enabled) { @@ -5765,8 +5766,8 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_disappearing) } DirectMessages -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_direct_messages) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_direct_messages) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.allow_direct_messages_channel else MR.strings.allow_direct_messages) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.prohibit_direct_messages_channel else MR.strings.prohibit_direct_messages) } FullDelete -> when(enabled) { GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_delete_messages) @@ -5793,54 +5794,54 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_member_reports) } History -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.enable_sending_recent_history_channel else MR.strings.enable_sending_recent_history) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.disable_sending_recent_history_channel else MR.strings.disable_sending_recent_history) } Support -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_chat_with_admins) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.allow_chat_with_admins_channel else MR.strings.allow_chat_with_admins) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_chat_with_admins) } } } else { when(this) { TimedMessages -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_disappearing) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_disappearing_channel else MR.strings.group_members_can_send_disappearing) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disappearing_messages_are_prohibited) } DirectMessages -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_dms) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.direct_messages_are_prohibited) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_dms_channel else MR.strings.group_members_can_send_dms) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.direct_messages_are_prohibited_channel else MR.strings.direct_messages_are_prohibited) } FullDelete -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_delete) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_delete_channel else MR.strings.group_members_can_delete) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.message_deletion_prohibited_in_chat) } Reactions -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_add_message_reactions) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_add_message_reactions_channel else MR.strings.group_members_can_add_message_reactions) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.message_reactions_are_prohibited) } Voice -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_voice) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_voice_channel else MR.strings.group_members_can_send_voice) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.voice_messages_are_prohibited) } Files -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_files) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_files_channel else MR.strings.group_members_can_send_files) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.files_are_prohibited_in_group) } SimplexLinks -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_simplex_links) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_simplex_links_channel else MR.strings.group_members_can_send_simplex_links) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.simplex_links_are_prohibited_in_group) } Reports -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_reports) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_reports_channel else MR.strings.group_members_can_send_reports) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.member_reports_are_prohibited) } History -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members) - GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.recent_history_is_sent_to_new_members_channel else MR.strings.recent_history_is_sent_to_new_members) + GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.recent_history_is_not_sent_to_new_members_channel else MR.strings.recent_history_is_not_sent_to_new_members) } Support -> when(enabled) { - GroupFeatureEnabled.ON -> generalGetString(MR.strings.members_can_chat_with_admins) + GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.members_can_chat_with_admins_channel else MR.strings.members_can_chat_with_admins) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.chat_with_admins_is_prohibited) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index b84d4a4730..740349eaea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -267,7 +267,7 @@ private fun FeatureSection( val timedOn = feature == GroupFeature.TimedMessages && enableFeature.value == GroupFeatureEnabled.ON if (groupInfo.isOwner) { PreferenceToggleWithIcon( - feature.text, + feature.text(groupInfo.isChannel), icon, iconTint, disabled = disabled, @@ -299,7 +299,7 @@ private fun FeatureSection( } } else { InfoRow( - feature.text, + feature.text(groupInfo.isChannel), enableFeature.value.text, icon = icon, iconTint = iconTint, @@ -317,7 +317,7 @@ private fun FeatureSection( onSelected(enableFeature.value, null) } } - SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner)) + SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner, groupInfo.isChannel)) if (notice != null) { SectionTextFooter(notice) } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 4bd9d9b96f..ade88f7fc8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2345,6 +2345,27 @@ Chats with admins in public channels have no E2E encryption - use only with trusted chat relays. Enable chats with admins? Enable + + + Subscriber reports + Allow sending direct messages to subscribers. + Prohibit sending direct messages to subscribers. + Send up to 100 last messages to new subscribers. + Do not send history to new subscribers. + Subscribers can send disappearing messages. + Subscribers can send direct messages. + Direct messages between subscribers are prohibited. + Subscribers can irreversibly delete sent messages. (24 hours) + Subscribers can add message reactions. + Subscribers can send voice messages. + Subscribers can send files and media. + Subscribers can send SimpleX links. + Subscribers can report messsages to moderators. + Up to 100 last messages are sent to new subscribers. + History is not sent to new subscribers. + Allow subscribers to chat with admins. + Subscribers can chat with admins. + Delete after %d sec %ds diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 43340f85dc..251987f658 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -2821,4 +2821,22 @@ Diese Adresse in Ihrem Social‑Media‑Profil, auf Ihrer Webseite oder in Ihrer E‑Mail‑Signatur verwenden. Wir haben das Verbinden für neue Nutzer vereinfacht. Ihre öffentliche Adresse + + + Abonnenten-Meldungen + Das Senden von Direktnachrichten an Abonnenten erlauben. + Das Senden von Direktnachrichten an Abonnenten nicht erlauben. + Bis zu 100 der letzten Nachrichten an neue Abonnenten senden. + Den Nachrichtenverlauf nicht an neue Abonnenten senden. + Abonnenten können verschwindende Nachrichten versenden. + Abonnenten können Direktnachrichten versenden. + Direktnachrichten zwischen Abonnenten sind nicht erlaubt. + Abonnenten können gesendete Nachrichten unwiederbringlich löschen. (24 Stunden) + Abonnenten können eine Reaktion auf Nachrichten geben. + Abonnenten können Sprachnachrichten versenden. + Abonnenten können Dateien und Medien versenden. + Abonnenten können SimpleX-Links versenden. + Abonnenten können Nachrichten an Moderatoren melden. + Bis zu 100 der letzten Nachrichten werden an neue Abonnenten gesendet. + Der Nachrichtenverlauf wird nicht an neue Abonnenten gesendet. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 4e372dc635..4fc83c8a84 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -2680,4 +2680,22 @@ Tu nombre del servidor Dejarás de recibir mensajes de este canal. El historial del chat se conservará. fallo + + + Informes de suscriptores + Se permiten mensajes directos entre suscriptores. + No se permiten mensajes directos entre suscriptores. + Se envían hasta 100 mensajes más recientes a los suscriptores nuevos. + No se envía el historial a los suscriptores nuevos. + Los suscriptores del canal pueden enviar mensajes temporales. + Los suscriptores del canal pueden enviar mensajes directos. + Los mensajes directos entre suscriptores del canal no están permitidos. + Los suscriptores del canal pueden eliminar mensajes de forma irreversible. (24 horas) + Los suscriptores pueden añadir reacciones a los mensajes. + Los suscriptores del canal pueden enviar mensajes de voz. + Los suscriptores del canal pueden enviar archivos y multimedia. + Los suscriptores del canal pueden enviar enlaces SimpleX. + Los suscriptores pueden informar de mensajes a los moderadores. + Hasta 100 últimos mensajes son enviados a los suscriptores nuevos. + El historial no se envía a suscriptores nuevos. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index c83aa77b0e..c706dc8e8a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -2714,4 +2714,22 @@ Biztonság: a csatornák kulcsait a tulajdonosok őrzik. A SimpleX hálózat hosszú távú működésének biztosítása érdekében. Az új felhasználók számára egyszerűbbé tettük a kapcsolatok létrehozását. + + + Feliratkozók jelentései + A közvetlen üzenetek küldése a feliratkozók között engedélyezve van. + A közvetlen üzenetek küldése a feliratkozók között le van tiltva. + Legfeljebb az utolsó 100 üzenet elküldése az új feliratkozók számára. + Az előzmények ne legyenek elküldve az új feliratkozók számára. + A feliratkozók küldhetnek eltűnő üzeneteket. + A feliratkozók küldhetnek egymásnak közvetlen üzeneteket. + A feliratkozók közötti közvetlen üzenetek le vannak tiltva. + A feliratkozók véglegesen törölhetik az elküldött üzeneteiket. (24 óra) + A feliratkozók reakciókat adhatnak hozzá az üzenetekhez. + A feliratkozók küldhetnek hangüzeneteket. + A feliratkozók küldhetnek fájlokat és médiatartalmakat. + A feliratkozók küldhetnek SimpleX-hivatkozásokat. + A feliratkozók jelenthetik az üzeneteket a moderátorok felé. + Legfeljebb az utolsó 100 üzenet lesz elküldve az új feliratkozók számára. + Az előzmények nem lesznek elküldve az új feliratkozók számára. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 201b7c4ea5..d65dc3b128 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -2750,4 +2750,22 @@ Link web sicuri Sicurezza: solo i proprietari hanno le chiavi del canale. Abbiamo semplificato la connessione per i nuovi utenti. + + + Segnalazioni degli iscritti + Permetti l\'invio di messaggi diretti agli iscritti. + Proibisci l\'invio di messaggi diretti agli iscritti. + Invia fino a 100 ultimi messaggi ai nuovi iscritti. + Non inviare la cronologia ai nuovi iscritti. + Gli iscritti al canale possono inviare messaggi a tempo. + Gli iscritti al canale possono inviare messaggi diretti. + I messaggi diretti tra gli iscritti sono vietati. + Gli iscritti al canale possono eliminare irreversibilmente i messaggi inviati. (24 ore) + Gli iscritti al canale possono aggiungere reazioni ai messaggi. + Gli iscritti al canale possono inviare messaggi vocali. + Gli iscritti al canale possono inviare file e contenuti multimediali. + Gli iscritti al canale possono inviare link di Simplex. + Gli iscritti possono segnalare messaggi ai moderatori. + Vengono inviati ai nuovi iscritti fino a 100 ultimi messaggi. + La cronologia non viene inviata ai nuovi iscritti. From 0b5af7694a985792291bcf1c039b4f3d9e0a8db9 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 26 Apr 2026 21:20:09 +0100 Subject: [PATCH 25/77] android, desktop: change httpd libraries reference --- apps/multiplatform/common/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 65f0acd86c..c3a4beb823 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -142,8 +142,8 @@ kotlin { implementation("org.slf4j:slf4j-simple:2.0.12") implementation("uk.co.caprica:vlcj:4.8.3") implementation("net.java.dev.jna:jna:5.14.0") - implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf") - implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf") + implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") + implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") implementation("com.squareup.okhttp3:okhttp:4.12.0") } } From 896fca7bef8d3d9d2ec5dd9e74727767c5083ccf Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 27 Apr 2026 00:27:16 +0100 Subject: [PATCH 26/77] core: 6.5.0.17 --- simplex-chat.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplex-chat.cabal b/simplex-chat.cabal index e777c6eeba..0fdcab89ab 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.5.0.16 +version: 6.5.0.17 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From b894243f43f7bb3fe9698b9529b9368500198a8d Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 27 Apr 2026 07:31:04 +0100 Subject: [PATCH 27/77] blog: update release date --- .../Views/Onboarding/WhatsNewView.swift | 2 +- .../common/views/onboarding/WhatsNewView.kt | 2 +- ...nsortium-crowdfunding-freedom-of-speech.md | 4 +- ...nsortium-crowdfunding-freedom-of-speech.md | 38 +++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 8572022ceb..41a342d7c8 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -634,7 +634,7 @@ private let versionDescriptions: [VersionDescription] = [ ), VersionDescription( version: "v6.5", - post: URL(string: "https://simplex.chat/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html"), + post: URL(string: "https://simplex.chat/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html"), features: [ .feature(Description( icon: nil, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 0edeee61af..e1415d071d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -882,7 +882,7 @@ private val versionDescriptions: List = listOf( ), VersionDescription( version = "v6.5", - post = "https://simplex.chat/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html", + post = "https://simplex.chat/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html", features = listOf( VersionFeature.FeatureDescription( icon = null, diff --git a/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md b/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md index 39616304dd..a82aa3ccaa 100644 --- a/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md +++ b/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md @@ -1,7 +1,7 @@ --- layout: layouts/article.html title: "SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding - to Preserve Freedom of Speech" -date: 2026-04-28 +date: 2026-04-30 # previewBody: blog_previews/20260421.html # image: images/20260421-channel.png # imageBottom: true @@ -11,7 +11,7 @@ permalink: "/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom # SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding - to Preserve Freedom of Speech -**To be published:** Apr 28, 2026 +**To be published:** Apr 30, 2026 This is a permalink for a blog post about: diff --git a/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md b/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md new file mode 100644 index 0000000000..6c6b7bf6ea --- /dev/null +++ b/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.md @@ -0,0 +1,38 @@ +--- +layout: layouts/article.html +title: "SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding - to Preserve Freedom of Speech" +date: 2026-04-30 +# previewBody: blog_previews/20260421.html +# image: images/20260421-channel.png +# imageBottom: true +draft: true +permalink: "/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html" +--- + +# SimpleX Channels, SimpleX Network Consortium and Community Crowdfunding - to Preserve Freedom of Speech + +**To be published:** Apr 30, 2026 + +This is a permalink for a blog post about: + +- SimpleX Channels - a new model for online publishing that preserves participation privacy, protecting both user and network operators. It is being released in v6.5 +- SimpleX Network Consortium - a cross-jurisdictional governance and licensing structure to ensure long term availability and sustainability of SimpleX Network. +- Testing the water for community crowdfunding under Reg CF. + +## SimpleX Channels - more public, more freedom, more private + +TODO + +## SimpleX Network Consortium - to govern SimpleX Network + +TODO + +## Community Crowdfunding + +TODO + +*Register your interest* to participate in crowdfunding here: https://simplexchat.typeform.com/crowdfunding + +Join the channel for updates here: https://smp4.simplex.im/g#g6pdBGlLoeOwqYmbmyvRye8EBiFd2inNUzKc87Pt3y4 + +_Disclaimer: SimpleX Chat is testing the waters for a possible Reg CF offering. We’re not asking for or accepting any money right now, and we won’t accept any if sent. We can’t accept any offers to buy securities or take any payments until the official filing is done and it’s live through a regulated platform. Our testing the waters and your possible indications of interest doesn’t create any obligation or commitment of any kind._ From 5a3dfdd2b440382868cf70e56057d18848c51e4c Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Mon, 27 Apr 2026 08:12:42 +0000 Subject: [PATCH 28/77] SimpleX support bot (#6625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * plans: 20260207-support-bot.md * Update 20260207-support-bot.md * plans: 20260207-support-bot-implementation.md * plans: Update 20260207-support-bot-implementation.md * Relocate plans * apps: support bot code & tests * apps: support bot relocate * support-bot: Fix basic functionality * apps: support-bot /add command & fixes * apps: simplex-support-bot: Change Grok logo * Further usability improvements * simplex-support-bot: Update support plan to reflect current flow * simplex-support-bot: update product design plan * support-bot: update plan * support-bot: review and refine product spec * support-bot: update product spec — complete state, /join team-only, card debouncing - Group preferences applied once at creation, not on every startup - /join restricted to team group only - Team/Grok reply or reaction auto-completes conversation (✅) - Customer message reverts to incomplete - Card updates debounced globally with 15-minute batch flush * support-bot: update implementation plan * support-bot: implement stateless bot with cards, Grok, team flow, hardening Complete rewrite of the support bot to stateless architecture: - State derived from group composition + chat history (survives restarts) - Card dashboard in team group with live status, preview, /join commands - Two-profile architecture (main + Grok) with profileMutex serialization - Grok join race condition fix via bufferedGrokInvitations - Card preview: newest-first truncation, newline sanitization, sender prefixes - Best-effort startup (invite link, group profile update) - Team group preferences: directMessages, fullDelete, commands - 122 tests across 27 suites * support-bot: use apiCreateMemberContact and apiSendMemberContactInvitation instead of raw commands Replace sendChatCmd("/_create member contact ...") and sendChatCmd("/_invite member contact ...") with the typed API methods added in simplex-chat-nodejs. Update plans and build script accordingly. * plans: 20260207-support-bot.md * Update 20260207-support-bot.md * plans: 20260207-support-bot-implementation.md * plans: Update 20260207-support-bot-implementation.md * Relocate plans * apps: support bot code & tests * apps: support bot relocate * support-bot: Fix basic functionality * apps: support-bot /add command & fixes * apps: simplex-support-bot: Change Grok logo * Further usability improvements * simplex-support-bot: Update support plan to reflect current flow * simplex-support-bot: update product design plan * support-bot: update plan * support-bot: review and refine product spec * support-bot: update product spec — complete state, /join team-only, card debouncing - Group preferences applied once at creation, not on every startup - /join restricted to team group only - Team/Grok reply or reaction auto-completes conversation (✅) - Customer message reverts to incomplete - Card updates debounced globally with 15-minute batch flush * support-bot: update implementation plan * support-bot: implement stateless bot with cards, Grok, team flow, hardening Complete rewrite of the support bot to stateless architecture: - State derived from group composition + chat history (survives restarts) - Card dashboard in team group with live status, preview, /join commands - Two-profile architecture (main + Grok) with profileMutex serialization - Grok join race condition fix via bufferedGrokInvitations - Card preview: newest-first truncation, newline sanitization, sender prefixes - Best-effort startup (invite link, group profile update) - Team group preferences: directMessages, fullDelete, commands - 122 tests across 27 suites * support-bot: use apiCreateMemberContact and apiSendMemberContactInvitation instead of raw commands Replace sendChatCmd("/_create member contact ...") and sendChatCmd("/_invite member contact ...") with the typed API methods added in simplex-chat-nodejs. Update plans and build script accordingly. * support-bot: more improvemets * support-bot: add tests for Grok batch dedup and initial response gating 7 new tests covering the duplicate Grok reply fix: - batch dedup: only last customer message per group triggers API call - batch dedup: multi-group batches handled independently - batch dedup: non-customer messages filtered from batch - initial response gating: per-message responses suppressed during activateGrok - gating clears: per-message responses resume after activation completes Update implementation plan test catalog (122 → 129 tests). * support-bot: load context from context file * Rename Grok AI -> Grok * Remove unused strings.ts * support-bot: change messages * cardFlushMinutes 15 -> cardFlushSeconds 300 * support-bot: /team message when grok present * support-bot: correct messages * support-bot: update plans to reflect latest changes * Update plan for state derivation * support-bot: Update state machine plans * support-bot: implement customData state * Fix Grok revertStateOnFail race condition * support-bot: plans adversarial review * support-bot: /join ID part of card in plan * support-bot: implement /join ID inside card * support-bot: plans use params instead of regex in /join * support-bot: Implement adversarial review changes * support-bot: no re-invite if already invited * support-bot: /team should give owner to invited member * Don't change username for existing database * support-bot: update bot commands before sending commands * support-bot: adversarial review fixes * support-bot: implement postgresql (#6876) * support-bot: sqlite/postgres backend via typed DbConfig and parseArgs flags * support-bot: add README with setup and flags reference * support-bot: use published simplex-chat, drop build.sh/start.sh * support-bot: switch CLI to commander, add --help * support-bot: update README --------- Co-authored-by: shum Co-authored-by: sh <37271604+shumvgolove@users.noreply.github.com> --- apps/simplex-support-bot/README.md | 101 + apps/simplex-support-bot/bot.test.ts | 2481 +++++++++++++++++ apps/simplex-support-bot/package-lock.json | 2061 ++++++++++++++ apps/simplex-support-bot/package.json | 23 + .../20260207-support-bot-implementation.md | 1471 ++++++++++ .../plans/20260207-support-bot.md | 513 ++++ apps/simplex-support-bot/src/bot.ts | 920 ++++++ apps/simplex-support-bot/src/cards.ts | 473 ++++ apps/simplex-support-bot/src/config.ts | 144 + apps/simplex-support-bot/src/grok.ts | 55 + apps/simplex-support-bot/src/index.ts | 372 +++ apps/simplex-support-bot/src/messages.ts | 43 + apps/simplex-support-bot/src/util.ts | 22 + .../test/__mocks__/simplex-chat-types.js | 12 + .../test/__mocks__/simplex-chat.js | 26 + apps/simplex-support-bot/tsconfig.json | 23 + apps/simplex-support-bot/vitest.config.ts | 22 + 17 files changed, 8762 insertions(+) create mode 100644 apps/simplex-support-bot/README.md create mode 100644 apps/simplex-support-bot/bot.test.ts create mode 100644 apps/simplex-support-bot/package-lock.json create mode 100644 apps/simplex-support-bot/package.json create mode 100644 apps/simplex-support-bot/plans/20260207-support-bot-implementation.md create mode 100644 apps/simplex-support-bot/plans/20260207-support-bot.md create mode 100644 apps/simplex-support-bot/src/bot.ts create mode 100644 apps/simplex-support-bot/src/cards.ts create mode 100644 apps/simplex-support-bot/src/config.ts create mode 100644 apps/simplex-support-bot/src/grok.ts create mode 100644 apps/simplex-support-bot/src/index.ts create mode 100644 apps/simplex-support-bot/src/messages.ts create mode 100644 apps/simplex-support-bot/src/util.ts create mode 100644 apps/simplex-support-bot/test/__mocks__/simplex-chat-types.js create mode 100644 apps/simplex-support-bot/test/__mocks__/simplex-chat.js create mode 100644 apps/simplex-support-bot/tsconfig.json create mode 100644 apps/simplex-support-bot/vitest.config.ts diff --git a/apps/simplex-support-bot/README.md b/apps/simplex-support-bot/README.md new file mode 100644 index 0000000000..19b9ab8bd6 --- /dev/null +++ b/apps/simplex-support-bot/README.md @@ -0,0 +1,101 @@ +# SimpleX Support Bot + +A business-address bot that triages incoming support chats, optionally runs them through Grok, and routes handoffs to a team group. + +## Prerequisites + +- Node.js v18 or newer (v24 tested) +- `GROK_API_KEY` env var (xAI) — optional; the bot runs without it +- For the PostgreSQL backend: Linux x86_64, `libpq5` installed on the host, and a reachable PostgreSQL server + +## Install & build + +```bash +cd apps/simplex-support-bot +npm install # downloads native libs + transitive deps +npm run build # tsc +``` + +By default this installs the **SQLite** backend. + +To use **PostgreSQL** instead, drop a `.npmrc` next to `package.json` *before* `npm install`: + +```bash +echo 'simplex_backend=postgres' > .npmrc +npm install # now pulls postgres-flavored native libs +npm run build +``` + +`.npmrc` lives next to the package — npm reads it natively, no extra setup. + +### Switching backends + +`npm install` is a no-op for already-installed deps, so editing `.npmrc` and re-running `npm install` will *not* re-trigger `simplex-chat`'s preinstall. To switch backends, force a clean install: + +```bash +rm -rf node_modules +npm install # download-libs.js re-runs and pulls the right native lib +``` + +## Run + +```bash +mkdir -p data # state file lives here by default + +# SQLite (default) +npm start -- --team-group "Support Team" + +# PostgreSQL +npm start -- --team-group "Support Team" \ + --pg-conn "postgres://user:pass@host/db" +``` + +The bot runs via `npm start` so npm can expose `.npmrc` settings to the process — `detectBackend()` reads `npm_config_simplex_backend` to know which backend was installed. + +## Flags + +Run `npm start -- --help` for the auto-generated reference. Summary: + +| Flag | Backend | Required | Default | Description | +|---|---|---|---|---| +| `--team-group` | both | yes | — | team group display name | +| `--state-file` | both | no | `./data/state.json` | path to bot state JSON | +| `--sqlite-file-prefix` | sqlite | no | `./data/simplex` | DB file prefix (creates `_chat.db`, `_agent.db`) | +| `--sqlite-key` | sqlite | no | (unencrypted) | SQLCipher encryption key | +| `--pg-conn` | postgres | yes | — | PostgreSQL connection string | +| `--pg-schema` | postgres | no | `simplex_v1` | schema prefix used for bot tables | +| `-a` / `--auto-add-team-members` | both | no | | comma-separated `ID:name` pairs (e.g. `1:Alice,2:Bob`) | +| `--timezone` | both | no | `UTC` | IANA zone for weekend detection | +| `--complete-hours` | both | no | `3` | auto-complete chats after N hours idle (`0` disables) | +| `--card-flush-seconds` | both | no | `300` | debounce card state writes | +| `--context-file` | both | required with `GROK_API_KEY` | | text file with Grok system context | +| `-h` / `--help` | both | no | | show usage and exit | + +## Environment variables + +| Var | Purpose | +|---|---| +| `GROK_API_KEY` | xAI API key; enables Grok replies | +| `SIMPLEX_BACKEND` | alternative to `.npmrc` for selecting the install backend (`sqlite` or `postgres`) | + +## Local development against unreleased lib changes + +This package depends on `simplex-chat` from npm. To test against an in-tree version: + +```bash +# In packages/simplex-chat-nodejs +npm link + +# In apps/simplex-support-bot +npm link simplex-chat +``` + +`npm unlink simplex-chat && npm install` reverts to the registry version. + +## Troubleshooting + +- **`--pg-conn is required when backend is postgres`** — the postgres backend is installed but you didn't pass a connection string. +- **`libpq5` errors at startup** — install `libpq5` on the host (`apt install libpq5` on Debian/Ubuntu). +- **`ENOENT: no such file or directory, open './data/state.json'`** — the parent directory of `--state-file` must exist; `mkdir -p data` before starting. +- **Wrong backend installed** — check `node_modules/simplex-chat/libs/installed.txt`. Edit `.npmrc`, then `rm -rf node_modules && npm install` to switch (`npm install` alone won't re-run the dep's preinstall). +- **`libpq` connection error** at startup with sqlite-flavored config (or vice versa) — `.npmrc` was changed but libs weren't reinstalled. See "Switching backends" above. diff --git a/apps/simplex-support-bot/bot.test.ts b/apps/simplex-support-bot/bot.test.ts new file mode 100644 index 0000000000..d390835f46 --- /dev/null +++ b/apps/simplex-support-bot/bot.test.ts @@ -0,0 +1,2481 @@ +import {describe, test, expect, beforeEach, vi} from "vitest" +import {SupportBot} from "./src/bot.js" +import {CardManager} from "./src/cards.js" +import {parseConfig} from "./src/config.js" +import {GrokApiClient} from "./src/grok.js" +import {welcomeMessage, queueMessage, grokActivatedMessage, teamLockedMessage} from "./src/messages.js" + +// Silence console output during tests +vi.spyOn(console, "log").mockImplementation(() => {}) +vi.spyOn(console, "error").mockImplementation(() => {}) + +// ─── Type stubs ─── + +const ChatType = {Direct: "direct" as const, Group: "group" as const, Local: "local" as const} +const GroupMemberRole = {Member: "member" as const, Owner: "owner" as const, Admin: "admin" as const} +const GroupMemberStatus = {Connected: "connected" as const, Complete: "complete" as const, Announced: "announced" as const, Left: "left" as const} +const GroupFeatureEnabled = {On: "on" as const, Off: "off" as const} +const CIDeleteMode = {Broadcast: "broadcast" as const} + +// ─── Mock infrastructure ─── + +let nextItemId = 1000 + +class MockChatApi { + sent: {chat: [string, number]; text: string}[] = [] + added: {groupId: number; contactId: number; role: string}[] = [] + removed: {groupId: number; memberIds: number[]}[] = [] + joined: number[] = [] + deleted: {chatType: string; chatId: number; itemIds: number[]; mode: string}[] = [] + customData = new Map() + roleChanges: {groupId: number; memberIds: number[]; role: string}[] = [] + profileUpdates: {groupId: number; profile: any}[] = [] + + members = new Map() + chatItems = new Map() + groups = new Map() + activeUserId = 1 + + private _addMemberFails = false + private _addMemberError: any = null + private _deleteChatItemsFails = false + + apiAddMemberWillFail(err?: any) { this._addMemberFails = true; this._addMemberError = err } + apiDeleteChatItemsWillFail() { this._deleteChatItemsFails = true } + + async apiSetActiveUser(userId: number) { this.activeUserId = userId; return {userId, profile: {displayName: "test"}} } + async apiSendMessages(chatRef: any, messages: any[]) { + // Normalize chat ref: accept both [type, id] tuples and {chatType, chatId} objects + const chat: [string, number] = Array.isArray(chatRef) + ? chatRef + : [chatRef.chatType, chatRef.chatId] + return messages.map(msg => { + const text = msg.msgContent?.text || "" + this.sent.push({chat, text}) + const itemId = nextItemId++ + return {chatItem: {meta: {itemId}, chatDir: {type: "groupSnd"}, content: {type: "sndMsgContent", msgContent: {type: "text", text}}}} + }) + } + async apiSendTextMessage(chat: [string, number], text: string) { + return this.apiSendMessages(chat, [{msgContent: {type: "text", text}, mentions: {}}]) + } + async apiAddMember(groupId: number, contactId: number, role: string) { + if (this._addMemberFails) { + this._addMemberFails = false + throw this._addMemberError || new Error("apiAddMember failed") + } + this.added.push({groupId, contactId, role}) + const memberId = `member-${contactId}` + const groupMemberId = 5000 + contactId + return {memberId, groupMemberId, memberContactId: contactId, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: `Contact${contactId}`}} + } + async apiRemoveMembers(groupId: number, memberIds: number[]) { + this.removed.push({groupId, memberIds}) + return memberIds.map(id => ({groupMemberId: id})) + } + async apiJoinGroup(groupId: number) { + this.joined.push(groupId) + return {groupId} + } + async apiSetMembersRole(groupId: number, memberIds: number[], role: string) { + this.roleChanges.push({groupId, memberIds, role}) + } + async apiListMembers(groupId: number) { + return this.members.get(groupId) || [] + } + async apiGetChat(_chatType: string, chatId: number, _count: number) { + const items = this.chatItems.get(chatId) || [] + const groupInfo = this.groups.get(chatId) + return { + chatInfo: {type: "group", groupInfo: groupInfo || makeGroupInfo(chatId)}, + chatItems: items, + chatStats: {unreadCount: 0, unreadMentions: 0, reportsCount: 0, minUnreadItemId: 0, unreadChat: false}, + } + } + async apiListGroups(_userId: number) { + return [...this.groups.values()].map(g => ({...g, customData: this.customData.get(g.groupId)})) + } + async apiSetGroupCustomData(groupId: number, data?: any) { + if (data === undefined) this.customData.delete(groupId) + else this.customData.set(groupId, data) + } + async apiDeleteChatItems(chatType: string, chatId: number, itemIds: number[], mode: string) { + if (this._deleteChatItemsFails) { + this._deleteChatItemsFails = false + throw new Error("apiDeleteChatItems failed") + } + this.deleted.push({chatType, chatId, itemIds, mode}) + return [] + } + async apiUpdateGroupProfile(groupId: number, profile: any) { + this.profileUpdates.push({groupId, profile}) + return this.groups.get(groupId) || makeGroupInfo(groupId) + } + + memberContacts: {groupId: number; groupMemberId: number; contactId: number}[] = [] + memberContactInvitations: {contactId: number; text: string}[] = [] + + async apiCreateMemberContact(groupId: number, groupMemberId: number): Promise { + const contactId = nextItemId++ + this.memberContacts.push({groupId, groupMemberId, contactId}) + return {contactId, profile: {displayName: "member"}} + } + async apiSendMemberContactInvitation(contactId: number, message?: any): Promise { + const text = typeof message === "string" ? message : (message?.text ?? "") + this.memberContactInvitations.push({contactId, text}) + this.sent.push({chat: [ChatType.Direct, contactId], text}) + return {contactId, profile: {displayName: "member"}} + } + + rawCmds: string[] = [] + async sendChatCmd(cmd: string) { + this.rawCmds.push(cmd) + return {type: "cmdOk"} + } + + sentTo(groupId: number): string[] { + return this.sent.filter(s => s.chat[0] === ChatType.Group && s.chat[1] === groupId).map(s => s.text) + } + lastSentTo(groupId: number): string | undefined { + const msgs = this.sentTo(groupId) + return msgs[msgs.length - 1] + } + sentDirect(contactId: number): string[] { + return this.sent.filter(s => s.chat[0] === ChatType.Direct && s.chat[1] === contactId).map(s => s.text) + } +} + +class MockGrokApi { + calls: {history: any[]; message: string}[] = [] + private _response = "Grok answer" + private _willFail = false + private _gate: {promise: Promise; release: () => void} | null = null + + willRespond(text: string) { this._response = text; this._willFail = false } + willFail() { this._willFail = true } + + // Block every subsequent chat() call until releaseChat() is invoked. Used to + // observe in-flight concurrency without relying on wall-clock timing. + blockChat() { + let release!: () => void + const promise = new Promise(r => { release = r }) + this._gate = {promise, release} + } + releaseChat() { + this._gate?.release() + this._gate = null + } + + async chat(history: any[], userMessage: string): Promise { + this.calls.push({history, message: userMessage}) + if (this._gate) await this._gate.promise + if (this._willFail) { this._willFail = false; throw new Error("Grok API error") } + return this._response + } +} + +// ─── Factory helpers ─── + +const MAIN_USER_ID = 1 +const GROK_USER_ID = 2 +const TEAM_GROUP_ID = 50 +const CUSTOMER_GROUP_ID = 100 +const GROK_CONTACT_ID = 10 +const TEAM_MEMBER_1_ID = 20 +const TEAM_MEMBER_2_ID = 21 +const GROK_LOCAL_GROUP_ID = 200 +const CUSTOMER_ID = "customer-1" + +// Commands passed into SupportBot; matches what index.ts constructs when +// Grok is enabled. Tests that disable grokApi still pass the full list +// because the ctor doesn't care; the value is pushed to a group's +// groupPreferences on the first sendToGroup() call. +const DESIRED_COMMANDS = [ + {type: "command" as const, keyword: "grok", label: "Ask Grok"}, + {type: "command" as const, keyword: "team", label: "Switch to team"}, +] + +// ─── Member factories ─── + +function makeTeamMember(contactId: number, name = `Contact${contactId}`, groupMemberId?: number) { + return { + memberId: `team-${contactId}`, + groupMemberId: groupMemberId ?? 5000 + contactId, + memberContactId: contactId, + memberStatus: GroupMemberStatus.Connected, + memberProfile: {displayName: name}, + } +} + +function makeGrokMember(groupMemberId = 7777) { + return { + memberId: "grok-member", + groupMemberId, + memberContactId: GROK_CONTACT_ID, + memberStatus: GroupMemberStatus.Connected, + memberProfile: {displayName: "Grok"}, + } +} + +function makeCustomerMember(status = GroupMemberStatus.Connected) { + return { + memberId: CUSTOMER_ID, + groupMemberId: 3000, + memberStatus: status, + memberProfile: {displayName: "Customer"}, + } +} + +function makeConfig(overrides: Partial = {}) { + return { + stateFile: "./test-data/state.json", + db: {type: "sqlite", filePrefix: "./test-data/simplex"}, + teamGroup: {id: TEAM_GROUP_ID, name: "SupportTeam"}, + teamMembers: [ + {id: TEAM_MEMBER_1_ID, name: "Alice"}, + {id: TEAM_MEMBER_2_ID, name: "Bob"}, + ], + groupLinks: "", + timezone: "UTC", + completeHours: 3, + cardFlushSeconds: 300, + grokApiKey: "test-key", + grokContactId: GROK_CONTACT_ID as number | null, + ...overrides, + } +} + +function makeGroupInfo(groupId: number, opts: Partial = {}): any { + return { + groupId, + groupProfile: {displayName: opts.displayName || `Group${groupId}`, fullName: ""}, + businessChat: opts.businessChat !== undefined ? opts.businessChat : { + chatType: "business", + businessId: "bot-1", + customerId: opts.customerId || CUSTOMER_ID, + }, + membership: {memberId: "bot-member"}, + customData: opts.customData, + chatSettings: {enableNtfs: "all", favorite: false}, + fullGroupPreferences: {}, + localDisplayName: `group-${groupId}`, + localAlias: "", + useRelays: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + chatTags: [], + groupSummary: {}, + membersRequireAttention: 0, + } +} + +function makeUser(userId: number) { + return {userId, profile: {displayName: userId === MAIN_USER_ID ? "Ask SimpleX Team" : "Grok"}} +} + +function makeChatItem(opts: { + dir: "groupSnd" | "groupRcv" | "directRcv" + text?: string + memberId?: string + memberContactId?: number + memberDisplayName?: string + msgType?: string + groupId?: number +}): any { + const itemId = nextItemId++ + const now = new Date().toISOString() + const msgContent = opts.msgType + ? {type: opts.msgType, text: opts.text || ""} + : {type: "text", text: opts.text || ""} + + let chatDir: any + if (opts.dir === "groupSnd") { + chatDir = {type: "groupSnd"} + } else if (opts.dir === "groupRcv") { + chatDir = { + type: "groupRcv", + groupMember: { + memberId: opts.memberId || CUSTOMER_ID, + groupMemberId: 3000, + memberContactId: opts.memberContactId, + memberStatus: GroupMemberStatus.Connected, + memberProfile: {displayName: opts.memberDisplayName || "Customer"}, + }, + } + } else { + chatDir = {type: "directRcv"} + } + + return { + chatDir, + meta: {itemId, itemTs: now, createdAt: now, itemText: opts.text || "", itemStatus: {type: "sndSent"}, itemEdited: false}, + content: {type: opts.dir === "groupSnd" ? "sndMsgContent" : "rcvMsgContent", msgContent}, + mentions: {}, + reactions: [], + } +} + +function makeAChatItem(chatItem: any, groupId = CUSTOMER_GROUP_ID): any { + return { + chatInfo: {type: "group", groupInfo: makeGroupInfo(groupId)}, + chatItem, + } +} + +function makeDirectAChatItem(chatItem: any, contactId: number): any { + return { + chatInfo: {type: "direct", contact: {contactId, profile: {displayName: "Someone"}}}, + chatItem, + } +} + +// ─── Shared test state ─── + +let chat: MockChatApi +let grokApi: MockGrokApi +let config: ReturnType +let bot: InstanceType +let cards: InstanceType + +// ─── Setup and helpers ─── + +function setup(configOverrides: Partial = {}) { + nextItemId = 1000 + chat = new MockChatApi() + grokApi = new MockGrokApi() + config = makeConfig(configOverrides) + + // Register team group and customer group in mock + const teamGroupInfo = makeGroupInfo(TEAM_GROUP_ID, {businessChat: null, displayName: "SupportTeam"}) + chat.groups.set(TEAM_GROUP_ID, teamGroupInfo) + chat.groups.set(CUSTOMER_GROUP_ID, makeGroupInfo(CUSTOMER_GROUP_ID)) + + cards = new CardManager(chat as any, config as any, MAIN_USER_ID, 999999999) + bot = new SupportBot(chat as any, grokApi as any, config as any, MAIN_USER_ID, GROK_USER_ID, DESIRED_COMMANDS) + // Replace cards with our constructed one that has a long flush interval + bot.cards = cards +} + +function customerMessage(text: string, groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: CUSTOMER_ID}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function customerNonTextMessage(groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text: "", memberId: CUSTOMER_ID, msgType: "image"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function teamMemberMessage(text: string, contactId = TEAM_MEMBER_1_ID, groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: `team-${contactId}`, memberContactId: contactId, memberDisplayName: "Alice"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function grokResponseMessage(text: string, groupId = CUSTOMER_GROUP_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: "grok-member", memberContactId: GROK_CONTACT_ID, memberDisplayName: "Grok"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci, groupId)], + } +} + +function directMessage(text: string, contactId: number): any { + const ci = makeChatItem({dir: "directRcv", text}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeDirectAChatItem(ci, contactId)], + } +} + +function teamGroupMessage(text: string, senderContactId = TEAM_MEMBER_1_ID): any { + const ci = makeChatItem({dir: "groupRcv", text, memberId: `team-${senderContactId}`, memberContactId: senderContactId, memberDisplayName: "Alice"}) + return { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(TEAM_GROUP_ID, {businessChat: null})}, chatItem: ci}], + } +} + +// Simulate bot sending a message to the customer group (adds it to chatItems history) +function addBotMessage(text: string, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupSnd", text}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +function addCustomerMessageToHistory(text: string, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupRcv", text, memberId: CUSTOMER_ID}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +function addTeamMemberMessageToHistory(text: string, contactId = TEAM_MEMBER_1_ID, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupRcv", text, memberId: `team-${contactId}`, memberContactId: contactId}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +function addGrokMessageToHistory(text: string, groupId = CUSTOMER_GROUP_ID) { + const ci = makeChatItem({dir: "groupRcv", text, memberId: "grok-member", memberContactId: GROK_CONTACT_ID}) + const items = chat.chatItems.get(groupId) || [] + items.push(ci) + chat.chatItems.set(groupId, items) +} + +// State helpers — reach specific states +async function reachQueue(groupId = CUSTOMER_GROUP_ID) { + await bot.onNewChatItems(customerMessage("Hello, I need help", groupId)) + // This should have sent queue message + created card +} + +async function reachGrok(groupId = CUSTOMER_GROUP_ID) { + await reachQueue(groupId) + // Add the queue message to history so state derivation sees it + addBotMessage("The team will reply to your message", groupId) + + // Send /grok command. This triggers activateGrok which needs the join flow. + // We need to simulate Grok join success. + const grokJoinPromise = simulateGrokJoinSuccess(groupId) + await bot.onNewChatItems(customerMessage("/grok", groupId)) + await grokJoinPromise +} + +async function simulateGrokJoinSuccess(mainGroupId = CUSTOMER_GROUP_ID) { + // Wait for apiAddMember to be called, then simulate Grok invitation + join + await new Promise(r => setTimeout(r, 10)) + // Find the pending grok join via the added members + const addedGrok = chat.added.find(a => a.contactId === GROK_CONTACT_ID && a.groupId === mainGroupId) + if (!addedGrok) return + + // Simulate Grok receivedGroupInvitation + const memberId = `member-${GROK_CONTACT_ID}` + await bot.onGrokGroupInvitation({ + type: "receivedGroupInvitation", + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + }) + + // Simulate Grok connectedToGroupMember + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999, memberContactId: undefined}, + }) +} + +async function reachTeamPending(groupId = CUSTOMER_GROUP_ID) { + await reachQueue(groupId) + addBotMessage("The team will reply to your message", groupId) + await bot.onNewChatItems(customerMessage("/team", groupId)) +} + +async function reachTeam(groupId = CUSTOMER_GROUP_ID) { + await reachTeamPending(groupId) + addBotMessage("We will reply within 24 hours.", groupId) + chat.members.set(groupId, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member sends a text message (triggers one-way gate) + addTeamMemberMessageToHistory("Hi, how can I help?", TEAM_MEMBER_1_ID, groupId) + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?", TEAM_MEMBER_1_ID, groupId)) +} + +// ─── Assertion helpers ─── + +function expectSentToGroup(groupId: number, substring: string) { + const msgs = chat.sentTo(groupId) + expect(msgs.some(m => m.includes(substring)), + `Expected message containing "${substring}" sent to group ${groupId}, got:\n${msgs.join("\n")}` + ).toBe(true) +} + +function expectNotSentToGroup(groupId: number, substring: string) { + expect(chat.sentTo(groupId).every(m => !m.includes(substring))).toBe(true) +} + +function expectDmSent(contactId: number, substring: string) { + expect(chat.sentDirect(contactId).some(m => m.includes(substring))).toBe(true) +} + +function expectAnySent(substring: string) { + expect(chat.sent.some(s => s.text.includes(substring))).toBe(true) +} + +function expectMemberAdded(groupId: number, contactId: number) { + expect(chat.added.some(a => a.groupId === groupId && a.contactId === contactId)).toBe(true) +} + +function expectCardDeleted(cardItemId: number) { + expect(chat.deleted.some(d => d.itemIds.includes(cardItemId))).toBe(true) +} + +// ─── Event factories ─── + +function connectedEvent(groupId: number, member: any, memberContact?: any) { + return { + type: "connectedToGroupMember" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {}), + member, + ...(memberContact !== undefined ? {memberContact} : {}), + } +} + +function leftEvent(groupId: number, member: any) { + return { + type: "leftMember" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {}), + member: {...member, memberStatus: GroupMemberStatus.Left}, + } +} + +function updatedEvent(groupId: number, chatItem: any, userId = MAIN_USER_ID) { + return { + type: "chatItemUpdated" as const, + user: makeUser(userId), + chatItem: { + chatInfo: {type: "group", groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {})}, + chatItem, + }, + } +} + +function reactionEvent(groupId: number, added: boolean) { + return { + type: "chatItemReaction" as const, + user: makeUser(MAIN_USER_ID), + added, + reaction: { + chatInfo: {type: "group", groupInfo: makeGroupInfo(groupId)}, + chatReaction: {reaction: {type: "emoji", emoji: "👍"}}, + }, + } +} + +function joinedEvent(groupId: number, member: any, userId = MAIN_USER_ID) { + return { + type: "joinedGroupMember" as const, + user: makeUser(userId), + groupInfo: makeGroupInfo(groupId, groupId === TEAM_GROUP_ID ? {businessChat: null} : {}), + member, + } +} + +function grokViewCustomerMessage(text: string, msgType?: string) { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + const ci = makeChatItem({dir: "groupRcv", text, memberId: CUSTOMER_ID, ...(msgType ? {msgType} : {})}) + return { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci}], + } +} + +// ═══════════════════════════════════════════════════════════ +// Tests +// ═══════════════════════════════════════════════════════════ + +describe("Welcome & First Message", () => { + beforeEach(() => setup()) + + test("first message → queue reply sent, card created in team group", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBeGreaterThan(0) + expect(teamMsgs[teamMsgs.length - 1]).toContain(`/'join ${CUSTOMER_GROUP_ID}'`) + }) + + test("non-text first message → no queue reply, no card", async () => { + await bot.onNewChatItems(customerNonTextMessage()) + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("second message → no duplicate queue reply", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + addBotMessage("The team will reply to your message") + const countBefore = chat.sentTo(CUSTOMER_GROUP_ID).filter(m => m.includes("The team will reply to your message")).length + await bot.onNewChatItems(customerMessage("Second message")) + const countAfter = chat.sentTo(CUSTOMER_GROUP_ID).filter(m => m.includes("The team will reply to your message")).length + expect(countAfter).toBe(countBefore) + }) + + test("unrecognized /command → treated as normal message", async () => { + await bot.onNewChatItems(customerMessage("/unknown")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) +}) + +describe("/grok Activation", () => { + beforeEach(() => setup()) + + test("/grok from QUEUE → Grok invited, grokActivatedMessage sent", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expectMemberAdded(CUSTOMER_GROUP_ID, GROK_CONTACT_ID) + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + }) + + test("/grok as first message → WELCOME→GROK directly, no queue message", async () => { + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("/grok in TEAM → rejected with teamLockedMessage", async () => { + await reachTeam() + await bot.onNewChatItems(customerMessage("/grok")) + expectSentToGroup(CUSTOMER_GROUP_ID, "team mode") + }) + + test("/grok when grokContactId is null → grokUnavailableMessage", async () => { + setup({grokContactId: null}) + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + }) + + test("/grok as first message + Grok join fails → queue message sent as fallback", async () => { + chat.apiAddMemberWillFail() + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) +}) + +describe("Grok Conversation", () => { + beforeEach(() => setup()) + + test("Grok per-message: reads history, calls API, sends response", async () => { + addCustomerMessageToHistory("How do I create a group?", GROK_LOCAL_GROUP_ID) + grokApi.willRespond("To create a group, tap +, then New Group.") + await bot.onGrokNewChatItems(grokViewCustomerMessage("How do I create a group?")) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("How do I create a group?") + expectAnySent("To create a group, tap +, then New Group.") + }) + + test("customer non-text in GROK → no Grok API call", async () => { + await bot.onGrokNewChatItems(grokViewCustomerMessage("", "image")) + expect(grokApi.calls.length).toBe(0) + }) + + test("Grok API error → error message in group, stays GROK", async () => { + grokApi.willFail() + await bot.onGrokNewChatItems(grokViewCustomerMessage("A question")) + expectAnySent("couldn't process that") + }) + + test("Grok ignores bot commands from customer", async () => { + await bot.onGrokNewChatItems(grokViewCustomerMessage("/team")) + expect(grokApi.calls.length).toBe(0) + }) + + test("Grok per-message: history includes prior Grok sent response as assistant", async () => { + addCustomerMessageToHistory("How do I create a group?", GROK_LOCAL_GROUP_ID) + addBotMessage("To create a group, tap + then New Group.", GROK_LOCAL_GROUP_ID) + addCustomerMessageToHistory("How do I invite members?", GROK_LOCAL_GROUP_ID) + grokApi.willRespond("Open the group and tap Invite.") + await bot.onGrokNewChatItems(grokViewCustomerMessage("How do I invite members?")) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("How do I invite members?") + expect(grokApi.calls[0].history).toEqual([ + {role: "user", content: "How do I create a group?"}, + {role: "assistant", content: "To create a group, tap + then New Group."}, + ]) + }) + + test("Grok ignores non-customer messages", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + const ci = makeChatItem({dir: "groupRcv", text: "Team message", memberId: "not-customer", memberContactId: TEAM_MEMBER_1_ID}) + const grokEvt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci}], + } + await bot.onGrokNewChatItems(grokEvt) + expect(grokApi.calls.length).toBe(0) + }) + + test("Grok ignores own messages (groupSnd)", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + const ci = makeChatItem({dir: "groupSnd", text: "My own response"}) + const grokEvt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci}], + } + await bot.onGrokNewChatItems(grokEvt) + expect(grokApi.calls.length).toBe(0) + }) + + test("batch: multiple customer messages in one event → only last triggers Grok API call", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + addCustomerMessageToHistory("First question", GROK_LOCAL_GROUP_ID) + addCustomerMessageToHistory("Second question", GROK_LOCAL_GROUP_ID) + + const ci1 = makeChatItem({dir: "groupRcv", text: "First question", memberId: CUSTOMER_ID}) + const ci2 = makeChatItem({dir: "groupRcv", text: "Second question", memberId: CUSTOMER_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci1}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: ci2}, + ], + } + + await bot.onGrokNewChatItems(evt) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("Second question") + }) + + test("batch: messages from different groups → each group gets one response", async () => { + const GROK_GROUP_A = 201 + const GROK_GROUP_B = 202 + chat.groups.set(GROK_GROUP_A, makeGroupInfo(GROK_GROUP_A)) + chat.groups.set(GROK_GROUP_B, makeGroupInfo(GROK_GROUP_B)) + addCustomerMessageToHistory("Question A", GROK_GROUP_A) + addCustomerMessageToHistory("Question B", GROK_GROUP_B) + + const ciA = makeChatItem({dir: "groupRcv", text: "Question A", memberId: CUSTOMER_ID}) + const ciB = makeChatItem({dir: "groupRcv", text: "Question B", memberId: CUSTOMER_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_A)}, chatItem: ciA}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_B)}, chatItem: ciB}, + ], + } + + await bot.onGrokNewChatItems(evt) + + expect(grokApi.calls.length).toBe(2) + }) + + test("batch: non-customer messages filtered, only customer messages trigger response", async () => { + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + addCustomerMessageToHistory("Customer question", GROK_LOCAL_GROUP_ID) + + const custCi = makeChatItem({dir: "groupRcv", text: "Customer question", memberId: CUSTOMER_ID}) + const teamCi = makeChatItem({dir: "groupRcv", text: "Team reply", memberId: "not-customer", memberContactId: TEAM_MEMBER_1_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: custCi}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID)}, chatItem: teamCi}, + ], + } + + await bot.onGrokNewChatItems(evt) + + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toBe("Customer question") + }) + + test("batch: across groups → Grok calls overlap in-flight (parallel dispatch)", async () => { + const GROK_GROUP_A = 201 + const GROK_GROUP_B = 202 + chat.groups.set(GROK_GROUP_A, makeGroupInfo(GROK_GROUP_A)) + chat.groups.set(GROK_GROUP_B, makeGroupInfo(GROK_GROUP_B)) + addCustomerMessageToHistory("A", GROK_GROUP_A) + addCustomerMessageToHistory("B", GROK_GROUP_B) + + const ciA = makeChatItem({dir: "groupRcv", text: "A", memberId: CUSTOMER_ID}) + const ciB = makeChatItem({dir: "groupRcv", text: "B", memberId: CUSTOMER_ID}) + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [ + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_A)}, chatItem: ciA}, + {chatInfo: {type: "group", groupInfo: makeGroupInfo(GROK_GROUP_B)}, chatItem: ciB}, + ], + } + + // Block both chat() calls until we release. If the handler serialized + // per-group work, only one call would enter chat() before release. + grokApi.blockChat() + const done = bot.onGrokNewChatItems(evt) + // Let both tasks run up to the gate. + await new Promise(r => setTimeout(r, 10)) + expect(grokApi.calls.length).toBe(2) + grokApi.releaseChat() + await done + }) +}) + +describe("/team Activation", () => { + beforeEach(() => setup()) + + test("/team from QUEUE → ALL team members added, teamAddedMessage sent", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/team")) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_2_ID) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + }) + + test("/team as first message → WELCOME→TEAM, no queue message", async () => { + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) + + test("/team when already activated → teamAlreadyInvitedMessage", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "already been invited") + }) + + test("/team with no team members → noTeamMembersMessage", async () => { + setup({teamMembers: []}) + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "No team members are available") + }) +}) + +describe("One-Way Gate", () => { + beforeEach(() => setup()) + + test("team member sends first TEXT → Grok removed if present", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember(), makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?")) + expect(chat.removed.some(r => r.groupId === CUSTOMER_GROUP_ID && r.memberIds.includes(7777))).toBe(true) + }) + + test("team member non-text (no ciContentText) → Grok NOT removed", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember()]) + await bot.onNewChatItems(teamMemberMessage("", TEAM_MEMBER_1_ID)) + expect(chat.removed.length).toBe(0) + }) + + test("/grok after gate → teamLockedMessage", async () => { + await reachTeam() + await bot.onNewChatItems(customerMessage("/grok")) + expectSentToGroup(CUSTOMER_GROUP_ID, "team mode") + }) + + test("customer text in TEAM → card update scheduled, no bot reply", async () => { + await reachTeam() + const sentBefore = chat.sentTo(CUSTOMER_GROUP_ID).length + await bot.onNewChatItems(customerMessage("Follow-up question")) + const sentAfter = chat.sentTo(CUSTOMER_GROUP_ID).length + expect(sentAfter).toBe(sentBefore) + }) + + test("/grok in TEAM-PENDING → invite Grok if not present", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expectMemberAdded(CUSTOMER_GROUP_ID, GROK_CONTACT_ID) + }) +}) + +describe("One-Way Gate with Grok Disabled", () => { + test("team text removes Grok even when grokApi is null", async () => { + setup() + // Recreate bot without grokApi but with grokContactId still set (simulates disabled Grok with persisted contact) + bot = new SupportBot(chat as any, null, config as any, MAIN_USER_ID, null, DESIRED_COMMANDS) + bot.cards = cards + // Reach QUEUE state with Grok + team member already present + addBotMessage("The team will reply to your message") + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember(), makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member sends text → one-way gate should fire + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?")) + expect(chat.removed.some(r => r.groupId === CUSTOMER_GROUP_ID && r.memberIds.includes(7777))).toBe(true) + }) + + test("Grok does not respond when disabled even if grokContactId is set", async () => { + setup() + bot = new SupportBot(chat as any, null, config as any, MAIN_USER_ID, null, DESIRED_COMMANDS) + bot.cards = cards + // Set up group with Grok member present + chat.members.set(CUSTOMER_GROUP_ID, [makeGrokMember()]) + addBotMessage("The team will reply to your message") + // Customer sends text in GROK state + await bot.onNewChatItems(customerMessage("How do I use SimpleX?")) + // Grok should not respond (grokApi is null) + expect(grokApi.calls.length).toBe(0) + }) +}) + +describe("Team Member Lifecycle", () => { + beforeEach(() => setup()) + + test("team member connected → promoted to Owner", async () => { + await bot.onMemberConnected(connectedEvent(CUSTOMER_GROUP_ID, makeTeamMember(TEAM_MEMBER_1_ID, "Alice"))) + expect(chat.roleChanges.some(r => r.groupId === CUSTOMER_GROUP_ID && r.memberIds.includes(5000 + TEAM_MEMBER_1_ID) && r.role === GroupMemberRole.Owner)).toBe(true) + }) + + test("/team invites team member → apiSetMembersRole(Owner) called at invite time", async () => { + await bot.onNewChatItems(customerMessage("/team")) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + expect(chat.roleChanges.some(r => + r.groupId === CUSTOMER_GROUP_ID + && r.memberIds.includes(5000 + TEAM_MEMBER_1_ID) + && r.role === GroupMemberRole.Owner + )).toBe(true) + }) + + test("/join invites team member → apiSetMembersRole(Owner) called at invite time", async () => { + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}`)) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + expect(chat.roleChanges.some(r => + r.groupId === CUSTOMER_GROUP_ID + && r.memberIds.includes(5000 + TEAM_MEMBER_1_ID) + && r.role === GroupMemberRole.Owner + )).toBe(true) + }) + + test("/team when team member already in group (any non-terminal status) → apiSetMembersRole NOT re-called", async () => { + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await cards.mergeCustomData(CUSTOMER_GROUP_ID, {state: "TEAM-PENDING"}) + chat.added.length = 0 + chat.roleChanges.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.added.length).toBe(0) + expect(chat.roleChanges.length).toBe(0) + }) + + test("/join when team member already in group → apiSetMembersRole NOT re-called", async () => { + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + chat.added.length = 0 + chat.roleChanges.length = 0 + + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}`)) + expect(chat.added.length).toBe(0) + expect(chat.roleChanges.length).toBe(0) + }) + + test("customer connected → NOT promoted to Owner", async () => { + await bot.onMemberConnected(connectedEvent(CUSTOMER_GROUP_ID, makeCustomerMember())) + expect(chat.roleChanges.length).toBe(0) + }) + + test("Grok connected → NOT promoted to Owner", async () => { + await bot.onMemberConnected(connectedEvent(CUSTOMER_GROUP_ID, makeGrokMember())) + expect(chat.roleChanges.length).toBe(0) + }) + + test("all team members leave before sending → state stays TEAM-PENDING", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + // Remove team members from the group + chat.members.set(CUSTOMER_GROUP_ID, []) + // State is authoritative and monotonic — composition changes never demote it. + // Customer is still waiting for the team's response. + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("TEAM-PENDING") + }) + + test("/team after all team members left (TEAM-PENDING, no msg sent) → re-adds members", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, []) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) + + test("/team after all team members left (TEAM, msg was sent) → re-adds members", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + addTeamMemberMessageToHistory("Hi, how can I help?", TEAM_MEMBER_1_ID) + await bot.onNewChatItems(teamMemberMessage("Hi, how can I help?")) + + // All team members leave + chat.members.set(CUSTOMER_GROUP_ID, []) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) +}) + +describe("Card Dashboard", () => { + beforeEach(() => setup()) + + test("first message creates card with /'join ' final line", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(1) + const card = teamMsgs[0] + expect(card).toContain(`/'join ${CUSTOMER_GROUP_ID}'`) + // Join command is the final line of the card + const lines = card.split("\n") + expect(lines[lines.length - 1]).toBe(`/'join ${CUSTOMER_GROUP_ID}'`) + }) + + test("card update deletes old card then posts new one", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 555}) + await cards.flush() + expect(chat.deleted.length).toBe(0) + + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + await cards.flush() + expectCardDeleted(555) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("apiDeleteChatItems failure → ignored, new card posted", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 555}) + chat.apiDeleteChatItemsWillFail() + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + await cards.flush() + // New card should still be posted despite delete failure + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("customData stores cardItemId → survives flush cycle", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + // After card creation, customData should have cardItemId + const data = chat.customData.get(CUSTOMER_GROUP_ID) + expect(data).toBeDefined() + expect(typeof data.cardItemId).toBe("number") + }) + + test("concurrent mergeCustomData on same group → both patches survive", async () => { + // Without per-group serialization, two overlapping mergeCustomData calls + // can both read the same snapshot and the second write clobbers the first + // patch. The mutex keeps them ordered so both patches land. + const GID = 500 + chat.groups.set(GID, makeGroupInfo(GID)) + + const pA = cards.mergeCustomData(GID, {state: "QUEUE"}) + const pB = cards.mergeCustomData(GID, {cardItemId: 123}) + await Promise.all([pA, pB]) + + const final = chat.customData.get(GID) + expect(final.state).toBe("QUEUE") + expect(final.cardItemId).toBe(123) + }) + + test("customer leaves → customData cleared", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 999}) + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeCustomerMember())) + expect(chat.customData.has(CUSTOMER_GROUP_ID)).toBe(false) + }) +}) + +describe("Card Debouncing", () => { + beforeEach(() => setup()) + + test("rapid events within flush interval → single card update on flush", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 500}) + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + cards.scheduleUpdate(CUSTOMER_GROUP_ID) + await cards.flush() + // Only one delete and one post + expect(chat.deleted.length).toBe(1) + // Multiple schedules → single update (one card message) + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(1) + }) + + test("multiple groups pending → each reposted once per flush", async () => { + const GROUP_A = 101 + const GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + chat.customData.set(GROUP_B, {cardItemId: 502}) + cards.scheduleUpdate(GROUP_A) + cards.scheduleUpdate(GROUP_B) + await cards.flush() + expect(chat.deleted.length).toBe(2) + }) + + test("card create is immediate (not debounced)", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + // Card should be posted immediately without flush + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("flush with no pending updates → no-op", async () => { + await cards.flush() + expect(chat.deleted.length).toBe(0) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("flush on group with no cardItemId → createCard posts a new card (retries failed create)", async () => { + // customData without cardItemId simulates a prior createCard that failed + // mid-flight and re-queued itself. flushOne must dispatch to createCard, + // not updateCard (which would early-return). + const GID = 777 + chat.groups.set(GID, makeGroupInfo(GID)) + chat.customData.set(GID, {state: "QUEUE"}) + cards.scheduleUpdate(GID) + await cards.flush() + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(1) + expect(typeof chat.customData.get(GID).cardItemId).toBe("number") + }) +}) + +describe("Card Format & State Derivation", () => { + beforeEach(() => setup()) + + test("QUEUE state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "QUEUE"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("QUEUE") + }) + + test("WELCOME state when customData.state is absent", async () => { + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("WELCOME") + }) + + test("GROK state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "GROK"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("GROK") + }) + + test("TEAM-PENDING state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "TEAM-PENDING"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("TEAM-PENDING") + }) + + test("TEAM state read from customData.state", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 1234, state: "TEAM"}) + const state = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(state).toBe("TEAM") + }) + + test("message count excludes bot's own messages", async () => { + addCustomerMessageToHistory("Hello") + addBotMessage("Queue message") + addCustomerMessageToHistory("Follow-up") + const chatResult = await cards.getChat(CUSTOMER_GROUP_ID, 100) + const nonBotCount = chatResult.chatItems.filter((ci: any) => ci.chatDir.type !== "groupSnd").length + expect(nonBotCount).toBe(2) + }) +}) + +describe("/join Command (Team Group)", () => { + beforeEach(() => setup()) + + test("/join (numeric only) → team member added to customer group", async () => { + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}`)) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) + + test("/join : (legacy form) → team member added", async () => { + await bot.onNewChatItems(teamGroupMessage(`/join ${CUSTOMER_GROUP_ID}:Customer`)) + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + }) + + test("/join validates target is business group → error if not", async () => { + const nonBizGroupId = 999 + chat.groups.set(nonBizGroupId, makeGroupInfo(nonBizGroupId, {businessChat: null})) + await bot.onNewChatItems(teamGroupMessage(`/join ${nonBizGroupId}:Test`)) + expectSentToGroup(TEAM_GROUP_ID, "not a business chat") + }) + + test("/join with non-existent groupId → error in team group", async () => { + await bot.onNewChatItems(teamGroupMessage("/join 99999:Nobody")) + expect(chat.sentTo(TEAM_GROUP_ID).some(m => m.toLowerCase().includes("error"))).toBe(true) + }) + + test("customer sending /join in customer group → treated as normal message", async () => { + await bot.onNewChatItems(customerMessage("/join 50:Test")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + }) + + test("/join with non-numeric params → error reply, no apiAddMember call", async () => { + await bot.onNewChatItems(teamGroupMessage("/join abc")) + expectSentToGroup(TEAM_GROUP_ID, `Error: invalid group id "abc"`) + expect(chat.added.length).toBe(0) + }) +}) + +describe("DM Handshake", () => { + beforeEach(() => setup()) + + test("team member joins team group → DM sent with contact ID", async () => { + const member = {memberId: "new-team", groupMemberId: 8000, memberContactId: 30, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Charlie"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, member, {contactId: 30, profile: {displayName: "Charlie"}})) + expectDmSent(30, "Your contact ID is 30:Charlie") + }) + + test("DM with spaces in name → name single-quoted", async () => { + const member = {memberId: "new-team", groupMemberId: 8001, memberContactId: 31, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Charlie Brown"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, member, {contactId: 31, profile: {displayName: "Charlie Brown"}})) + expectDmSent(31, "31:'Charlie Brown'") + }) + + test("pending DM delivered on contactConnected", async () => { + const invEvt = { + type: "newMemberContactReceivedInv" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 32, profile: {displayName: "Dave"}}, + groupInfo: makeGroupInfo(TEAM_GROUP_ID, {businessChat: null}), + member: {memberId: "dave-member", groupMemberId: 8002, memberContactId: 32, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Dave"}}, + } + await bot.onMemberContactReceivedInv(invEvt) + + await bot.onContactConnected({ + type: "contactConnected" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 32, profile: {displayName: "Dave"}}, + }) + expectDmSent(32, "Your contact ID is 32:Dave") + }) + + test("team member with no DM contact → creates member contact and sends invitation", async () => { + const member = {memberId: "new-team-no-dm", groupMemberId: 8010, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Frank"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, member, undefined)) + expect(chat.memberContacts.some(c => c.groupId === TEAM_GROUP_ID && c.groupMemberId === 8010)).toBe(true) + expect(chat.memberContactInvitations.some(i => i.text.includes("Your contact ID is") && i.text.includes("Frank"))).toBe(true) + const dms = chat.sent.filter(s => s.chat[0] === ChatType.Direct) + expect(dms.some(m => m.text.includes("Your contact ID is") && m.text.includes("Frank"))).toBe(true) + }) + + test("joinedGroupMember in team group → creates member contact and sends invitation", async () => { + const member = {memberId: "link-joiner", groupMemberId: 8020, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Grace"}} + await bot.onJoinedGroupMember(joinedEvent(TEAM_GROUP_ID, member)) + expect(chat.memberContacts.some(c => c.groupId === TEAM_GROUP_ID && c.groupMemberId === 8020)).toBe(true) + expect(chat.memberContactInvitations.some(i => i.text.includes("Grace"))).toBe(true) + }) + + test("no duplicate DM when both sendTeamMemberDM succeeds and onMemberContactReceivedInv fires", async () => { + const invEvt = { + type: "newMemberContactReceivedInv" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 33, profile: {displayName: "Eve"}}, + groupInfo: makeGroupInfo(TEAM_GROUP_ID, {businessChat: null}), + member: {memberId: "eve-member", groupMemberId: 8003, memberContactId: 33, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Eve"}}, + } + await bot.onMemberContactReceivedInv(invEvt) + + const eveMember = {memberId: "eve-member", groupMemberId: 8003, memberContactId: 33, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Eve"}} + await bot.onMemberConnected(connectedEvent(TEAM_GROUP_ID, eveMember, {contactId: 33, profile: {displayName: "Eve"}})) + + await bot.onContactConnected({ + type: "contactConnected" as const, + user: makeUser(MAIN_USER_ID), + contact: {contactId: 33, profile: {displayName: "Eve"}}, + }) + + const dms = chat.sentDirect(33) + const contactIdMsgs = dms.filter(m => m.includes("Your contact ID is 33:Eve")) + expect(contactIdMsgs.length).toBe(1) + }) +}) + +describe("Direct Message Handling", () => { + beforeEach(() => setup()) + + test("regular DM → bot replies with business address link", async () => { + bot.businessAddress = "simplex:/contact#abc123" + await bot.onNewChatItems(directMessage("Hi there", 99)) + expectDmSent(99, "simplex:/contact#abc123") + }) + + test("DM without business address set → no reply", async () => { + bot.businessAddress = null + await bot.onNewChatItems(directMessage("Hi there", 99)) + expect(chat.sentDirect(99).length).toBe(0) + }) + + test("non-message DM event (e.g. contactConnected) → no reply", async () => { + bot.businessAddress = "simplex:/contact#abc123" + const ci = { + chatDir: {type: "directRcv"}, + content: {type: "rcvDirectEvent"}, + meta: {itemId: 9999, createdAt: new Date().toISOString()}, + } + const evt = { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeDirectAChatItem(ci, 99)], + } + await bot.onNewChatItems(evt) + expect(chat.sentDirect(99).length).toBe(0) + }) +}) + +describe("Business Request Handler", () => { + beforeEach(() => setup()) + + test("acceptingBusinessRequest → enables file uploads AND visible history", async () => { + await bot.onBusinessRequest({ + type: "acceptingBusinessRequest" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(CUSTOMER_GROUP_ID), + }) + expect(chat.profileUpdates.some(u => + u.groupId === CUSTOMER_GROUP_ID + && u.profile.groupPreferences?.files?.enable === GroupFeatureEnabled.On + && u.profile.groupPreferences?.history?.enable === GroupFeatureEnabled.On + )).toBe(true) + }) +}) + +describe("chatItemUpdated Handler", () => { + beforeEach(() => setup()) + + test("chatItemUpdated in business group → card update scheduled", async () => { + await bot.onChatItemUpdated(updatedEvent(CUSTOMER_GROUP_ID, makeChatItem({dir: "groupRcv", text: "edited message", memberId: CUSTOMER_ID}))) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 600}) + await cards.flush() + expectCardDeleted(600) + }) + + test("chatItemUpdated in non-business group → ignored", async () => { + await bot.onChatItemUpdated(updatedEvent(TEAM_GROUP_ID, makeChatItem({dir: "groupRcv", text: "team msg"}))) + await cards.flush() + expect(chat.deleted.length).toBe(0) + }) + + test("chatItemUpdated from wrong user → ignored", async () => { + await bot.onChatItemUpdated(updatedEvent(CUSTOMER_GROUP_ID, makeChatItem({dir: "groupRcv", text: "edited"}), GROK_USER_ID)) + await cards.flush() + expect(chat.deleted.length).toBe(0) + }) +}) + +describe("Reactions", () => { + beforeEach(() => setup()) + + test("reaction in business group → card update scheduled", async () => { + await bot.onChatItemReaction(reactionEvent(CUSTOMER_GROUP_ID, true)) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 700}) + await cards.flush() + expectCardDeleted(700) + }) + + test("reaction removed (added=false) → no card update", async () => { + await bot.onChatItemReaction(reactionEvent(CUSTOMER_GROUP_ID, false)) + await cards.flush() + expect(chat.deleted.length).toBe(0) + }) +}) + +describe("Customer Leave", () => { + beforeEach(() => setup()) + + test("customer leaves → customData cleared", async () => { + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 800}) + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeCustomerMember())) + expect(chat.customData.has(CUSTOMER_GROUP_ID)).toBe(false) + }) + + test("Grok leaves → in-memory maps cleaned", async () => { + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeGrokMember())) + }) + + test("team member leaves → logged, no crash", async () => { + await bot.onLeftMember(leftEvent(CUSTOMER_GROUP_ID, makeTeamMember(TEAM_MEMBER_1_ID, "Alice"))) + }) + + test("leftMember in non-business group → ignored", async () => { + const member = {memberId: "someone", groupMemberId: 9000, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Someone"}} + await bot.onLeftMember(leftEvent(TEAM_GROUP_ID, member)) + }) +}) + +describe("Error Handling", () => { + beforeEach(() => setup()) + + test("apiAddMember fails (Grok invite) → grokUnavailableMessage", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + chat.apiAddMemberWillFail() + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expectSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + }) + + test("groupDuplicateMember on Grok invite → only inviting message, no result", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + chat.apiAddMemberWillFail({chatError: {errorType: {type: "groupDuplicateMember"}}}) + const sentBefore = chat.sent.length + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + // Only the "Inviting Grok" message is sent — no activated/unavailable result + expect(chat.sent.length).toBe(sentBefore + 1) + expectSentToGroup(CUSTOMER_GROUP_ID, "Inviting Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "temporarily unavailable") + }) + + test("/team while members are in Invited status → no second apiAddMember call", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + + // Simulate the realistic post-/team state: both members have been invited + // but have not yet accepted (memberStatus = "invited"). The SimpleX API + // would resend the invitation if apiAddMember is called for an Invited + // member — the pre-check in addOrFindTeamMember must skip them. + const invited = (contactId: number) => ({ + memberId: `team-${contactId}`, + groupMemberId: 5000 + contactId, + memberContactId: contactId, + memberStatus: "invited", + memberProfile: {displayName: `Contact${contactId}`}, + }) + chat.members.set(CUSTOMER_GROUP_ID, [invited(TEAM_MEMBER_1_ID), invited(TEAM_MEMBER_2_ID)]) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.added.filter(a => a.groupId === CUSTOMER_GROUP_ID)).toEqual([]) + }) + + test("/grok in TEAM-PENDING while Grok is in Invited status → no second apiAddMember call", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [ + makeTeamMember(TEAM_MEMBER_1_ID, "Alice"), + {memberId: "grok-member", groupMemberId: 7777, memberContactId: GROK_CONTACT_ID, memberStatus: "invited", memberProfile: {displayName: "Grok"}}, + ]) + chat.added.length = 0 + + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + + expect(chat.added.filter(a => a.contactId === GROK_CONTACT_ID)).toEqual([]) + expectNotSentToGroup(CUSTOMER_GROUP_ID, "Inviting Grok") + }) +}) + +describe("Profile / Event Filtering", () => { + beforeEach(() => setup()) + + test("newChatItems from Grok profile → ignored by main handler", async () => { + const evt = { + type: "newChatItems" as const, + user: makeUser(GROK_USER_ID), + chatItems: [makeAChatItem(makeChatItem({dir: "groupRcv", text: "test"}))], + } + const sentBefore = chat.sent.length + await bot.onNewChatItems(evt) + expect(chat.sent.length).toBe(sentBefore) + }) + + test("Grok events from main profile → ignored by Grok handlers", async () => { + const evt = { + type: "receivedGroupInvitation" as const, + user: makeUser(MAIN_USER_ID), + groupInfo: makeGroupInfo(300), + contact: {contactId: 1}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + } + await bot.onGrokGroupInvitation(evt) + expect(chat.joined.length).toBe(0) + }) + + test("own messages (groupSnd) → ignored", async () => { + const ci = makeChatItem({dir: "groupSnd", text: "Bot message"}) + const evt = { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [makeAChatItem(ci)], + } + const sentBefore = chat.sent.length + await bot.onNewChatItems(evt) + expect(chat.sent.length).toBe(sentBefore) + }) + + test("non-business group messages → ignored", async () => { + const ci = makeChatItem({dir: "groupRcv", text: "test"}) + const nonBizGroup = makeGroupInfo(999, {businessChat: null}) + const evt = { + type: "newChatItems" as const, + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: nonBizGroup}, chatItem: ci}], + } + const sentBefore = chat.sent.length + await bot.onNewChatItems(evt) + expect(chat.sent.length).toBe(sentBefore) + }) +}) + +describe("Grok Join Flow", () => { + beforeEach(() => setup()) + + test("Grok receivedGroupInvitation → apiJoinGroup called", async () => { + // First need to set up a pending grok join + // Simulate the main profile side: add Grok to a group + await reachQueue() + addBotMessage("The team will reply to your message") + + // This kicks off activateGrok which adds member and waits + const joinComplete = new Promise(async (resolve) => { + // Simulate Grok invitation after a small delay + setTimeout(async () => { + const addedGrok = chat.added.find(a => a.contactId === GROK_CONTACT_ID) + if (addedGrok) { + const memberId = `member-${GROK_CONTACT_ID}` + await bot.onGrokGroupInvitation({ + type: "receivedGroupInvitation", + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + }) + } + resolve() + }, 10) + }) + + // Don't await bot.onNewChatItems yet — let it start + const botPromise = bot.onNewChatItems(customerMessage("/grok")) + await joinComplete + // Complete the join + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999}, + }) + await botPromise + + expect(chat.joined).toContain(GROK_LOCAL_GROUP_ID) + }) + + test("unmatched Grok invitation → buffered, not joined", async () => { + const evt = { + type: "receivedGroupInvitation" as const, + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(999), membership: {memberId: "unknown-member"}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + } + await bot.onGrokGroupInvitation(evt) + expect(chat.joined.length).toBe(0) + }) + + test("buffered invitation drained after pendingGrokJoins set → apiJoinGroup called", async () => { + // Simulate the race: invitation arrives before pendingGrokJoins is set + const memberId = `member-${GROK_CONTACT_ID}` + const invEvt = { + type: "receivedGroupInvitation" as const, + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + } + // Buffer the invitation (no pending join registered yet) + await bot.onGrokGroupInvitation(invEvt) + expect(chat.joined.length).toBe(0) + + // Now trigger activateGrok — apiAddMember returns, pendingGrokJoins set, buffer drained + const joinComplete = new Promise((resolve) => { + setTimeout(async () => { + // Grok connected after buffer drain processed the invitation + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999}, + }) + resolve() + }, 20) + }) + + await reachQueue() + addBotMessage("The team will reply to your message") + const botPromise = bot.onNewChatItems(customerMessage("/grok")) + await joinComplete + await botPromise + await bot.flush() + + expect(chat.joined).toContain(GROK_LOCAL_GROUP_ID) + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + }) + + test("per-message responses suppressed during activateGrok initial response", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + + // Customer's message visible in Grok's view (activateGrok reads it for initial response) + addCustomerMessageToHistory("Hello, I need help", GROK_LOCAL_GROUP_ID) + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + + // Start /grok activation (fireAndForget) + const botPromise = bot.onNewChatItems(customerMessage("/grok")) + + // Wait for apiAddMember to complete + await new Promise(r => setTimeout(r, 10)) + + // Simulate Grok invitation → sets grokGroupMap/reverseGrokMap + const memberId = `member-${GROK_CONTACT_ID}` + await bot.onGrokGroupInvitation({ + type: "receivedGroupInvitation", + user: makeUser(GROK_USER_ID), + groupInfo: {...makeGroupInfo(GROK_LOCAL_GROUP_ID), membership: {memberId}}, + contact: {contactId: 99}, + fromMemberRole: GroupMemberRole.Admin, + memberRole: GroupMemberRole.Member, + }) + + // grokInitialResponsePending is set, reverseGrokMap is set. + // Simulate per-message event (as if message backlog arrived for Grok profile) + await bot.onGrokNewChatItems(grokViewCustomerMessage("Hello, I need help")) + + // Gating: per-message handler must NOT have called Grok API + expect(grokApi.calls.length).toBe(0) + + // Now complete the join → activateGrok sends initial combined response + await bot.onGrokMemberConnected({ + type: "connectedToGroupMember", + user: makeUser(GROK_USER_ID), + groupInfo: makeGroupInfo(GROK_LOCAL_GROUP_ID), + member: {memberId: "bot-in-grok-view", groupMemberId: 9999, memberContactId: undefined}, + }) + + await botPromise + await bot.flush() + + // Only 1 Grok API call: the initial combined response from activateGrok + expect(grokApi.calls.length).toBe(1) + expect(grokApi.calls[0].message).toContain("Hello, I need help") + }) + + test("per-message responses resume after activateGrok completes", async () => { + await reachGrok() + await bot.flush() + const callsAfterActivation = grokApi.calls.length + + // Send a new customer message via Grok's view — should be processed normally + addCustomerMessageToHistory("Follow-up question", GROK_LOCAL_GROUP_ID) + await bot.onGrokNewChatItems(grokViewCustomerMessage("Follow-up question")) + + expect(grokApi.calls.length).toBe(callsAfterActivation + 1) + expect(grokApi.calls[grokApi.calls.length - 1].message).toBe("Follow-up question") + }) + + test("activateGrok groupDuplicateMember path → gate cleared by outer finally", async () => { + // After reachGrok(), gate is cleared and reverseGrokMap is populated. + await reachGrok() + await bot.flush() + const callsBaseline = grokApi.calls.length + + // Second /grok while Grok is already present → apiAddMember throws duplicate. + // The outer try/finally must clear the gate even though the handler returns + // silently from inside the try — otherwise per-message responses stay + // suppressed for this group forever. + chat.apiAddMemberWillFail({chatError: {errorType: {type: "groupDuplicateMember"}}}) + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + + // Gate must be clear: a subsequent per-message event triggers Grok. + addCustomerMessageToHistory("another question", GROK_LOCAL_GROUP_ID) + await bot.onGrokNewChatItems(grokViewCustomerMessage("another question")) + expect(grokApi.calls.length).toBe(callsBaseline + 1) + }) +}) + +describe("Grok No-History Fallback", () => { + beforeEach(() => setup()) + + test("Grok joins but sees no customer messages → sends grokNoHistoryMessage", async () => { + chat.chatItems.set(GROK_LOCAL_GROUP_ID, []) + chat.groups.set(GROK_LOCAL_GROUP_ID, makeGroupInfo(GROK_LOCAL_GROUP_ID)) + + const grokJoinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await grokJoinPromise + await bot.flush() + expectAnySent("couldn't see your earlier messages") + }) +}) + +describe("Non-customer messages trigger card update", () => { + beforeEach(() => setup()) + + test("Grok response in customer group → card update scheduled", async () => { + await bot.onNewChatItems(grokResponseMessage("Grok says hi")) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 900}) + await cards.flush() + expectCardDeleted(900) + }) + + test("team member message → card update scheduled", async () => { + await bot.onNewChatItems(teamMemberMessage("Team says hi")) + chat.customData.set(CUSTOMER_GROUP_ID, {cardItemId: 901}) + await cards.flush() + expectCardDeleted(901) + }) +}) + +describe("End-to-End Flows", () => { + beforeEach(() => setup()) + + test("WELCOME → QUEUE → /team → TEAM-PENDING → team msg → TEAM", async () => { + await bot.onNewChatItems(customerMessage("Help me")) + expectSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + addBotMessage("The team will reply to your message") + + await bot.onNewChatItems(customerMessage("/team")) + expectSentToGroup(CUSTOMER_GROUP_ID, "We will reply within") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + addBotMessage("We will reply within 24 hours.") + + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + const pendingState = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(pendingState).toBe("TEAM-PENDING") + + addTeamMemberMessageToHistory("I'll help you", TEAM_MEMBER_1_ID) + await bot.onNewChatItems(teamMemberMessage("I'll help you")) + + const teamState = await cards.deriveState(CUSTOMER_GROUP_ID) + expect(teamState).toBe("TEAM") + }) + + test("WELCOME → /grok first msg → GROK", async () => { + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + + expectSentToGroup(CUSTOMER_GROUP_ID, "You are chatting with Grok") + expectNotSentToGroup(CUSTOMER_GROUP_ID, "The team will reply to your message") + expect(chat.sentTo(TEAM_GROUP_ID).length).toBeGreaterThan(0) + }) + + test("multiple concurrent conversations are independent", async () => { + const GROUP_A = 101 + const GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A, {customerId: "cust-a"})) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B, {customerId: "cust-b"})) + + const ciA = makeChatItem({dir: "groupRcv", text: "Help A", memberId: "cust-a"}) + await bot.onNewChatItems({ + type: "newChatItems", + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROUP_A, {customerId: "cust-a"})}, chatItem: ciA}], + }) + + const ciB = makeChatItem({dir: "groupRcv", text: "Help B", memberId: "cust-b"}) + await bot.onNewChatItems({ + type: "newChatItems", + user: makeUser(MAIN_USER_ID), + chatItems: [{chatInfo: {type: "group", groupInfo: makeGroupInfo(GROUP_B, {customerId: "cust-b"})}, chatItem: ciB}], + }) + + expectSentToGroup(GROUP_A, "The team will reply to your message") + expectSentToGroup(GROUP_B, "The team will reply to your message") + }) +}) + +describe("Message Templates", () => { + test("welcomeMessage is a non-empty string", () => { + expect(typeof welcomeMessage).toBe("string") + expect(welcomeMessage.length).toBeGreaterThan(0) + }) + + test("grokActivatedMessage mentions chatting with Grok", () => { + expect(grokActivatedMessage).toContain("chatting with Grok") + }) + + test("teamLockedMessage mentions team mode", () => { + expect(teamLockedMessage).toContain("team mode") + }) + + test("queueMessage mentions hours", () => { + const msg = queueMessage("UTC", true) + expect(msg).toContain("hours") + }) +}) + +describe("State persistence in customData", () => { + beforeEach(() => setup()) + + test("first customer text writes state=QUEUE to customData", async () => { + await bot.onNewChatItems(customerMessage("Hello")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("QUEUE") + }) + + test("/team writes state=TEAM-PENDING immediately (before team accepts)", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM-PENDING") + }) + + test("/grok writes state=GROK when activation succeeds", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + const joinPromise = simulateGrokJoinSuccess() + await bot.onNewChatItems(customerMessage("/grok")) + await joinPromise + await bot.flush() + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("GROK") + }) + + test("/grok from QUEUE reverts state to QUEUE if activation fails", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + chat.apiAddMemberWillFail() + await bot.onNewChatItems(customerMessage("/grok")) + await bot.flush() + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("QUEUE") + }) + + test("concurrent /team during Grok activation timeout does not demote state", async () => { + await reachQueue() + addBotMessage("The team will reply to your message") + + // Pause activateGrok at waitForGrokJoin so /team can run in the meantime. + // Patching apiAddMember won't work: it's wrapped in withMainProfile's mutex, + // which /team's activateTeam also needs. waitForGrokJoin awaits outside the + // mutex — that's the real race window in production. + let releaseJoin!: (joined: boolean) => void + ;(bot as any).waitForGrokJoin = () => + new Promise((resolve) => { releaseJoin = resolve }) + + // /grok: writes state=GROK optimistically, fire-and-forgets activateGrok. + await bot.onNewChatItems(customerMessage("/grok")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("GROK") + + // Let activateGrok progress past apiAddMember into waitForGrokJoin. + await Promise.resolve() + await Promise.resolve() + + // /team while activateGrok is waiting for join — writes TEAM-PENDING + adds members. + await bot.onNewChatItems(customerMessage("/team")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM-PENDING") + expectMemberAdded(CUSTOMER_GROUP_ID, TEAM_MEMBER_1_ID) + + // Simulate Grok join timeout — activateGrok's revertStateOnFail runs. + releaseJoin(false) + await bot.flush() + + // Fix asserts: revert guard sees state != "GROK" and leaves TEAM-PENDING alone. + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM-PENDING") + }) + + test("first team text writes state=TEAM via gate", async () => { + await reachTeamPending() + addBotMessage("We will reply within 24 hours.") + chat.members.set(CUSTOMER_GROUP_ID, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + await bot.onNewChatItems(teamMemberMessage("I'll help")) + expect(chat.customData.get(CUSTOMER_GROUP_ID)?.state).toBe("TEAM") + }) +}) + +describe("Card Preview Sender Prefixes", () => { + beforeEach(() => setup()) + + // Helper: extract preview line from card text posted to team group + function getCardPreview(): string { + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + const cardText = teamMsgs[0] + if (!cardText) return "" + const lines = cardText.split("\n") + // Card layout: header, state, preview, /'join N' — preview is second to last + return lines[lines.length - 2] || "" + } + + test("customer-only messages: first prefixed, rest not", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Hello") + addCustomerMessageToHistory("Need help") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Alice: Hello") + expect(preview).toContain("!3 /! Need help") + // Second message must NOT have prefix (same sender) + expect(preview).not.toContain("Alice: Need help") + }) + + test("three consecutive customer messages: only first gets prefix", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("First") + addCustomerMessageToHistory("Second") + addCustomerMessageToHistory("Third") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + const prefixCount = (preview.match(/Alice:/g) || []).length + expect(prefixCount).toBe(1) + expect(preview).toContain("Alice: First") + }) + + test("alternating customer and Grok: each sender change triggers prefix", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("How does encryption work?") + addGrokMessageToHistory("SimpleX uses double ratchet") + addCustomerMessageToHistory("And metadata?") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Alice: How does encryption work?") + expect(preview).toContain("Grok: SimpleX uses double ratchet") + expect(preview).toContain("Alice: And metadata?") + }) + + test("Grok identified by grokContactId, not by display name", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + // Grok message uses GROK_CONTACT_ID → labeled "Grok" regardless of memberProfile + addGrokMessageToHistory("I am Grok") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Grok: I am Grok") + }) + + test("team member messages use their memberProfile displayName", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Help please") + // Add team member message with explicit display name + const teamCi = makeChatItem({ + dir: "groupRcv", text: "On it!", + memberId: `team-${TEAM_MEMBER_1_ID}`, memberContactId: TEAM_MEMBER_1_ID, + memberDisplayName: "Bob", + }) + const items = chat.chatItems.get(CUSTOMER_GROUP_ID) || [] + items.push(teamCi) + chat.chatItems.set(CUSTOMER_GROUP_ID, items) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("Alice: Help please") + expect(preview).toContain("Bob: On it!") + }) + + test("bot messages (groupSnd) excluded from preview", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Hello") + addBotMessage("The team will reply to your message") + addCustomerMessageToHistory("Thanks") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).not.toContain("The team will reply to your message") + // Both customer messages are from the same sender — only first prefixed + expect(preview).toContain("Alice: Hello") + expect(preview).toContain("!3 /! Thanks") + }) + + test("media-only message shows type label", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + const imgCi = makeChatItem({dir: "groupRcv", text: "", memberId: CUSTOMER_ID, msgType: "image"}) + const items = chat.chatItems.get(CUSTOMER_GROUP_ID) || [] + items.push(imgCi) + chat.chatItems.set(CUSTOMER_GROUP_ID, items) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[image]") + }) + + test("media message with caption shows label + text", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + const imgCi = makeChatItem({dir: "groupRcv", text: "screenshot of the bug", memberId: CUSTOMER_ID, msgType: "image"}) + const items = chat.chatItems.get(CUSTOMER_GROUP_ID) || [] + items.push(imgCi) + chat.chatItems.set(CUSTOMER_GROUP_ID, items) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[image] screenshot of the bug") + }) + + test("long message truncated with [truncated]", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + const longMsg = "x".repeat(300) + addCustomerMessageToHistory(longMsg) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[truncated]") + // Truncated at ~200 chars + prefix + expect(preview.length).toBeLessThan(300) + }) + + test("total overflow truncates oldest messages, keeps newest", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + // Add many messages to exceed 1000 chars total + for (let i = 0; i < 20; i++) { + addCustomerMessageToHistory(`Message number ${i} with some extra padding text to fill space quickly`) + } + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toContain("[truncated]") + // Newest messages should be present, oldest truncated + expect(preview).toContain("Message number 19") + expect(preview).not.toContain("Message number 0") + // Should not include all 20 messages + const slashCount = (preview.match(/ \/ /g) || []).length + expect(slashCount).toBeLessThan(19) + }) + + test("empty preview when no messages", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toBe('""') + }) + + test("only bot messages → empty preview", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addBotMessage("Welcome!") + addBotMessage("Queue message") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).toBe('""') + }) + + test("newlines in message text → replaced with spaces", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "Alice"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("line1\nline2\n\nline3") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const preview = getCardPreview() + expect(preview).not.toContain("\n") + expect(preview).toContain("line1 line2 line3") + }) + + test("newlines in customer display name → sanitized in card header", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID, {displayName: "First\nLast"}) + chat.groups.set(CUSTOMER_GROUP_ID, gi) + addCustomerMessageToHistory("Hello") + await cards.createCard(CUSTOMER_GROUP_ID, gi) + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(1) + const cardText = teamMsgs[0] + // Card header should have sanitized name (no newlines) + expect(cardText).toContain("First Last") + // Exactly 4 lines: header, state, preview, /'join N' + expect(cardText.split("\n").length).toBe(4) + expect(cardText).toContain(`/'join ${CUSTOMER_GROUP_ID}'`) + }) +}) + +describe("Restart Card Recovery", () => { + beforeEach(() => setup()) + + test("refreshAllCards refreshes groups with active cards", async () => { + const GROUP_A = 101 + const GROUP_B = 102 + const GROUP_NO_CARD = 103 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.groups.set(GROUP_NO_CARD, makeGroupInfo(GROUP_NO_CARD)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + chat.customData.set(GROUP_B, {cardItemId: 503}) + + await cards.refreshAllCards() + + expectCardDeleted(501) + expectCardDeleted(503) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(2) // 2 cards × 1 message each + }) + + test("refreshAllCards with no active cards → no-op", async () => { + await cards.refreshAllCards() + expect(chat.deleted.length).toBe(0) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("refreshAllCards ignores groups without cardItemId in customData", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.customData.set(GROUP_A, {someOtherData: true}) + + await cards.refreshAllCards() + expect(chat.deleted.length).toBe(0) + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(0) + }) + + test("refreshAllCards orders by cardItemId ascending (oldest first, newest last)", async () => { + // GROUP_C has higher cardItemId (more recent) than GROUP_A and GROUP_B + const GROUP_A = 101, GROUP_B = 102, GROUP_C = 103 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.groups.set(GROUP_C, makeGroupInfo(GROUP_C)) + chat.customData.set(GROUP_C, {cardItemId: 900}) // newest — should refresh last + chat.customData.set(GROUP_A, {cardItemId: 100}) // oldest — should refresh first + chat.customData.set(GROUP_B, {cardItemId: 500}) // middle + + await cards.refreshAllCards() + + // Verify deletion order: oldest cardItemId first, newest last + expect(chat.deleted.length).toBe(3) + expect(chat.deleted[0].itemIds).toEqual([100]) + expect(chat.deleted[1].itemIds).toEqual([500]) + expect(chat.deleted[2].itemIds).toEqual([900]) + + // Newest card is posted last → appears at bottom of team group + const teamMsgs = chat.sentTo(TEAM_GROUP_ID) + expect(teamMsgs.length).toBe(3) // 3 cards × 1 message each + }) + + test("refreshAllCards skips cards marked complete", async () => { + const GROUP_A = 101, GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.customData.set(GROUP_A, {cardItemId: 100, complete: true}) + chat.customData.set(GROUP_B, {cardItemId: 200}) + + await cards.refreshAllCards() + + expect(chat.deleted.length).toBe(1) + expect(chat.deleted[0].itemIds).toEqual([200]) + expect(chat.deleted.some(d => d.itemIds.includes(100))).toBe(false) + }) + + test("refreshAllCards deletes old card before reposting", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + + await cards.refreshAllCards() + + // Old card should be deleted + expect(chat.deleted.length).toBe(1) + expect(chat.deleted[0].itemIds).toEqual([501]) + // New card posted + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(1) + }) + + test("refreshAllCards ignores delete failure (>24h old card)", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.customData.set(GROUP_A, {cardItemId: 501}) + chat.apiDeleteChatItemsWillFail() + + await cards.refreshAllCards() + + // Delete failed but new card still posted + expect(chat.sentTo(TEAM_GROUP_ID).length).toBe(1) + // customData updated with new cardItemId + const newData = chat.customData.get(GROUP_A) + expect(typeof newData.cardItemId).toBe("number") + expect(newData.cardItemId).not.toBe(501) // new ID, not the old one + }) + + test("card flush writes complete: true for auto-completed conversations", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.members.set(GROUP_A, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member message from 4 hours ago (> completeHours=3h) → auto-complete + const oldCi = makeChatItem({dir: "groupRcv", text: "Resolved!", memberId: `team-${TEAM_MEMBER_1_ID}`, memberContactId: TEAM_MEMBER_1_ID}) + oldCi.meta.createdAt = new Date(Date.now() - 4 * 3600_000).toISOString() + chat.chatItems.set(GROUP_A, [oldCi]) + // Create initial card data + chat.customData.set(GROUP_A, {cardItemId: 500}) + + cards.scheduleUpdate(GROUP_A) + await cards.flush() + + const data = chat.customData.get(GROUP_A) + expect(data.complete).toBe(true) + }) + + test("card flush clears complete flag when conversation becomes active again", async () => { + const GROUP_A = 101 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.members.set(GROUP_A, [makeTeamMember(TEAM_MEMBER_1_ID, "Alice")]) + // Team member message from 4h ago + recent customer message → NOT complete + const teamCi = makeChatItem({dir: "groupRcv", text: "Resolved!", memberId: `team-${TEAM_MEMBER_1_ID}`, memberContactId: TEAM_MEMBER_1_ID}) + teamCi.meta.createdAt = new Date(Date.now() - 4 * 3600_000).toISOString() + const custCi = makeChatItem({dir: "groupRcv", text: "Actually one more question", memberId: CUSTOMER_ID}) + chat.chatItems.set(GROUP_A, [teamCi, custCi]) + // Previously complete + chat.customData.set(GROUP_A, {cardItemId: 500, complete: true}) + + cards.scheduleUpdate(GROUP_A) + await cards.flush() + + const data = chat.customData.get(GROUP_A) + expect(data.complete).toBeUndefined() + }) + + test("refreshAllCards continues on individual card failure", async () => { + const GROUP_A = 101, GROUP_B = 102 + chat.groups.set(GROUP_A, makeGroupInfo(GROUP_A)) + chat.groups.set(GROUP_B, makeGroupInfo(GROUP_B)) + chat.customData.set(GROUP_A, {cardItemId: 100}) + chat.customData.set(GROUP_B, {cardItemId: 200}) + + chat.apiDeleteChatItemsWillFail() + await cards.refreshAllCards() + expectCardDeleted(200) + }) +}) + +describe("joinedGroupMember Event Filtering", () => { + beforeEach(() => setup()) + + test("joinedGroupMember in non-team group → ignored (no DM)", async () => { + const member = {memberId: "someone", groupMemberId: 9000, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Someone"}} + await bot.onJoinedGroupMember(joinedEvent(CUSTOMER_GROUP_ID, member)) + expect(chat.rawCmds.length).toBe(0) + expect(chat.sent.filter(s => s.chat[0] === ChatType.Direct).length).toBe(0) + }) + + test("joinedGroupMember from wrong user → ignored", async () => { + const member = {memberId: "someone", groupMemberId: 9001, memberContactId: null, memberStatus: GroupMemberStatus.Connected, memberProfile: {displayName: "Someone"}} + await bot.onJoinedGroupMember(joinedEvent(TEAM_GROUP_ID, member, GROK_USER_ID)) + expect(chat.rawCmds.length).toBe(0) + }) +}) + +describe("parseConfig Validation", () => { + const baseArgs = ["--team-group", "Support"] + + test("--complete-hours non-numeric → throws", () => { + expect(() => parseConfig([...baseArgs, "--complete-hours", "abc"])) + .toThrow(/--complete-hours must be a non-negative integer, got "abc"/) + }) + + test("postgres backend without --pg-conn → throws", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "postgres" + try { + expect(() => parseConfig(baseArgs)) + .toThrow(/--pg-conn is required when backend is postgres/) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("postgres backend with --pg-conn → db is postgres DbConfig", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "postgres" + try { + const cfg = parseConfig([...baseArgs, "--pg-conn", "postgres://user:pass@localhost/db"]) + expect(cfg.db).toEqual({type: "postgres", connectionString: "postgres://user:pass@localhost/db"}) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("postgres backend with --pg-schema → DbConfig carries schemaPrefix", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "postgres" + try { + const cfg = parseConfig([...baseArgs, "--pg-conn", "postgres://localhost/db", "--pg-schema", "bot"]) + expect(cfg.db).toEqual({type: "postgres", connectionString: "postgres://localhost/db", schemaPrefix: "bot"}) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("sqlite backend (default) → db is sqlite DbConfig with default filePrefix", () => { + const prevBackend = process.env.SIMPLEX_BACKEND + const prevNpm = process.env.npm_config_simplex_backend + delete process.env.SIMPLEX_BACKEND + delete process.env.npm_config_simplex_backend + try { + const cfg = parseConfig(baseArgs) + expect(cfg.db).toEqual({type: "sqlite", filePrefix: "./data/simplex"}) + } finally { + if (prevBackend !== undefined) process.env.SIMPLEX_BACKEND = prevBackend + if (prevNpm !== undefined) process.env.npm_config_simplex_backend = prevNpm + } + }) + + test("sqlite backend with --sqlite-key → DbConfig carries encryptionKey", () => { + const cfg = parseConfig([...baseArgs, "--sqlite-key", "secret"]) + expect(cfg.db).toEqual({type: "sqlite", filePrefix: "./data/simplex", encryptionKey: "secret"}) + }) + + test("unknown flag → parseArgs throws", () => { + expect(() => parseConfig([...baseArgs, "--team-gropu", "typo"])) + .toThrow() + }) + + test("missing --team-group → throws", () => { + expect(() => parseConfig([])) + .toThrow(/required option '--team-group/) + }) + + test("invalid SIMPLEX_BACKEND → throws", () => { + const prev = process.env.SIMPLEX_BACKEND + process.env.SIMPLEX_BACKEND = "mysql" + try { + expect(() => parseConfig(baseArgs)) + .toThrow(/Invalid SIMPLEX_BACKEND: "mysql"/) + } finally { + if (prev === undefined) delete process.env.SIMPLEX_BACKEND + else process.env.SIMPLEX_BACKEND = prev + } + }) + + test("--complete-hours negative → throws", () => { + // parseArgs refuses "-1" as a bare arg (ambiguous with a short flag), so use `=` form + expect(() => parseConfig([...baseArgs, "--complete-hours", "-1"])) + .toThrow(/--complete-hours must be a non-negative integer, got "-1"/) + }) + + test("--card-flush-seconds non-numeric → throws", () => { + expect(() => parseConfig([...baseArgs, "--card-flush-seconds", "xyz"])) + .toThrow(/--card-flush-seconds must be a non-negative integer, got "xyz"/) + }) + + test("--timezone invalid IANA → throws", () => { + expect(() => parseConfig([...baseArgs, "--timezone", "Not/AZone"])) + .toThrow(/--timezone "Not\/AZone" is not a valid IANA time zone/) + }) + + test("--complete-hours 0 → allowed (disables auto-complete)", () => { + const cfg = parseConfig([...baseArgs, "--complete-hours", "0"]) + expect(cfg.completeHours).toBe(0) + }) + + test("valid IANA timezone → accepted", () => { + const cfg = parseConfig([...baseArgs, "--timezone", "America/New_York"]) + expect(cfg.timezone).toBe("America/New_York") + }) +}) + +describe("GrokApiClient HTTP timeout", () => { + test("chat() calls AbortSignal.timeout(60_000) and passes the signal to fetch", async () => { + const timeoutSpy = vi.spyOn(AbortSignal, "timeout") + const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response(JSON.stringify({choices: [{message: {content: "ok"}}]}), {status: 200}), + ) + + const client = new GrokApiClient("test-key", "system prompt") + await client.chat([], "hello") + + expect(timeoutSpy).toHaveBeenCalledWith(60_000) + expect((fetchSpy.mock.calls[0][1] as RequestInit).signal).toBeInstanceOf(AbortSignal) + + fetchSpy.mockRestore() + timeoutSpy.mockRestore() + }) +}) + +// Lazy per-group command sync. sendToGroup always calls +// apiUpdateGroupProfile on the first send per group when the group's +// stored groupPreferences.commands don't match desiredCommands. Each +// group is synced at most once per process (cache hit on subsequent +// sends). +describe("Command sync in sendToGroup", () => { + beforeEach(() => setup()) + + test("first send → apiUpdateGroupProfile called once with merged commands", async () => { + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Hello, just a greeting.") + expect(chat.profileUpdates).toHaveLength(1) + const {groupId, profile} = chat.profileUpdates[0] + expect(groupId).toBe(CUSTOMER_GROUP_ID) + expect(profile.groupPreferences.commands).toEqual(DESIRED_COMMANDS) + // Existing groupProfile fields (displayName, fullName) are preserved. + expect(profile.displayName).toBe(`Group${CUSTOMER_GROUP_ID}`) + expect(profile.fullName).toBe("") + // The actual message still goes out after the sync. + expect(chat.lastSentTo(CUSTOMER_GROUP_ID)).toBe("Hello, just a greeting.") + }) + + test("group already has desired commands → no apiUpdateGroupProfile, but still cached", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID) + gi.groupProfile.groupPreferences = {commands: DESIRED_COMMANDS} + chat.groups.set(CUSTOMER_GROUP_ID, gi) + + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok for help.") + expect(chat.profileUpdates).toHaveLength(0) + // Cache was populated — a subsequent send even against a divergent DB + // won't re-check. + gi.groupProfile.groupPreferences = {commands: []} + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Send /team for a human.") + expect(chat.profileUpdates).toHaveLength(0) + }) + + test("cache: two sends to same group → sync only once", async () => { + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok first.") + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Or send /team.") + expect(chat.profileUpdates).toHaveLength(1) + expect(chat.sentTo(CUSTOMER_GROUP_ID)).toHaveLength(2) + }) + + test("independent per group: different groups each sync separately", async () => { + const gId2 = 101 + chat.groups.set(gId2, makeGroupInfo(gId2)) + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok.") + await bot.sendToGroup(gId2, "Send /team.") + expect(chat.profileUpdates.map(p => p.groupId).sort()).toEqual([CUSTOMER_GROUP_ID, gId2].sort()) + }) + + test("merge preserves existing group preference fields (files, etc.)", async () => { + const gi = makeGroupInfo(CUSTOMER_GROUP_ID) + gi.groupProfile.groupPreferences = { + files: {enable: "on"}, + reactions: {enable: "on"}, + } + chat.groups.set(CUSTOMER_GROUP_ID, gi) + + await bot.sendToGroup(CUSTOMER_GROUP_ID, "Click /grok.") + expect(chat.profileUpdates).toHaveLength(1) + const prefs = chat.profileUpdates[0].profile.groupPreferences + expect(prefs.commands).toEqual(DESIRED_COMMANDS) + expect(prefs.files).toEqual({enable: "on"}) + expect(prefs.reactions).toEqual({enable: "on"}) + }) +}) diff --git a/apps/simplex-support-bot/package-lock.json b/apps/simplex-support-bot/package-lock.json new file mode 100644 index 0000000000..c6bc006a4b --- /dev/null +++ b/apps/simplex-support-bot/package-lock.json @@ -0,0 +1,2061 @@ +{ + "name": "simplex-chat-support-bot", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simplex-chat-support-bot", + "version": "0.1.0", + "license": "AGPL-3.0", + "dependencies": { + "@simplex-chat/types": "^0.5.0", + "async-mutex": "^0.5.0", + "commander": "^14.0.3", + "simplex-chat": "^6.5.0-beta.10" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.9.3", + "vitest": "^1.6.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@simplex-chat/types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@simplex-chat/types/-/types-0.5.0.tgz", + "integrity": "sha512-f680CRlf+O8WfIaPb7wxVj3PB8mTIOE+HqmetCSe0NBheVAjU3ovg3+zkrWwDlavrHuCLbb7Gmeu4HyNtjDfog==", + "license": "AGPL-3.0", + "dependencies": { + "typescript": "^5.9.2" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", + "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simplex-chat": { + "version": "6.5.0-beta.10", + "resolved": "https://registry.npmjs.org/simplex-chat/-/simplex-chat-6.5.0-beta.10.tgz", + "integrity": "sha512-K5yt4zAA04Ds0XvrSLhuknXx1rmCM8ByjgjJ0iHcQmPU5us9aaiIuez9HPP8FG1a+9xj8XMcgJfi0W1f2fTCdQ==", + "hasInstallScript": true, + "license": "AGPL-3.0", + "dependencies": { + "@simplex-chat/types": "^0.5.0", + "extract-zip": "^2.0.1", + "fast-deep-equal": "^3.1.3", + "node-addon-api": "^8.5.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/apps/simplex-support-bot/package.json b/apps/simplex-support-bot/package.json new file mode 100644 index 0000000000..68105851b8 --- /dev/null +++ b/apps/simplex-support-bot/package.json @@ -0,0 +1,23 @@ +{ + "name": "simplex-chat-support-bot", + "version": "0.1.0", + "private": true, + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "start": "node dist/index.js" + }, + "dependencies": { + "@simplex-chat/types": "^0.5.0", + "async-mutex": "^0.5.0", + "commander": "^14.0.3", + "simplex-chat": "^6.5.0-beta.10" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.9.3", + "vitest": "^1.6.1" + }, + "author": "SimpleX Chat", + "license": "AGPL-3.0" +} diff --git a/apps/simplex-support-bot/plans/20260207-support-bot-implementation.md b/apps/simplex-support-bot/plans/20260207-support-bot-implementation.md new file mode 100644 index 0000000000..c3c11ef61f --- /dev/null +++ b/apps/simplex-support-bot/plans/20260207-support-bot-implementation.md @@ -0,0 +1,1471 @@ +# SimpleX Support Bot — Implementation Plan + +## 1. Executive Summary + +SimpleX Chat support bot — standalone Node.js app using `simplex-chat-nodejs` native NAPI binding. Single `ChatApi` instance with two user profiles (main bot + Grok agent) sharing one SQLite database. A `profileMutex` serializes all profile-switching + SimpleX API calls. Team sees active conversations as cards in a dashboard group — no text forwarding. Implements flow: Welcome → Queue → Grok/Team-Pending → Team. + +## 2. Architecture + +``` +┌─────────────────────────────────────────────────┐ +│ Support Bot Process (Node.js) │ +│ │ +│ chat: ChatApi ← ChatApi.init("./data/simplex") │ +│ Single database, two user profiles │ +│ │ +│ mainUserId ← non-Grok user (default name: │ +│ "Ask SimpleX Team") │ +│ • Business address, event routing, state mgmt │ +│ • Controls group membership │ +│ │ +│ grokUserId ← "Grok" profile │ +│ • Joins customer groups as Member │ +│ • Sends Grok responses into groups │ +│ │ +│ profileMutex: serialize apiSetActiveUser + call │ +│ GrokApiClient → api.x.ai/v1/chat/completions │ +└─────────────────────────────────────────────────┘ +``` + +- Single Node.js process, single `ChatApi` instance via native NAPI +- Two user profiles in one database. The main profile is returned directly from `bot.run()`. The Grok profile's `userId` is persisted to `state.json` as `grokUserId` on the first run (when the bot creates it); subsequent runs identify Grok strictly by that persisted ID (never by display name, which a rename would invalidate). The main profile's displayName is set only on fresh-DB user creation (`"Ask SimpleX Team"`) and is never rewritten by bot code thereafter — `bot.run()` is invoked with `updateProfile: false`. Bot commands (`/grok`, `/team`) are never pushed via global `apiUpdateProfile`; instead they sync lazily per-group in `sendToGroup` — the first send to each group triggers `syncGroupCommands(groupId)`, which verifies the group's `groupPreferences.commands` against `desiredCommands` and calls `apiUpdateGroupProfile` if different (scoped broadcast to that group's members only). Subsequent sends to the same group are cache hits. +- `profileMutex` serializes `apiSetActiveUser(userId)` + the subsequent SimpleX API call. Grok HTTP API calls run **outside** the mutex. +- Events delivered for all profiles — routed by `event.user` field (main → main handler, Grok → Grok handler) +- Business address auto-accept creates a group per customer +- Grok is a second profile invited as a Member — appears as a separate participant +- No cross-profile ID mapping needed — Grok profile uses its own local group IDs from its own events + +## 3. Project Structure + +``` +apps/simplex-support-bot/ +├── package.json # deps: simplex-chat, @simplex-chat/types, async-mutex; devDeps: vitest, @types/node +├── tsconfig.json # ES2022, strict, Node16 module resolution +├── vitest.config.ts # test runner config, path aliases for mocks +├── src/ +│ ├── index.ts # Entry: parse config, init instance, run +│ ├── config.ts # CLI arg parsing, ID:name validation, Config type +│ ├── bot.ts # SupportBot class: state derivation, event dispatch, cards +│ ├── cards.ts # Card formatting, debouncing, lifecycle +│ ├── grok.ts # GrokApiClient: xAI API wrapper, system prompt, history +│ ├── messages.ts # All user-facing message templates +│ └── util.ts # isWeekend, profileMutex, logging helpers +├── bot.test.ts # Vitest suite (154 tests, 31 describes) +├── test/ +│ └── __mocks__/ +│ ├── simplex-chat.js # MockChatApi + utility re-exports +│ └── simplex-chat-types.js # enum re-exports for tests +└── data/ # SQLite databases (created at runtime) +``` + +The Grok system-prompt / context file is supplied at runtime via `--context-file ` (see §4). It is not part of the repo tree. + +## 4. Configuration + +**CLI flags:** + +| Flag | Required | Default | Format | Purpose | +|------|----------|---------|--------|---------| +| `--db-prefix` | No | `./data/simplex` | path | Database file prefix (both profiles share it) | +| `--team-group` | Yes | — | `name` | Team group display name (auto-created if absent, resolved by persisted ID on restarts) | +| `--auto-add-team-members` / `-a` | No | `""` | `ID:name,...` | Comma-separated team member contacts. Validated at startup — exits on mismatch. | +| `--context-file` | Required when `GROK_API_KEY` set | — | path | Grok system-prompt file (SimpleX documentation context). `parseConfig` throws if `GROK_API_KEY` is set without this flag. | +| `--timezone` | No | `"UTC"` | IANA tz | For weekend detection (24h vs 48h). Weekend = Sat 00:00 – Sun 23:59 in this tz. `parseConfig` validates the value by constructing a probe `Intl.DateTimeFormat` and throws with a clear error on `RangeError` (invalid IANA zone) — bot exits before init. | +| `--complete-hours` | No | `3` | integer ≥ 0 | Hours of customer inactivity after last team/Grok reply before auto-completing a conversation (✅). `parseConfig` rejects non-numeric, negative, or `NaN` values with a fail-fast error. `0` is allowed and disables auto-complete. | +| `--card-flush-seconds` | No | `300` | integer ≥ 0 | Seconds between card dashboard update flushes. `parseConfig` rejects non-numeric, negative, or `NaN` values with a fail-fast error. `0` is allowed and disables periodic flush (card updates still occur on explicit `scheduleUpdate` callers but never auto-drain). | + +**Env vars:** `GROK_API_KEY` (optional) — xAI API key. If unset or empty, the bot starts with Grok support fully disabled: it logs `"No GROK_API_KEY provided, disabling Grok support"`, skips Grok profile/contact setup and event handler registration, omits `/grok` from the bot command list, drops the `/grok` clause from customer-facing messages, and treats any `/grok` the customer still types as an unknown command. + +**Numeric argument validation:** `parseConfig` MUST validate every numeric flag (`--complete-hours`, `--card-flush-seconds`) using a helper that throws on non-finite or negative results, rather than raw `parseInt`: + +```typescript +function parseNonNegativeInt(raw: string, flag: string): number { + const n = parseInt(raw, 10) + if (!Number.isFinite(n) || n < 0) { + throw new Error(`${flag} must be a non-negative integer, got "${raw}"`) + } + return n +} + +const completeHours = parseNonNegativeInt(optionalArg(args, "--complete-hours", "3"), "--complete-hours") +const cardFlushSeconds = parseNonNegativeInt(optionalArg(args, "--card-flush-seconds", "300"), "--card-flush-seconds") +``` + +Rationale: `parseInt("foo", 10)` returns `NaN`, and `NaN * 3600_000 === NaN`. Every subsequent comparison (`now - lastTeamGrokTime >= completeMs`) is `false`, so the feature silently becomes a no-op — auto-complete never fires, cards never auto-refresh — and the operator has no signal that they typo'd a flag. Failing fast at startup surfaces the typo before customers interact. `0` is explicitly allowed as a valid "disable" setting. + +**Timezone validation:** `parseConfig` MUST validate `--timezone` by constructing a probe `Intl.DateTimeFormat`: + +```typescript +try { + new Intl.DateTimeFormat("en-US", {timeZone: timezone, weekday: "short"}) +} catch (err) { + throw new Error(`--timezone "${timezone}" is not a valid IANA time zone: ${(err as Error).message}`) +} +``` + +Rationale: `isWeekend` is called from `queueMessage` and `teamAddedMessage` — both run on the hot customer message path. `new Intl.DateTimeFormat(..., {timeZone: , ...})` throws `RangeError: Invalid time zone specified` at every call. Without startup validation, a typo in `--timezone` turns every `/grok`, `/team`, or first-customer-message dispatch into an unhandled error that crashes the per-item handler (though the outer try/catch in `onNewChatItems` contains it, customers receive no reply at all). Validating once at startup surfaces the typo in the operator's console before any customer interaction. + +```typescript +interface Config { + dbPrefix: string + teamGroup: {id: number; name: string} // id=0 at parse time, resolved at startup + teamMembers: {id: number; name: string}[] + grokContactId: number | null // always restored from state file at startup (even when Grok API is disabled, so the one-way gate can identify and remove Grok members) + timezone: string + completeHours: number // default 3 + cardFlushSeconds: number // default 300 + contextFile: string | null // path to Grok system-prompt file; required when grokApiKey !== null + grokApiKey: string | null // null when GROK_API_KEY is not set → Grok disabled +} +``` + +**State file** — `{dbPrefix}_state.json` (co-located with DB files): +```json +{"teamGroupId": 123, "grokContactId": 4} +``` + +Only two keys. All other state is persisted in the group's `customData` (per-conversation state, card IDs) or derived from group metadata (`apiListMembers`). Display data like message counts is read from chat history on demand. + +**Grok contact resolution** (state-file lookup always runs; contact establishment only when enabled): +1. Read `grokContactId` from state file → validate via `apiListContacts` → set `config.grokContactId` (this always runs, even when `grokApiKey === null`, so the one-way gate can identify and remove Grok members from groups) +2. If not found and `grokEnabled`: main profile creates one-time invite link, Grok profile connects, wait for a `contactConnected` event filtered by profile identity (60s — see "Grok contact identification" below), persist the resulting `contactId` atomically before proceeding. +3. If unavailable (with Grok otherwise enabled), bot runs but `/grok` returns "temporarily unavailable" +4. If `grokApiKey === null`: the Grok profile is not resolved or created, no invite link is issued — but `config.grokContactId` is still set from the state file if the contact exists. + +### Grok contact identification + +`grokContactId` is written once and used forever — it is the single identifier for every subsequent Grok check (one-way gate, `onMemberConnected` skip, `isGrok` in card rendering). Identification MUST be narrowly scoped so that the `contactId` stored is unambiguously Grok's and no other contact completing a handshake in the 60s establishment window can be latched by mistake. + +Use the predicate form of `ChatApi.wait`. The signature (defined in `node_modules/simplex-chat/src/api.ts:217`) is: + +```typescript +wait( + event: K, + predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined, + timeout: number, +): Promise +``` + +The implementation (api.ts:234) keeps the subscriber attached when the predicate returns `false`, so non-matching events are silently discarded and the wait continues until a matching event arrives or the timeout fires. + +Identification accepts only a `contactConnected` event observed by the MAIN profile (the profile whose `apiCreateLink` issued the invite, and whose `contactId` we persist and later pass to `apiAddMember`) whose connecting contact's profile `displayName` equals the Grok profile's displayName: + +```typescript +const grokProfileName = grokUser.profile.displayName // "Grok" (canonical) +const evt = await chat.wait( + "contactConnected", + (e) => + e.user.userId === mainUser.userId && + e.contact.profile.displayName === grokProfileName, + 60_000, +) +if (!evt) { + console.error(`Timeout waiting for Grok contact (60s, displayName="${grokProfileName}"). ` + + `Check SMP relay availability or re-run after clearing state. Exiting.`) + process.exit(1) +} +config.grokContactId = evt.contact.contactId +state.grokContactId = config.grokContactId +writeState(stateFilePath, state) // atomic: tmp-file + rename (see §13 state persistence) +log(`Grok contact established: ID=${config.grokContactId} (displayName="${grokProfileName}")`) +``` + +Filter rationale: +- `e.user.userId === mainUser.userId` selects the main profile's view of the handshake. Both profiles observe the handshake (the Grok-side event describes the main profile as the `contact`); only the main-side event carries the `contactId` we need for subsequent `apiAddMember` calls. +- `e.contact.profile.displayName === grokProfileName` accepts only the contact whose profile matches the Grok profile just created/updated. This rejects stray inbound contacts (late business-request acceptance, operator test DM, a reconnect of an existing contact) that may complete in the same 60s window. The displayName is read from `evt.contact.profile`, which is `LocalProfile` (see `@simplex-chat/types/src/types.ts:2867`). + +`grokProfileName` is captured from `grokUser.profile.displayName` immediately before the wait, so whichever name the Grok profile was created/updated with earlier in startup is the exact string matched here. + +Single-tenant deployment caveat: if a human contact happens to set its SimpleX displayName to the literal `"Grok"` and completes a handshake with the main profile in the 60s window, the displayName filter alone cannot distinguish them. MVP is single-tenant and Grok's profile is created by the bot itself, so this is not expected in practice; deployments that need stronger guarantees can add a second filter (e.g. `e.contact.profile.image === grokImage` — the bot knows the exact image bytes it assigned to the Grok profile). + +Persistence: `writeState` is atomic (tmp-file + `fs.renameSync`, see §13 "State persistence") so a crash between identification and persistence cannot corrupt the state file. `state.grokContactId` is flushed to disk BEFORE proceeding to bot event wiring — if the process dies after wiring but before persistence, the next startup would issue a second invite link and leave the first Grok contact orphaned in the database. + +**Team group resolution** (auto-create): +1. Read `teamGroupId` from state file → validate via group list +2. If not found: create with `apiNewGroup`, persist new group ID +3. If found: compare `fullGroupPreferences` (directMessages, fullDelete, commands) and displayName with desired values. Only call `apiUpdateGroupProfile` if something differs — avoids unnecessary SMP relay round-trips on every restart. + +**Team group invite link lifecycle:** +1. Delete stale link (best-effort), create new link, print to stdout. Creation is best-effort — if the SMP relay is unreachable, the error is logged and the bot continues without an invite link. The 10-minute deletion timer is only scheduled if creation succeeded. +2. Delete after 10 minutes. On SIGINT/SIGTERM, delete before exit. Deletion must go through `profileMutex` with `apiSetActiveUser(mainUserId)` — the active user may be the Grok profile at the time the timer fires or the signal arrives. + +**Team member validation:** +- If `--auto-add-team-members` (`-a`) provided: validate each contact ID/name pair, fail-fast on mismatch +- If not provided: `/team` tells customers "no team members available yet" + +## 5. State Derivation (Stateless) + +Per-conversation state is stored in the group's `customData` and written at the moment the bot handles each transition (customer's first message, `/grok`, `/team`, team member's first message). On subsequent events `deriveState` returns the stored state as-is — composition changes (team members leaving, Grok leaving) do **not** demote the stored state. The customer's mode (e.g. "waiting for a team response") is meaningful even when no team member is currently present; keeping the state preserves that. Composition is read only by specific handlers (e.g. the `/team` duplicate-invite guard). No chat-history scans for state decisions. No in-memory conversations map — survives restarts. + +**WELCOME detection:** customData has no `state` field until the bot handles the first transition. `deriveState` returns `WELCOME` precisely when `customData.state` is absent. + +**Type vs. persisted state.** The `ConversationState` union in `cards.ts` enumerates all five conceptual states (`WELCOME | QUEUE | GROK | TEAM-PENDING | TEAM`) so event handlers and composition can reason about them uniformly. However, `WELCOME` is NEVER written to `customData.state` — the runtime invariant is "persisted state ∈ {QUEUE, GROK, TEAM-PENDING, TEAM}; absence of the `state` field derives as WELCOME". The `isConversationState` guard in `cards.ts` rejects `WELCOME` on read to preserve this invariant (any stale `state: "WELCOME"` from a crashed transition is treated as absent). Do NOT introduce a separate `PersistedState` type in MVP — the invariant is small enough to enforce at two choke points: `getRawCustomData` on read and the dispatch handlers on write. + +**State-write matrix:** + +| Bot-observed event | `customData.state` written | +|---|---| +| *(initial — no customData yet)* | *(absent ⇒ WELCOME)* | +| Customer's first non-command message | `QUEUE` | +| `/grok` handled — Grok invited | `GROK` | +| `/team` handled — team members added (written at handler time; does not wait for team acceptance) | `TEAM-PENDING` | +| First team-member text message observed | `TEAM` | + +**State is authoritative and monotonic.** Once written, `customData.state` persists across member leave/join events. The only path that clears it is the existing `onLeftMember` handler when the customer themselves leaves — at that point the entire customData is cleared. + +**Failure-path revert is CAS-guarded.** `activateGrok` runs fire-and-forget, so its `setStateOnFail` revert (`QUEUE`) can race with a concurrent transition (e.g. `/team` writing `TEAM-PENDING` while `waitForGrokJoin` is pending). To preserve monotonicity, `revertStateOnFail` is a compare-and-set: it only writes `setStateOnFail` if `customData.state === "GROK"` (the optimistic value both call sites write before invoking `activateGrok`). If another handler has since stamped a different state, the revert is skipped — the in-flight transition wins and stays. + +TEAM-PENDING takes priority over GROK when both Grok and team are present (after `/team` but before team member's first message). `/grok` remains available in TEAM-PENDING — if Grok is not yet in the group, it gets invited; if already present, the command is ignored. + +**State derivation helpers:** +- `getGroupComposition(groupId)` → `{grokMember, teamMembers}` from `apiListMembers` — used for card rendering and the `/team` duplicate-invite guard. +- `deriveState(groupId)` → reads `customData.state`. Returns `WELCOME` iff `customData.state` is absent. No composition lookup. +- `getLastCustomerMessageTime(groupId)` / `getLastTeamOrGrokMessageTime(groupId)` → chat-history timestamp reads used by the card renderer for wait-time and auto-complete only (display, not state). + +**Transitions:** +``` +WELCOME ──(1st msg)──────> QUEUE (send queue msg, create card 🆕) +WELCOME ──(/grok 1st)────> GROK (skip queue msg, create card 🤖) +WELCOME ──(/team 1st)────> TEAM-PENDING (skip queue msg, add team members, create card 👋) +QUEUE ──(/grok)──────────> GROK (invite Grok, update card) +QUEUE ──(/team)──────────> TEAM-PENDING (add team members, update card) +GROK ──(/team)───────────> TEAM-PENDING (add all team members, Grok stays, update card) +GROK ──(user msg)────────> GROK (Grok responds, update card) +TEAM-PENDING ──(/grok)───> invite Grok if not present, else ignore (state stays TEAM-PENDING) +TEAM-PENDING ──(/team)───> reply "already invited" (if team members still present; else re-add silently) +TEAM-PENDING ──(team msg)> TEAM (remove Grok, disable /grok permanently, update card) +TEAM ──(/grok)───────────> reply "team mode", stay TEAM +``` + +## 6. Card-Based Dashboard + +The team group is a live dashboard. The bot maintains exactly one message ("card") per active customer conversation. Cards are deleted and reposted on changes — the group is always a current snapshot. + +### Card format + +Card is a single message. The join command is the final line of the card text — there is no separate join message. + +``` +[ICON] *[Customer Name]* · [wait] · [N msgs] +[STATE][· agent1, agent2, ...] +"[last message(s), truncated]" +/'join [id]' +``` + +**Icons:** + +| Icon | Condition | +|------|-----------| +| 🆕 | QUEUE — first message < 5 min ago | +| 🟡 | QUEUE — waiting < 2 h | +| 🔴 | QUEUE — waiting > 2 h | +| 🤖 | GROK — Grok handling | +| 👋 | TEAM — team added, no reply yet | +| 💬 | TEAM — team has replied, conversation active (customer replied after team) | +| ⏰ | TEAM — customer follow-up unanswered > 2 h | +| ✅ | Done — no customer reply for `completeHours` (default 3h) after last team/Grok message | + +**State labels:** `Queue`, `Grok`, `Team – pending`, `Team` + +**Agents:** comma-separated display names of team members in the group. Omitted when none. + +**Message count:** All messages in chat history except the bot's own (`groupSnd` from main profile). + +**Message preview:** Last several messages, most recent last, separated by ` / `. Newlines in message text are replaced with spaces to prevent card layout bloat from spam. The customer's display name is sanitized (newlines → spaces) for the card header; the `/join` command embeds only the numeric group id. Newest messages are prioritized — when the total exceeds ~500 chars (`maxTotal = 500` in `composeCard`), the oldest messages are truncated (with `[truncated]` prepended) while the newest are always shown. When truncation occurs, the first visible message is guaranteed to have a sender prefix even if it was a continuation in the original sequence. Each message is prefixed with the sender's name (`Name: message`) on the first message in a consecutive run from that sender - subsequent messages from the same sender omit the prefix until a different sender's message appears. Sender identification: Grok contact is detected by `grokContactId` and labeled "Grok"; the customer is identified by matching `memberId` to the group's `customerId` and labeled with their display name; all other members use their `memberProfile.displayName`. Bot's own messages (`groupSnd`) are excluded. Each message truncated to ~200 chars. Media-only messages show type labels: `[image]`, `[file]`, `[voice]`, `[video]`. + +**Join command:** the final line of the card renders as `/'join '` where `` is the customer group's numeric ID. The outer single quotes around `join ` are rendered by SimpleX clients as a clickable quoted command; tapping it sends `/join ` back to the team group. The handler does not pattern-match the message text — it uses the framework's structured command parser (`util.ciBotCommand`) which returns `{keyword: "join", params: ""}` directly from the chat item. The handler then converts `params` to an integer via `Number.parseInt(params, 10)` and rejects anything that is not a positive integer. There is no legacy `/join :` form — the card never emits it, so the handler never needs to strip it. + +### Card lifecycle + +**Tracking:** `{state, cardItemId, complete?}` stored in customer group's `customData` via `apiSetGroupCustomData`. `state` is the canonical conversation state (`QUEUE | GROK | TEAM-PENDING | TEAM`); `cardItemId` is the team-group chat item ID for the (single) card message; `complete` flags the auto-completed state. Absence of `state` means WELCOME. Written at event time by the dispatch handlers — `/grok` handler writes `GROK` on invite; `/team` handler writes `TEAM-PENDING` immediately (does not wait for team acceptance); first observed team-member text message writes `TEAM`; first customer text message writes `QUEUE`. Read back from `groupInfo.customData` — single source of truth, survives restarts. All writes go through `CardManager.mergeCustomData` to preserve fields across independent write paths. + +**Create** — on first customer message (→ QUEUE) or `/grok` as first message (→ GROK): +1. Compose card text (including the `/'join '` final line) +2. Post it via `apiSendMessages(chatRef, [{msgContent: {type: "text", text}, mentions: {}}])` → get one `chatItemId`. The card is a single message; the `/'join '` line is clickable because SimpleX clients render the slash-prefixed single-quoted token as a clickable command even inside a multi-line message. +3. Write `{cardItemId}` to customer group's `customData` + +**Update** (delete + repost) — on every subsequent event (new customer msg, team/Grok reply, state change, agent join): +1. Read `{cardItemId}` from `customData` +2. Delete old card via `apiDeleteChatItems([Group, teamGroupId], [cardItemId], "broadcast")`. Per `simplex-chat/src/api.ts:436-445` the call either returns `T.ChatItemDeletion[]` (possibly empty if the item no longer exists) or throws `ChatCommandError`. Both outcomes are acceptable: the surrounding `try { ... } catch { /* log and continue */ }` allows execution to proceed whether the item was still present, already gone, or the server returned a transient error. +3. Post new card as a single message via `apiSendMessages` → get new `cardItemId`. **On failure** the partial-failure policy below applies: log, re-queue this groupId into `pendingUpdates`, return without writing `customData`. +4. Write `{cardItemId, complete?}` to `customData` via `mergeCustomData`. **On failure** the tracking-write policy below applies. + +**Debouncing:** Card updates debounced globally — pending changes flushed every `cardFlushSeconds` seconds (default 300, configurable via `--card-flush-seconds`). Within a batch, each group's card reposted at most once with latest state. + +**Wait time rules:** Time since the customer's last unanswered message. For ✅ (auto-completed) conversations, the wait field shows the literal string "done". If customer sends a follow-up, wait time resets to count from that message. + +**Auto-complete:** A conversation is marked ✅ when `completeHours` (default 3h, configurable via `--complete-hours`) have passed since the last team/Grok message **without any customer reply**. The card debounce flush (every 300 seconds / 5 min, configurable via `--card-flush-seconds`) checks elapsed time and transitions to ✅ when the threshold is met. Customer follow-up at any point — including after ✅ — reverts to the derived active icon (👋/💬/⏰ for team states, 🟡/🔴 for queue), and wait time resets from that message. + +**Card icon derivation (TEAM states) — computed at each card render by comparing the timestamps of the most recent customer and team/Grok messages in the group; nothing about the icon is stored:** +``` +Team added, no reply yet → 👋 +Team replied → 💬 +Customer follow-up unanswered >2h → ⏰ +No customer reply for completeHours → ✅ +Customer sends after ✅ → back to 💬 or ⏰ (derived from wait time) +``` + +**Cleanup** — customer leaves: card remains (TBD retention), clear `customData`. + +**Restart recovery:** On startup, `CardManager.refreshAllCards()` lists all groups, finds those with `customData.cardItemId` set and `customData.complete` not set, sorts by `cardItemId` ascending (higher ID = more recently updated), and re-posts them oldest-first so the most recently active cards end up at the bottom of the team group. Completed cards (`complete: true`) and old/pre-bot groups (no `customData`) are skipped. Old card messages are deleted before reposting; deletion failures (e.g., >24h old) are silently ignored. Individual card failures are caught and logged without aborting the batch. + +### Partial-failure and retry policy + +`createCard` and `updateCard` perform a multi-step sequence (delete + send + customData write). To design the correct policy we MUST be explicit about which failures the SimpleX core already handles for us vs. which surface to the bot: + +**SimpleX core semantics** (per `simplex-chat/src/api.ts` JSDoc): +- `apiSendMessages` — "Network usage: background". The call returns `newChatItems` once the chat item is CREATED LOCALLY (written to SQLite) and the SMP broadcast is QUEUED. The core's background machinery retries relay delivery transparently — **the bot never observes a transient relay failure from `apiSendMessages`**. A thrown `ChatCommandError` means the local create step itself failed: permission denied, chat does not exist, invalid content, DB locked/corrupted. +- `apiDeleteChatItems` — "Network usage: background". Same pattern: local delete + queued broadcast + core-managed delivery retry. A thrown error means the local delete step failed (item not found, permission, DB error). +- `apiSetGroupCustomData` — "Network usage: **no**". Pure local SQLite write, no SMP involvement at all. A thrown error means a local DB error. + +Consequence: failures surfaced to the bot are **terminal local errors** (bad state, DB problem, permission change), not transient network blips. Retrying the same operation against the same DB/relay state will usually hit the same error. Retry value comes from the narrow slice of genuinely transient local conditions — a brief SQLite lock held by a concurrent write, a race with group-state mutation elsewhere in the same process — where the next attempt sees a different state. + +This reshapes the policy: the bot does not need aggressive retry for "network" reasons (core handles that), and compensating actions for customData-write failure are rarely useful (if the pure-local customData write fails, the retry's customData write will almost certainly fail for the same reason). The bot needs a light safety net: re-queue on any step failure, let the flush loop try again at most once per `cardFlushSeconds`, and on persistent failure accept that operator intervention is needed. + +Policy (applies to both `createCard` and `updateCard`): + +**Any step fails** — whether step 2 (delete), step 3 (send), or step 4 (customData write): +- Log via `logError` with `{groupId, step, err}` so the operator can diagnose the underlying cause (permission change, DB corruption, bot removed from team group, etc). +- Re-add `groupId` to `pendingUpdates` via `this.scheduleUpdate(groupId)`. +- Return. Do NOT attempt compensating actions (no compensating delete for tracking-write failure — the scenario where send succeeds locally but customData write fails requires the SQLite DB to be healthy-then-unhealthy between two synchronous calls in the same transaction window, which is not a realistic transient state; the retry path handles any resulting duplicate by reading the stale `cardItemId` and deleting it on the next update attempt). + +**Flush dispatch** — the current `flush` loop calls `updateCard` unconditionally and `updateCard` returns early when `customData.cardItemId` is unset. This silently drops the retry path for a failed `createCard` — the group is in `pendingUpdates` but nothing will ever create a card for it. Replace with a single `flushOne(groupId)` that reads `customData` once and dispatches to create or update: + +```typescript +private async flushOne(groupId: number): Promise { + const groupInfo = await this.getGroupInfo(groupId) + if (!groupInfo) return // group deleted + const customData = this.deriveCustomData(groupInfo) + if (customData.complete) return // ✅ conversations don't auto-repost + if (typeof customData.cardItemId === "number") { + await this.updateCard(groupId, groupInfo) + } else { + await this.createCard(groupId, groupInfo) + } +} + +async flush(): Promise { + const groups = [...this.pendingUpdates] + this.pendingUpdates.clear() + for (const groupId of groups) { + try { await this.flushOne(groupId) } + catch (err) { + logError(`flush failed for group ${groupId}`, err) + this.scheduleUpdate(groupId) // re-queue on any thrown error + } + } +} +``` + +Retry behavior for each failure point under this design: + +| Failure point | `customData` after failure | Retry's `flushOne` path | Retry outcome if condition cleared | +|---|---|---|---| +| `createCard` send fails | `cardItemId` absent | create-path | fresh card posted, `customData` written | +| `updateCard` delete fails | old `cardItemId` still set | update-path | delete retried (idempotent — see below) + send + write | +| `updateCard` send fails (delete succeeded) | old (now-deleted) `cardItemId` still set | update-path | delete retried against stale ID — tolerated (see below) — then send + write | +| `updateCard` write fails (send succeeded, duplicate may exist) | old `cardItemId` still set, new card orphaned in team group | update-path | delete retried against stale old ID — tolerated — new card posted, tracking written; **leaked** new card from the failed attempt persists until operator removes it | + +**Delete idempotency on retry** — `apiDeleteChatItems` against already-deleted IDs returns either an empty `ChatItemDeletion[]` or throws `ChatCommandError`. The step-2 `try { ... } catch { logError(...) }` swallows both; execution proceeds to step 3. Do NOT escalate a step-2 error to the partial-failure policy — that would create a retry loop for a permanent condition (items past the 24h deletion window will throw on every retry forever). + +**Persistent failures** — if the underlying condition is not transient (bot removed from team group, DB corruption, permission revoked), every retry hits the same error and the group stays in `pendingUpdates` indefinitely, logging at each flush. MVP accepts this — the operator-visible log stream makes the problem diagnosable. A bounded-retry-with-backoff-and-giveup strategy can be added later without changing the failure-point table above. + +### Card implementation + +```typescript +class CardManager { + private pendingUpdates = new Set() // groupIds with pending updates + private flushInterval: NodeJS.Timeout + + constructor(private chat: ChatApi, private config: Config, private mainUserId: number, + flushIntervalMs = 300 * 1000) { + this.flushInterval = setInterval(() => this.flush(), flushIntervalMs) + this.flushInterval.unref() + } + + scheduleUpdate(groupId: number): void { + this.pendingUpdates.add(groupId) + } + + async createCard(groupId: number, groupInfo: T.GroupInfo): Promise { + const {text} = await this.composeCard(groupId, groupInfo) + // Single-message card — the `/'join '` line is the final line of `text`. + const items = await this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + await this.chat.apiSetGroupCustomData(groupId, { + cardItemId: items[0].chatItem.meta.itemId, + }) + } + + async flush(): Promise { + const groups = [...this.pendingUpdates] + this.pendingUpdates.clear() + for (const groupId of groups) { + await this.updateCard(groupId) + } + } + + async refreshAllCards(): Promise { + const groups = await this.chat.apiListGroups(mainUserId) + const activeCards = groups + .filter(g => typeof g.customData?.cardItemId === "number" && !g.customData?.complete) + .map(g => ({groupId: g.groupId, cardItemId: g.customData.cardItemId})) + // Sort ascending by cardItemId (higher = more recently updated) + activeCards.sort((a, b) => a.cardItemId - b.cardItemId) + for (const {groupId} of activeCards) { + try { await this.updateCard(groupId) } + catch (err) { logError(`Startup card refresh failed for group ${groupId}`, err) } + } + } + + private async updateCard(groupId: number): Promise { + // Read customData via apiListGroups + const customData = ... // {cardItemId} from groupInfo.customData + if (!customData?.cardItemId) return + // Delete old card message + try { + await this.chat.apiDeleteChatItems(Group, teamGroupId, + [customData.cardItemId], "broadcast") + } catch {} // card may already be deleted + const {text, complete} = await this.composeCard(groupId, groupInfo) + const items = await this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + const data = { + cardItemId: items[0].chatItem.meta.itemId, + ...(complete ? {complete: true} : {}), + } + await this.chat.apiSetGroupCustomData(groupId, data) + } + + private async composeCard(groupId: number, groupInfo: T.GroupInfo): Promise<{text: string, complete: boolean}> { + // Icon, state, agents, preview (with sender-name prefixes), /'join ' — per spec format + // The final line of `text` is `/'join '` — clickable in SimpleX clients. + // buildPreview(chatItems, customerName, customerId) — prefixes each sender's first message in a run + // Preview messages joined with blue "/" separator: " !3 /! " (SimpleX markdown for blue colored text) + // Message text is escaped via escapeStyledMarkdown() before joining — inserts U+200B after "!" + // when followed by a color trigger (1-6,r,g,b,y,c,m,-) to prevent false markdown interpretation. + // No escape mechanism exists in the SimpleX markdown parser for "!" styled text. + // complete = (icon === "✅") + } +} +``` + +## 7. Bot Initialization + +**Main bot** uses `bot.run()` with `events` parameter: + +```typescript +let supportBot: SupportBot + +const [chat, mainUser, mainAddress] = await bot.run({ + profile: {displayName: "Ask SimpleX Team", fullName: "", image: supportImage}, + dbOpts: {dbFilePrefix: config.dbPrefix}, + options: { + addressSettings: { + businessAddress: true, + autoAccept: true, + welcomeMessage, + }, + commands: [ + {type: "command", keyword: "grok", label: "Ask Grok"}, + {type: "command", keyword: "team", label: "Switch to team"}, + ], + useBotProfile: true, + updateProfile: false, // bot code never rewrites displayName/image/etc. + }, + events: { + acceptingBusinessRequest: (evt) => supportBot?.onBusinessRequest(evt), + newChatItems: (evt) => supportBot?.onNewChatItems(evt), + chatItemUpdated: (evt) => supportBot?.onChatItemUpdated(evt), + chatItemReaction: (evt) => supportBot?.onChatItemReaction(evt), + leftMember: (evt) => supportBot?.onLeftMember(evt), + joinedGroupMember: (evt) => supportBot?.onJoinedGroupMember(evt), + connectedToGroupMember: (evt) => supportBot?.onMemberConnected(evt), + newMemberContactReceivedInv: (evt) => supportBot?.onMemberContactReceivedInv(evt), + contactConnected: (evt) => supportBot?.onContactConnected(evt), + contactSndReady: (evt) => supportBot?.onContactSndReady(evt), + }, +}) +``` + +Note: `/grok` and `/team` are passed in `options.commands` so `bot.run()` has a profile to use when `apiCreateActiveUser` is needed on a fresh DB, but since `updateProfile: false` is set, `bot.run()` never writes the profile on subsequent runs. The user profile's `preferences.commands` is intentionally not pushed globally at startup — broadcasting `XInfo` to every contact is not wanted. Instead, the `SupportBot` takes `desiredCommands` as a constructor argument and syncs commands lazily per-group: `sendToGroup` (`src/bot.ts`) always calls `syncGroupCommands(groupId)` before dispatching the message. That helper reads the group via `apiGetChat(Group, groupId, 0)` (local, no network), and if `groupPreferences.commands` differs from `desiredCommands`, issues `apiUpdateGroupProfile` with the merged profile. `apiUpdateGroupProfile` broadcasts `XGrpInfo`/`XGrpPrefs` to group members only (scoped to the chat audience). Already-synced groups are cached in `syncedGroups: Set` so subsequent sends skip the read entirely — the first send per group costs one local read; every later send is a cache hit. Earlier drafts used a regex on the outgoing text to skip the sync when no command keyword appeared; that optimization was removed because the cache already makes repeated syncs free and the parser was a fragile source of correctness bugs. `/join` is registered as a team group command separately — after team group is resolved, call `apiUpdateGroupProfile(teamGroupId, groupProfile)` with `groupPreferences` including the `/join` command definition. Customer sending `/join` in a customer group → treated as ordinary message (unrecognized command). + +**Grok profile** — resolved from same ChatApi instance. Grok is identified strictly by the `userId` persisted in `state.json`; there is no by-name fallback (a renamed profile would otherwise be silently mistaken): + +```typescript +let grokUser: T.User | null = null +if (state.grokUserId !== undefined) { + const users = await chat.apiListUsers() + grokUser = users.find(u => u.user.userId === state.grokUserId)?.user ?? null + if (!grokUser) { + throw new Error( + `Persisted Grok userId=${state.grokUserId} not found in DB. ` + + `Either restore the user or delete state.json to re-create Grok.` + ) + } +} else { + // First run: create Grok and persist its userId immediately. + grokUser = await chat.apiCreateActiveUser({displayName: "Grok", fullName: "", image: grokImage}) + // apiCreateActiveUser sets Grok as active — switch back to main + await chat.apiSetActiveUser(mainUser.userId) + state.grokUserId = grokUser.userId + writeState(stateFilePath, state) +} + +// Refresh Grok's profile if it has drifted from the canonical values. +const grokProfile = {displayName: "Grok", fullName: "", image: grokImage} +const current = util.fromLocalProfile(grokUser.profile) +if (current.image !== grokProfile.image || current.displayName !== grokProfile.displayName || current.fullName !== grokProfile.fullName) { + await chat.apiSetActiveUser(grokUser.userId) + await chat.apiUpdateProfile(grokUser.userId, grokProfile) + await chat.apiSetActiveUser(mainUser.userId) +} +``` + +**Profile mutex** — all SimpleX API calls go through: + +```typescript +import {Mutex} from "async-mutex" + +const profileMutex = new Mutex() + +async function withProfile(userId: number, fn: () => Promise): Promise { + return profileMutex.runExclusive(async () => { + await chat.apiSetActiveUser(userId) + return fn() + }) +} +``` + +Grok HTTP API calls are made **outside** the mutex to avoid blocking. + +**Per-group customData mutex** — `mergeCustomData` and `clearCustomData` must be serialized per customer group. `mergeCustomData` has two awaits (read via `getRawCustomData` → `apiListGroups`, then write via `apiSetGroupCustomData`); between them the event loop runs, so two concurrent async chains operating on the same `groupId` can both read the same snapshot, both produce a merged object, and the second write clobbers the first's patch. + +Concrete call sites that can overlap on one `groupId`: +- `processMainChatItem` writing `state` transitions (WELCOME→QUEUE, WELCOME→GROK, QUEUE→GROK, one-way gate →TEAM) +- `activateGrok`'s `revertStateOnFail` (fire-and-forget) racing with subsequent customer messages +- `activateTeam` writing `TEAM-PENDING` racing with `/grok` or another `/team` on the same group +- `CardManager.flush → updateCard` writing `{cardItemId, complete}` racing with dispatch writing `state` +- `createCard` writing `{cardItemId}` immediately after dispatch writes `state` + +The CAS-on-state inside `revertStateOnFail` guards only the `state` key — other keys (`cardItemId`, `complete`) can still be lost when spread from a stale snapshot. + +Implementation: + +```typescript +// In CardManager +private customDataMutexes = new Map() + +private getCustomDataMutex(groupId: number): Mutex { + let m = this.customDataMutexes.get(groupId) + if (!m) { m = new Mutex(); this.customDataMutexes.set(groupId, m) } + return m +} + +async mergeCustomData(groupId: number, patch: Partial): Promise { + return this.getCustomDataMutex(groupId).runExclusive(async () => { + const current = (await this.getRawCustomData(groupId)) ?? {} + const merged = {...current, ...patch} + for (const key of Object.keys(merged) as (keyof CardData)[]) { + if (merged[key] === undefined) delete merged[key] + } + await this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId, merged)) + }) +} + +async clearCustomData(groupId: number): Promise { + return this.getCustomDataMutex(groupId).runExclusive(() => + this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId)) + ) +} +``` + +Nesting rule: the per-group customData mutex is the **outer** lock; `profileMutex` (via `withMainProfile`) is the **inner** lock. Never acquire them in the opposite order, and never hold the customData mutex while calling an external (non-SimpleX) async function — this prevents cross-group deadlock and keeps the critical section short. + +Cleanup: entries in `customDataMutexes` are bounded by the number of customer groups. Removing the entry on `onLeftMember(customer)` is sufficient (the group's `customData` is also cleared at that point). Skip this refinement in MVP if acceptable — a long-running bot with many customers accumulates a few bytes per group. + +**Profile images:** Both profiles have base64-encoded JPEG profile pictures (128x128, quality 85, under the 12,500-char data URI limit enforced by iOS/Android clients) set via the `image` field in `T.Profile`. The images are defined as `data:image/jpg;base64,...` string constants in `index.ts`. The main profile image is passed to `bot.run()` which handles update-on-change automatically. The Grok profile image is passed to `apiCreateActiveUser()` on first run; on subsequent runs, the bot compares the current profile against the desired one using `util.fromLocalProfile()` and calls `apiUpdateProfile()` if any field differs — this sends the update to all Grok contacts. + +**Startup sequence:** +0. **Active user recovery + name preservation:** Two related safeguards. + + **(a) Active user recovery.** On restart, the active user may be Grok (if the previous run was killed mid-profile-switch). `bot.run()` uses `apiGetActiveUser()` and would then operate against Grok's `userId` as if it were the main user. Fix: when `state.grokUserId` is set (i.e. this is not the very first run), pre-init the DB with a temporary `ChatApi` and compare the active user's `userId` against `state.grokUserId`. If they match, `apiListUsers()` + `apiSetActiveUser()` to the single non-Grok user — throw loudly if zero or multiple candidates exist, rather than silently picking. Close the temporary `ChatApi` before `bot.run()` reopens it. Identification is by userId, never by display name; a renamed Grok profile would defeat name matching. + + **(b) Never rewrite the main profile.** The core auto-creates a preset contact named `"Ask SimpleX Team"` in every user's DB (`src/Simplex/Chat/Library/Internal.hs:2749`, exact name from commit `362bdc328` 2025-07-12). That collides with the bot's preferred main-profile displayName within the user's `display_names` namespace (`UNIQUE (user_id, local_display_name)`), so any attempt to rename the main profile to `"Ask SimpleX Team"` fails with `duplicateName`. Worse, `bot.run`'s internal `updateBotUserProfile` (`packages/simplex-chat-nodejs/dist/bot.js:176`) re-syncs image, preferences, and `contactLink` on every startup, and on a DB where `users.local_display_name` has drifted from `contact_profiles.display_name`, the fast path (`src/Simplex/Chat/Store/Profiles.hs:311`) silently rewrites the customer-facing `contact_profiles.display_name`. Fix: pass `options.updateProfile: false` to `bot.run()` so the bot code never calls `apiUpdateProfile` on its own initiative. Whatever displayName the CLI saw is what stays. + + **(c) Lazy per-group command sync.** The bot's command list (`/grok`, `/team`) is synced lazily and per-group, not globally. `sendToGroup` (in `src/bot.ts`) unconditionally calls `syncGroupCommands(groupId)` before dispatching the message. That helper uses `apiGetChat(Group, groupId, 0)` (local DB read, no network) to read the current `groupProfile.groupPreferences.commands`, and if it doesn't match `desiredCommands`, issues `apiUpdateGroupProfile` with the commands merged in. `apiUpdateGroupProfile` broadcasts `XGrpInfo`/`XGrpPrefs` to group members only — scoped to the chat audience, never the whole contact list. Groups confirmed in-sync are cached in `syncedGroups: Set` so the first send per group costs one local read; every later send is a cache hit. No `apiUpdateProfile` (global XInfo broadcast) is ever invoked by bot code. Earlier drafts gated the sync behind a regex match on the outgoing text (to skip the read when no `/keyword` appeared); that optimization was removed because the cache already made repeated syncs free and the parser was a fragile source of correctness bugs. +1. `bot.run()` → init ChatApi, create/resolve main profile (with profile image), business address. Print business address link to stdout. +2. Resolve Grok profile: if `state.grokUserId` is set, look it up by ID via `apiListUsers()` (throw if missing); otherwise create via `apiCreateActiveUser()` and persist the new `userId`. Then compare the resolved profile against the canonical `{displayName, fullName, image}` and call `apiUpdateProfile()` if anything changed — pushes to Grok's contacts. +3. Read `{dbPrefix}_state.json` for `teamGroupId` and `grokContactId` +4. Enable auto-accept DM contacts: `apiSetAutoAcceptMemberContacts(mainUser.userId, true)` +5. List contacts, resolve Grok contact (from state or auto-establish) +6. Resolve team group (from state or auto-create) +7. Ensure direct messages + delete for everyone enabled on team group (conditional — only updates profile if preferences or name differ from desired) +8. Create team group invite link (best-effort), schedule 10min deletion if created +9. Validate `--auto-add-team-members` (`-a`) if provided +10. Register Grok event handlers on `chat` (filtered by `event.user === grokUserId`) +10b. Refresh stale cards: `CardManager.refreshAllCards()` — lists all groups, skips those with `customData.complete` or no `customData.cardItemId`, sorts remaining by `cardItemId` ascending, re-posts oldest-first so newest cards land at the bottom of team group +11. On SIGINT/SIGTERM → `clearTimeout(inviteLinkTimer)` (noop if already deleted), `cards.destroy()` (stops the card-flush interval), `deleteInviteLink()` (profileMutex-gated `apiDeleteGroupLink`), `process.exit(0)`. Signal handler is reentrant-safe: an `inviteLinkDeleted` flag prevents double-deletion; `clearTimeout`/`clearInterval` are no-op on undefined. + +**Grok event registration** (same ChatApi, filtered by profile): + +```typescript +chat.on("receivedGroupInvitation", async (evt) => { + if (evt.user.userId !== grokUserId) return + supportBot?.onGrokGroupInvitation(evt) +}) +chat.on("newChatItems", async (evt) => { + if (evt.user.userId !== grokUserId) return + supportBot?.onGrokNewChatItems(evt) +}) +chat.on("connectedToGroupMember", (evt) => { + if (evt.user.userId !== grokUserId) return + supportBot?.onGrokMemberConnected(evt) +}) +``` + +## 8. Event Processing + +**Main profile event handlers:** + +| Event | Handler | Action | +|-------|---------|--------| +| `acceptingBusinessRequest` | `onBusinessRequest` | Enable file uploads + visible history on business group | +| `newChatItems` | `onNewChatItems` | Route: team group → handle `/join`; customer group → derive state, dispatch; direct message → reply with business address link | +| `chatItemUpdated` | `onChatItemUpdated` | Schedule card update | +| `leftMember` | `onLeftMember` | Customer left → cleanup, card remains. Grok left → cleanup. Team member left → revert if no message sent. | +| `joinedGroupMember` | `onJoinedGroupMember` | Team group joiner (link-join): initiate DM via `apiCreateMemberContact` + `apiSendMemberContactInvitation`. Fires for any member joining via group invite link. | +| `connectedToGroupMember` | `onMemberConnected` | In team group: send DM with contact ID (if not already sent by `onJoinedGroupMember`). In customer group: promote to Owner (unless customer or Grok). | +| `chatItemReaction` | `onChatItemReaction` | Team/Grok reaction in customer group → schedule card update (auto-complete) | +| `newMemberContactReceivedInv` | `onMemberContactReceivedInv` | Team group member DM contact received: send contact ID message immediately (dedup via `sentTeamDMs`) | +| `contactConnected` | `onContactConnected` | Deliver pending DM if queued (dedup via `sentTeamDMs`) | +| `contactSndReady` | `onContactSndReady` | Deliver pending DM if queued (dedup via `sentTeamDMs`) | + +**Grok profile event handlers:** + +| Event | Handler | Action | +|-------|---------|--------| +| `receivedGroupInvitation` | `onGrokGroupInvitation` | Look up `pendingGrokJoins`; if found, auto-accept via `apiJoinGroup`; if not found (race), buffer in `bufferedGrokInvitations` for `activateGrok` to drain | +| `connectedToGroupMember` | `onGrokMemberConnected` | Grok now fully connected — read last 100 msgs from own view, call Grok API, send initial response | +| `newChatItems` | `onGrokNewChatItems` | Batch dedup: collect last customer text message per group in the event. Skip groups with `grokInitialResponsePending` set (initial combined response in flight). For the selected message: read last 100 msgs, call Grok API, send response. Non-text (images, files, voice) → ignored by Grok (card update handled by main profile). | + +**Message routing in `onNewChatItems` (main profile):** + +```typescript +// For each chatItem: +// 1. Direct message (not group) → reply with business address link, stop +// 2. Team group (groupId === teamGroupId) → handle /join command +// 3. Skip non-business-chat groups +// 4. Skip groupSnd (own messages) +// 5. Identify sender via businessChat.customerId +// 6. Team member message → check if first team text (trigger one-way gate: remove Grok, disable /grok), schedule card update +// 7. Customer message → derive state, dispatch: +// - WELCOME: create card, send queue msg (or handle /grok first msg → WELCOME→GROK, skip queue) +// - QUEUE: /grok → invite Grok; /team → add ALL configured team members; else schedule card update +// - GROK: /team → add ALL configured team members (Grok stays); else schedule card update +// - TEAM-PENDING: /grok → invite Grok if not present, else ignore; /team → if team members still present, reply "already invited"; if all team members have left, re-add silently (state stays TEAM-PENDING); else no action +// - TEAM: /grok → reply "team mode"; else no action +``` + +## 9. One-Way Gate + +The gate is event-driven and persists its transitions. The initial `/team` guard reads `customData.state` AND group composition: if state is already `TEAM-PENDING`/`TEAM` **and** team members are still present, the bot replies `teamAlreadyInvitedMessage` without re-adding. If state is `TEAM-PENDING`/`TEAM` but all team members have left, the bot re-adds them (state stays `TEAM-PENDING`). The first-team-message detection writes `state: 'TEAM'` into customData at the moment the bot observes the message, then removes Grok and disables `/grok`. + +1. User sends `/team` → ALL configured `--auto-add-team-members` (`-a`) added to group (each promoted to Owner at invite time via `apiSetMembersRole`, re-asserted on connect as fallback) → Grok stays if present → TEAM-PENDING +2. Repeat `/team` → detected via `customData.state ∈ {TEAM-PENDING, TEAM}` **and team members still present** → reply with `teamAlreadyInvitedMessage`. If team members have since left, re-add them silently (state stays `TEAM-PENDING`). +3. `/grok` still works in TEAM-PENDING (if Grok not present, invite it; if present, ignore — Grok responds to customer messages) +4. Any team member sends first text message in customer group → **gate triggers**: + - Remove Grok from group (`apiRemoveMembers`) + - `/grok` permanently disabled → replies: "You are now in team mode. A team member will reply to your message." + - State = `TEAM` (written as `customData.state = 'TEAM'` at observation time) +5. Detection: in `onNewChatItems`, when sender is a team member and `customData.state !== 'TEAM'`, trigger the gate and write `state: 'TEAM'` via `mergeCustomData`. + +**Edge cases:** +- All team members leave before sending → state stays `TEAM-PENDING` (customer is still waiting for a response); sending `/team` re-adds them without the "already invited" reply. +- Team member leaves after sending → state stays `TEAM` (`customData.state` persists). Customer can send `/team` again to re-add team members. + +## 10. Grok Integration + +Grok is a **second user profile** in the same ChatApi instance. Self-contained: watches its own events, reads history from its own view, calls Grok HTTP API, sends responses. + +### Grok-disabled mode (no `GROK_API_KEY`) + +If `GROK_API_KEY` is unset or empty, `parseConfig` returns `grokApiKey: null` (via `process.env.GROK_API_KEY || null`, so `GROK_API_KEY=` is treated the same as unset; no throw) and `index.ts` derives `grokEnabled = config.grokApiKey !== null`. When `grokEnabled === false`: + +- Startup logs: `"No GROK_API_KEY provided, disabling Grok support"`. +- **`config.grokContactId` is still restored from the state file** (the lookup runs unconditionally before the `if (grokEnabled)` block). This ensures `getGroupComposition` can identify Grok members so the one-way gate can remove them when a team member sends a text message — even while Grok API is disabled. Without this, Grok members would become "phantom" members: physically present in groups but invisible to the state machine, preventing the gate from firing and causing dual responses (Grok + team) if Grok is later re-enabled. +- The Grok profile is not resolved or created (no `apiListUsers`/`apiCreateActiveUser` for "Grok"; no invite link issued). +- `GrokApiClient` is not instantiated. +- `SupportBot` receives `grokApi = null` and `grokUserId = null`. +- Bot command list registered at startup contains only `/team` — `/grok` is not advertised. +- Grok event handlers (`receivedGroupInvitation`, `connectedToGroupMember`, Grok-side `newChatItems`) are not registered. Handlers that are shared with the main profile (e.g. `onMemberConnected`) remain correct because their Grok checks are guarded by `this.config.grokContactId !== null`. +- Customer-facing messages (`queueMessage`, `noTeamMembersMessage`) accept a `grokEnabled` flag and drop the `/grok` clause when false. +- If the customer still types `/grok` manually, `processMainChatItem` rewrites `cmd` to `null` when `rawCmd?.keyword === "grok" && !this.grokEnabled`, so the dispatcher treats it as an unrecognized command (same as any other plain text). +- Defense in depth: `activateGrok` and `processGrokChatItem` short-circuit on entry when `this.grokApi === null`; `withGrokProfile` throws if called with `grokUserId === null`. + +Type signatures affected: +- `Config.grokApiKey: string | null` +- `SupportBot` constructor: `chat, grokApi: GrokApiClient | null, config, mainUserId, grokUserId: number | null, desiredCommands: T.ChatBotCommand[]` — `desiredCommands` is required (used by `sendToGroup`'s lazy per-group commands sync; see §20.4 suite 30 and the §7 "Note" describing `syncGroupCommands`). +- `queueMessage(timezone: string, grokEnabled: boolean): string` +- `noTeamMembersMessage(grokEnabled: boolean): string` (was a plain `const string`) + +### Grok join flow + +**Critical:** `activateGrok` awaits `waitForGrokJoin(120s)` which depends on future events dispatched through the same sequential event loop (`runEventsLoop` in api.ts). Awaiting it in an event handler deadlocks — the event loop is blocked waiting for events it can't dispatch. **Solution:** All `activateGrok` calls use `fireAndForget()` — tracked but not awaited. Tests call `bot.flush()` to await completion. + +**Main profile side (invite + failure detection):** +0. Send `grokInvitingMessage` ("Inviting Grok, please wait...") +1. **Set `grokInitialResponsePending.add(groupId)` FIRST** — the gate must be raised before any operation that could make Grok recognizable to `onGrokNewChatItems`. Specifically: before `apiAddMember`, before `pendingGrokJoins` is set, and before `bufferedGrokInvitations` is drained (which populates `reverseGrokMap`). Without this ordering, the sequence `apiAddMember → pendingGrokJoins.set → drain → reverseGrokMap.set → gate.add` contains a window where `reverseGrokMap` identifies the group as a Grok-active group but the gate is still DOWN. A customer message arriving in that window triggers a per-message response concurrent with the initial combined response — producing duplicate Grok replies. Every error path below MUST clear the gate. +2. **Pre-check via `apiListMembers`**: silent return if Grok is already in the group in any non-terminal status (covers `GSMemInvited`, which the SimpleX API would otherwise resend the invitation for without throwing). Then `apiAddMember(groupId, grokContactId, Member)` → get `member.memberId`. On `groupDuplicateMember` (race between pre-check and add — Grok joined as Connected meanwhile), **clear the gate** and silent return — the in-flight activation handles the outcome. On any other error, clear the gate, revert state, send `grokUnavailableMessage`. +3. Store `pendingGrokJoins.set(memberId, mainGroupId)` +4. Drain `bufferedGrokInvitations` — if the `receivedGroupInvitation` event arrived during step 2's await (race condition), process it now. (The gate is already up from step 1, so `onGrokNewChatItems` suppresses any per-message responses during drain and the subsequent join.) +5. `waitForGrokJoin(120s)` — awaits resolver from Grok profile's `connectedToGroupMember` (step 8 below) +6. Timeout → notify customer (`grokUnavailableMessage`), send queue message if was WELCOME→GROK, fall back to QUEUE (CAS-guarded: only if `customData.state` is still `GROK` — a concurrent `/team` that switched to `TEAM-PENDING` is respected), clear `grokInitialResponsePending` + +**Grok profile side (independent, triggered by its own events):** +7. `receivedGroupInvitation` → look up `pendingGrokJoins` by `evt.groupInfo.membership.memberId`. If found, auto-accept via `apiJoinGroup(groupId)`, set up `grokGroupMap` and `reverseGrokMap`. If not found (race: event arrived before step 2), buffer in `bufferedGrokInvitations` for step 3. Grok is NOT yet connected — cannot read history or send messages. +8. `connectedToGroupMember` → Grok now fully connected. Uses `reverseGrokMap` to find `mainGroupId`, resolves `grokJoinResolvers` — this unblocks step 5. + +**Back in `activateGrok` (after step 5 resolves):** +9. Read visible history — last 100 messages — build Grok API context (customer messages → `user` role) +10. If no customer messages found (visible history disabled or API failed), send generic greeting asking customer to repeat their question +11. Call Grok HTTP API (outside mutex) +12. Send response via `apiSendTextMessage` (through mutex with Grok profile) +13. Clear `grokInitialResponsePending` (via `finally` block — runs on success, failure, or early return). After this, per-message responses from `onGrokNewChatItems` resume normally for subsequent customer messages. Note: because the gate is raised at step 1 (before any other work), the `finally` block MUST be wired to cover every code path from step 1 onward — including the `groupDuplicateMember` silent-return and all revert/timeout branches — otherwise per-message responses stay suppressed indefinitely for the affected group. + +```typescript +const pendingGrokJoins = new Map() // memberId → mainGroupId +const bufferedGrokInvitations = new Map() // memberId → buffered event +const grokGroupMap = new Map() // mainGroupId → grokLocalGroupId +const reverseGrokMap = new Map() // grokLocalGroupId → mainGroupId +const grokJoinResolvers = new Map void>() // mainGroupId → resolve fn +const grokInitialResponsePending = new Set() // mainGroupIds where activateGrok is sending initial response +``` + +### Per-message Grok conversation + +Grok profile's `onGrokNewChatItems` handler: +1. **Batch deduplication:** When multiple customer messages arrive in a single `newChatItems` event (e.g., rapid messages delivered as a batch), collect the last customer message per group. Only the last message triggers a Grok API call — earlier messages are included in the history context via `apiGetChat`. Without this, each message in the batch would trigger a separate API call, and earlier calls would include later messages in their history (already in the group) — producing incoherent responses that reference messages "from the future" and duplicate replies. +2. **Initial response gate:** Skip groups where `grokInitialResponsePending` is set (checked via `reverseGrokMap` to translate Grok's local groupId to mainGroupId). This prevents per-message responses from racing with the initial combined response in `activateGrok`. +3. Only trigger for `groupRcv` **text** messages from customer (identified via `businessChat.customerId`) +4. Ignore: non-text messages (images, files, voice — card update handled by main profile), bot messages, own messages (`groupSnd`), team member messages +5. Read last 100 messages from own view (customer → `user`, own → `assistant`) +6. Call Grok HTTP API — different groups' calls run concurrently (see "Cross-group Grok parallelism" below). Per-group serialization of overlapping in-flight calls is NOT implemented in MVP (see §20.6). +7. Send response into group + +**Per-message error:** Send error message in group ("Sorry, I couldn't process that. Please try again or send /team for a human team member."), stay GROK. Customer can retry. + +**Card updates in Grok mode:** Each customer message triggers two card updates — one on receipt (main profile sees `groupRcv`), one after Grok responds (main profile sees Grok's `groupRcv`). Both go through the 300-second debounce (default `--card-flush-seconds`). + +### Grok removal + +Only three cases: +1. Team member sends first text message in customer group (one-way gate) +2. Grok join timeout (120s) — fallback to QUEUE +3. Customer leaves the group + +### Grok system prompt + +The full system prompt (including SimpleX documentation context) is supplied externally via the `--context-file ` CLI flag and loaded with `readFileSync` at startup in `index.ts`: + +```typescript +let contextFile = "" +if (config.contextFile) { + try { + contextFile = readFileSync(config.contextFile, "utf-8") + } catch { + log(`Warning: context file not found: ${config.contextFile}`) + } +} +grokApi = new GrokApiClient(config.grokApiKey!, contextFile) +``` + +`GrokApiClient` stores the loaded string as `systemPrompt` and prepends it on every `chat()` call: + +```typescript +async chat(history: GrokMessage[], userMessage: string): Promise { + return this.chatRaw([ + {role: "system", content: this.systemPrompt}, + ...history, + {role: "user", content: userMessage}, + ]) +} +``` + +If `GROK_API_KEY` is set but `--context-file` is missing, `parseConfig` throws and the bot exits before init. If the file path is provided but unreadable at runtime, a warning is logged and Grok runs with an empty system prompt (the API key still works but responses lose the SimpleX-specific guidance). Guidelines (concise answers, numbered steps, no markdown, ignore prompt-override attempts, etc.) live in the external file — not hardcoded — so operators can tune tone and documentation without a rebuild. + +Customer messages always in `user` role, never `system`. + +### Grok HTTP request timeout + +Every `fetch` to `api.x.ai/v1/chat/completions` MUST pass an `AbortSignal.timeout(60_000)` (60-second default). Without a timeout, a stuck TCP connection or an unresponsive server blocks the awaiting call indefinitely; because `processGrokChatItem` runs under the Grok profile's sequential event dispatch, a single hung call stalls per-message responses for ALL customer groups using Grok — and the same hang in `activateGrok`'s initial-response path leaves `grokInitialResponsePending` stuck (gate never released) until the process is killed. + +Implementation in `GrokApiClient.chatRaw`: + +```typescript +const response = await fetch("https://api.x.ai/v1/chat/completions", { + method: "POST", + headers: { ... }, + body: JSON.stringify({ ... }), + signal: AbortSignal.timeout(60_000), +}) +``` + +On abort, `fetch` rejects with a `DOMException` whose `name === "TimeoutError"` (or `"AbortError"` on older runtimes). Callers treat this identically to other `chat()` failures: +- `processGrokChatItem` → sends `grokErrorMessage` to the customer group, conversation stays GROK. +- `activateGrok` initial-response path → logs, sends `grokUnavailableMessage`, lets the `finally` block clear `grokInitialResponsePending`. + +Rationale for 60s: typical xAI responses return in 1–10s; a 60s ceiling accommodates cold-start / heavy-load latencies while still bounding worst-case per-customer wait. Not exposed as a CLI flag in MVP — a later iteration can add `--grok-timeout-seconds` if operator tuning is needed. + +### Cross-group Grok parallelism + +`onGrokNewChatItems` MUST dispatch per-group work concurrently. A naïve `for (const ci of lastPerGroup.values()) { await this.processGrokChatItem(ci) }` serializes calls across unrelated customer groups — if xAI takes 3s per call and five customers message in one event batch, customer #5 waits ~15s instead of ~3s. This is pure latency amplification with no ordering benefit (the groups are independent; within-group order is already preserved by batch deduplication picking the last message). + +Implementation: + +```typescript +async onGrokNewChatItems(evt: CEvt.NewChatItems): Promise { + const lastPerGroup = new Map() + for (const ci of evt.chatItems) { + // filter: groupRcv, customer text, not bot/team + // keep last per groupId + } + await Promise.allSettled( + [...lastPerGroup.values()].map((ci) => this.processGrokChatItem(ci)), + ) +} +``` + +Why `Promise.allSettled` (not `Promise.all`): one group's Grok API failure MUST NOT cancel or reject pending work for other groups. Each `processGrokChatItem` already handles its own errors (sends `grokErrorMessage`, logs); the outer handler only needs to wait until all per-group tasks finish before returning control to the event dispatcher. + +Concurrency bound: the number of distinct customer groups that have new Grok-eligible messages in a single event batch — typically ≤ the SimpleX batch-delivery size, practically small. No global semaphore needed in MVP. If xAI rate limits become a concern, add a shared semaphore later; orthogonal to this fix. + +Ordering guarantees preserved: +- Within a group, batch deduplication still picks only the latest message and earlier messages appear in the history context via `apiGetChat`. +- Across groups, there is no ordering requirement — each customer group is an independent conversation. +- The per-group gate (`grokInitialResponsePending`) still serializes against `activateGrok`'s initial response; this is a group-local check unaffected by cross-group parallelism. + +## 11. Team Group Commands + +| Command | Effect | +|---------|--------| +| `/join ` | Join specified customer group | + +**`/join` handling:** +1. Extract `{keyword, params}` from the chat item with `util.ciBotCommand(chatItem)`. The framework already parses the leading `/keyword` and returns the trimmed remainder as `params` — the handler does not run its own regex over the message text. Cards emit `/'join '`; a team-member tap delivers a chat item whose text is `/join `, which `ciBotCommand` returns as `{keyword: "join", params: ""}`. +2. Convert `params` to a number with `const targetGroupId = Number.parseInt(params, 10)`. If `Number.isNaN(targetGroupId) || targetGroupId <= 0`, reply in the team group with `Error: invalid group id "${params}"` and return. No regex, no `split(":")`, no legacy fallback — operators must use the numeric form (which is what the card always emits). +3. Validate target is a business group (has `businessChat` property) — error in team group if not. +4. Add requesting team member to customer group via `addOrFindTeamMember` (which calls `apiAddMember` + immediately `apiSetMembersRole(Owner)`). +5. On connect, `connectedToGroupMember` re-asserts Owner as an idempotent fallback (see §8). + +**Team member promotion:** Promotion happens at two points, both idempotent: +- **At invite time** — immediately after `apiAddMember`, `addOrFindTeamMember` calls `apiSetMembersRole(groupId, [memberId], Owner)`. The call is wrapped in try/catch: if the member is not yet connected and the API rejects, it's silently ignored (the connect-time promotion covers the fallback). SimpleX persists the role on `GSMemInvited` members so the role is active when they accept. This is only called for *newly invited* members — the pre-check in `addOrFindTeamMember` returns early for any member already in the group in a non-terminal status, so an already-invited member is not re-promoted. +- **On connect** — every `connectedToGroupMember` event in a customer group promotes to Owner unless the member is the customer or Grok. Idempotent. + +**DM handshake:** When a team member joins or connects in the team group, the bot sends a DM with the member's contact ID. Four delivery paths, deduplicated via `sentTeamDMs` Set: + +1. **`onJoinedGroupMember`** — fires when ANY member joins the team group via invite link (`joinedGroupMember` event). Calls `sendTeamMemberDM` without a `memberContact`. Since link-joiners typically have no existing DM contact, this creates the contact via `apiCreateMemberContact(groupId, groupMemberId)`, then sends the invitation with message via `apiSendMemberContactInvitation(contactId, msg)`. +2. **`onMemberConnected`** — `sendTeamMemberDM` called with `memberContact` from the event. If not already sent by path 1: + - If `contactId` exists: sends DM via `apiSendTextMessage`. + - If `contactId` is null: uses the same `apiCreateMemberContact` + `apiSendMemberContactInvitation` path as path 1. +3. **`onMemberContactReceivedInv`** — fires when the member initiates a DM first. Sends the contact ID message immediately. If send fails, queues for `contactConnected`/`contactSndReady`. +4. **`onContactConnected` / `onContactSndReady`** — delivers any pending DM queued by paths 1, 2, or 3. + +DM message: +> Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is `N:name` + +## 12. Message Templates + +```typescript +const welcomeMessage = `Hello! This is a *SimpleX team* support bot - not an AI. +Please ask any question about SimpleX Chat.` + +function queueMessage(timezone: string, grokEnabled: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `The team will reply to your message within ${hours} hours.` + if (!grokEnabled) return base + return `${base} + +If your question is about SimpleX, click /grok for an *instant Grok answer*. + +Send /team to switch back.` +} + +const grokActivatedMessage = `*You are chatting with Grok* - use any language.` + +function teamAddedMessage(timezone: string, grokPresent: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `We will reply within ${hours} hours.` + if (!grokPresent) return base + return `${base} +Grok will be answering your questions until then.` +} + +const teamAlreadyInvitedMessage = "A team member has already been invited to this conversation and will reply when available." + +const teamLockedMessage = "You are now in team mode. A team member will reply to your message." + +function noTeamMembersMessage(grokEnabled: boolean): string { + return grokEnabled + ? "No team members are available yet. Please try again later or click /grok." + : "No team members are available yet. Please try again later." +} + +const grokInvitingMessage = "Inviting Grok, please wait..." + +const grokUnavailableMessage = "Grok is temporarily unavailable. Please try again later or send /team for a human team member." + +const grokErrorMessage = "Sorry, I couldn't process that. Please try again or send /team for a human team member." + +const grokNoHistoryMessage = "I just joined but couldn't see your earlier messages. Could you repeat your question?" +``` + +`teamAddedMessage` takes a second `grokPresent` argument — when the customer switches from GROK → TEAM-PENDING (Grok still in the group until the gate triggers), the message appends a second line telling the customer Grok will keep answering until the team replies. Callers detect this by checking the current group composition for a Grok member before sending. + +**Weekend detection:** +```typescript +function isWeekend(timezone: string): boolean { + const day = new Intl.DateTimeFormat("en-US", {timeZone: timezone, weekday: "short"}).format(new Date()) + return day === "Sat" || day === "Sun" +} +``` + +## 13. Direct Message Handling + +If a user contacts the bot via a regular direct-message address (not business address), the bot replies with the business address link and does not continue the conversation. The reply is guarded by `chatItem.content.type === "rcvMsgContent"` — only actual text messages trigger the business address reply. System events on the DM contact (e.g. `contactConnected`, `rcvDirectEvent`) are ignored to prevent spam. + +## 14. Persistent State + +**State file:** `{dbPrefix}_state.json` — three keys: + +| Key | Type | Why persisted | +|-----|------|---------------| +| `teamGroupId` | number | Team group created once on first run | +| `grokContactId` | number | Bot↔Grok contact takes 60s to establish | +| `grokUserId` | number | Identifies the Grok user by ID across restarts; prevents silent mis-matching if the Grok profile is ever renamed | + +**Not persisted:** + +| State | Where it lives | +|-------|---------------| +| `state`, `cardItemId`, `complete` | Customer group's `customData` | +| `mainUserId` | Returned by `bot.run()` on startup; created fresh per DB | +| Message counts, timestamps | Derived from chat history | +| Customer name | Group display name | +| `pendingGrokJoins` | In-flight during 120s window only | +| `grokInitialResponsePending` | In-flight during `activateGrok` initial response only | +| Owner promotion | Idempotent: fired at invite time in `addOrFindTeamMember` and again on every `memberConnected` | + +**Failure modes:** +- State file deleted → new team group created, Grok contact re-established (60s delay) +- Grok remains in groups it was already in — self-contained, continues responding via own events + +## 15. Error Handling + +| Scenario | Handling | +|----------|----------| +| ChatApi init fails | Exit (let process manager restart) | +| Active user is Grok on restart | Pre-init DB, find main user, set active, close — before `bot.run()` | +| Grok join timeout (120s) | Notify customer, fall back to QUEUE | +| Grok API error (initial or per-message) | Send error in group, stay GROK. Customer can retry or `/team`. | +| `apiAddMember` fails | Send error msg, stay in current state | +| `groupDuplicateMember` on Grok invite | Silent return — in-flight activation handles the outcome (customer sent `/grok` again before join completed) | +| `apiRemoveMembers` fails | Ignore (member may have left) | +| `apiDeleteChatItems` fails (card) | Ignore, post new card, overwrite `customData` | +| Customer leaves | Cleanup in-memory state, card remains | +| Team member leaves (no message sent) | State stays `TEAM-PENDING` (`customData.state` persists). Customer's next `/team` re-adds silently. | +| Team member leaves (message sent) | State stays `TEAM` (`customData.state` persists). Customer's next `/team` re-adds silently. | +| No `--auto-add-team-members` (`-a`) configured | `/team` → "no team members available yet" | +| `grokContactId` unavailable | `/grok` → "temporarily unavailable" | +| Member already in group when `/team` re-runs | `addOrFindTeamMember` pre-checks via `apiListMembers` and skips BOTH `apiAddMember` and the invite-time `apiSetMembersRole(Owner)` entirely if the contact is present in any non-terminal status (so an `Invited`-but-not-yet-accepted member is never re-invited — the SimpleX API would otherwise resend the invitation for `GSMemInvited` — and is never re-promoted) | + +## 16. API Call Map + +| # | Operation | Instance | Method | When | +|---|-----------|----------|--------|------| +| 1 | Init bot | main | `bot.run()` | Startup | +| 2 | List users | chat | `apiListUsers()` | Startup — resolve profiles | +| 3 | Create Grok user | chat | `apiCreateActiveUser()` | First run | +| 4 | Set active user | chat | `apiSetActiveUser(userId)` | Before every API call (via mutex) | +| 5 | Resolve team group | main | `apiNewGroup()` / state file | Startup | +| 6 | Create team invite link | main | `apiCreateGroupLink()` | Startup | +| 7 | Delete team invite link | main | `apiDeleteGroupLink()` | 10min / shutdown | +| 8 | Auto-accept DM | main | `apiSetAutoAcceptMemberContacts(userId, true)` | Startup | +| 9 | List contacts | main | `apiListContacts()` | Startup — validate members | +| 10 | Establish Grok contact | main+grok | `apiCreateLink()` + `apiConnectActiveUser()` | First run | +| 11 | Update group profile | main | `apiUpdateGroupProfile()` | Business request; startup (conditional — only if preferences differ) | +| 12 | Send msg to customer | main | `apiSendTextMessage([Group, gId], text)` | Various | +| 13 | Post card to team group | main | `apiSendMessages(chatRef, [{card text with /'join ' final line}])` | Card create/update — one message per card | +| 14 | Delete card | main | `apiDeleteChatItems([Group, teamGId], [cardItemId], "broadcast")` | Card update | +| 15 | Set customData | main | `apiSetGroupCustomData(gId, data)` | Card lifecycle | +| 16 | Invite Grok | main | `apiAddMember(gId, grokContactId, Member)` | `/grok` | +| 17 | Grok joins | grok | `apiJoinGroup(gId)` | `receivedGroupInvitation` | +| 18 | Grok reads history | grok | `apiGetChat([Group, gId], 100)` | After join + per message | +| 19 | Grok sends response | grok | `apiSendTextMessage([Group, gId], text)` | After API call | +| 20 | Add team member | main | `apiAddMember(gId, teamContactId, Member)` | `/team`, `/join` — only when not already in group | +| 21 | Promote to Owner | main | `apiSetMembersRole(gId, [memberId], Owner)` | Immediately after #20 (invite-time) AND `connectedToGroupMember` (fallback) | +| 22 | Remove Grok | main | `apiRemoveMembers(gId, [memberId])` | Gate trigger / timeout / leave | +| 23 | List members | main | `apiListMembers(gId)` | State derivation, duplicate check | +| 24 | Register team commands | main | `apiUpdateGroupProfile(teamGId, profile)` | Startup — register `/join` in team group | +| 25 | Get group info | main | `apiListGroups()` + find by ID | Card compose — read `customData.cardItemId` from `groupInfo` | +| 26 | Create DM contact | main | `apiCreateMemberContact(gId, memberId)` | `joinedGroupMember` / `onMemberConnected` — bot-initiated DM with team member | +| 27 | Send DM invitation | main | `apiSendMemberContactInvitation(contactId, msg)` | After #26 — sends invite with message in one step | + +## 17. Implementation Sequence + +**Phase 1: Scaffold** +- `package.json`, `tsconfig.json`, `config.ts`, `util.ts` (isWeekend, profileMutex) +- `index.ts`: init ChatApi, resolve both profiles, state file, startup sequence +- **Verify:** Instance inits, profiles resolved, Grok contact established, team group created + +**Phase 2: Event processing + cards** +- `bot.ts`: SupportBot class, state derivation helpers, event dispatch +- `cards.ts`: CardManager — format, debounce, lifecycle (create/update/cleanup) +- `messages.ts`: all templates +- Handle `acceptingBusinessRequest` → enable file uploads + visible history +- Handle `newChatItems` → WELCOME/QUEUE routing, card creation +- Handle DM → reply with business address link +- **Verify:** Customer connects → welcome → sends msg → card appears in team group → queue reply + +**Phase 3: Grok integration** +- `grok.ts`: GrokApiClient with system prompt + docs +- Grok event handlers (invitation → join, newChatItems → respond) +- `/grok` activation: invite, wait join, Grok reads history + responds independently +- `/grok` as first message (WELCOME → GROK, skip queue) +- Per-message Grok conversation + serialization per group +- **Verify:** `/grok` → Grok joins as separate participant → responds from "Grok" + +**Phase 4: Team mode + one-way gate** +- `/team` → add team members, Grok stays +- One-way gate: detect first team text → remove Grok, disable `/grok` +- `/join` command in team group (validate business group, add member, promote Owner) +- DM handshake with team members +- Team member promotion on `connectedToGroupMember` +- **Verify:** Full flow: QUEUE → /grok → GROK → /team → TEAM-PENDING → team msg → TEAM + +**Phase 5: Polish** +- Edge cases: customer leave, Grok timeout, member leave, restart recovery +- Team group invite link lifecycle +- Graceful shutdown +- Supply Grok context via `--context-file ` at runtime (required when `GROK_API_KEY` is set) +- End-to-end test all flows + +## 18. Self-Review Requirement + +Each code artifact must undergo adversarial self-review/fix loop: +1. Write/edit code +2. Self-review against this plan: correctness, completeness, all state transitions, all API calls, all error cases +3. Fix issues found +4. Repeat until **2 consecutive zero-issue passes** +5. Report completion → user reviews → if changes needed, restart from step 1 + +## 19. Verification + +**Startup:** +```bash +cd apps/simplex-support-bot +npm install +# With Grok support: +GROK_API_KEY=xai-... npx ts-node src/index.ts \ + --team-group SupportTeam \ + --timezone America/New_York \ + --context-file ./context.md + +# Without Grok (logs "No GROK_API_KEY provided, disabling Grok support"): +npx ts-node src/index.ts \ + --team-group SupportTeam \ + --timezone America/New_York +``` + +**Test scenarios:** +1. Connect → verify welcome message, business address link printed to stdout +2. Send question → verify card appears in team group (🆕), queue reply received +3. `/grok` → verify Grok joins, responses from "Grok", card updates to 🤖 +4. `/grok` as first message → verify WELCOME→GROK, no queue message, card 🤖 +5. `/team` in GROK → verify team added, Grok stays, card 👋 Team-pending +6. `/grok` in TEAM-PENDING → verify Grok still responds +7. Team member sends text → verify Grok removed, `/grok` rejected, card → 💬 +8. `/grok` in TEAM → verify "team mode" rejection +9. `/team` when already invited → verify "already invited" message +10. Card debouncing: multiple rapid events → verify single card update per 300s flush (default) +11. `/join` from team group → verify team member added to customer group, promoted to Owner +12. `/join` with non-business group → verify error +13. Weekend → verify "48 hours" +14. Customer leaves → verify cleanup, card remains +15. Grok timeout → verify fallback to QUEUE, queue message sent +16. Grok API error (per-message) → verify error in group, stays GROK +17. Grok no-history fallback → verify generic greeting sent +18. Non-text message in GROK mode → verify no Grok API call, card updated +19. Team/Grok reaction → verify card auto-complete (✅ icon, "done") +20. DM contact text message → verify business address link reply +21. DM contact non-message event (e.g. contactConnected) → verify no reply (rcvMsgContent guard) +22. DM handshake via `joinedGroupMember` → team member joins team group via link → verify `apiCreateMemberContact` + `apiSendMemberContactInvitation` called, contact ID message sent +23. DM handshake via `connectedToGroupMember` → verify contact ID message sent (dedup with #22) +24. Restart → verify same team group + Grok contact from state file, cards resume via `customData` +25. No `--auto-add-team-members` (`-a`) → `/team` → verify "no team members available" +26. Repeated `/team` while members are still in `Invited` status → verify `apiAddMember` is NOT called again (pre-check in `addOrFindTeamMember` returns the existing member) +27. Team member leaves (no message sent) → verify revert to QUEUE +28. Team member leaves (message sent), customer sends `/team` → verify re-adds team members +29. Card preview sender prefixes → verify first message in each consecutive sender run gets `Name:` prefix, subsequent same-sender messages do not +30. `/team` after all team members left → verify re-adds team members (not "already invited") + +### Critical Reference Files + +- **Native library API:** `packages/simplex-chat-nodejs/src/api.ts` +- **Bot automation:** `packages/simplex-chat-nodejs/src/bot.ts` +- **Utilities:** `packages/simplex-chat-nodejs/src/util.ts` +- **Types:** `packages/simplex-chat-client/types/typescript/src/types.ts` +- **Events:** `packages/simplex-chat-client/types/typescript/src/events.ts` +- **Product spec:** `apps/simplex-support-bot/plans/20260207-support-bot.md` + +## 20. Testing + +Vitest 1.x (Node 18 compatible). All tests verify **observable behavior** — messages sent, members added/removed, cards posted/deleted, API calls made — never internal state. + +### 20.1 Mock Infrastructure + +**Approach:** Vite resolve aliases redirect native-dependent packages to lightweight JS stubs at build time. Tests import from TypeScript source (`./src/bot.js`) — Vitest transpiles inline, so mocks apply before any code runs. + +**Files:** + +| File | Purpose | +|------|---------| +| `bot.test.ts` | All tests (co-located with source) | +| `vitest.config.ts` | Resolve aliases, globals, timeout | +| `test/__mocks__/simplex-chat.js` | CJS stub: `api.ChatApi`, `util.ciContentText`, `util.ciBotCommand`, `util.contactAddressStr` | +| `test/__mocks__/simplex-chat-types.js` | CJS stub: `T.ChatType`, `T.GroupMemberRole`, `T.GroupMemberStatus`, `T.GroupFeatureEnabled`, `T.CIDeleteMode` | + +```typescript +// vitest.config.ts +export default defineConfig({ + test: { globals: true, testTimeout: 10000 }, + resolve: { + alias: { + "simplex-chat": path.resolve(__dirname, "test/__mocks__/simplex-chat.js"), + "@simplex-chat/types": path.resolve(__dirname, "test/__mocks__/simplex-chat-types.js"), + }, + }, +}) +``` + +**`MockChatApi`** — inline class in `bot.test.ts`: + +- **Tracking arrays:** `sent`, `added`, `removed`, `joined`, `deleted`, `customData`, `roleChanges`, `profileUpdates`, `memberContacts`, `memberContactInvitations` +- **Simulated DB:** `members` (Map), `chatItems` (Map), `groups` (Map), `activeUserId` +- **Failure injection:** `apiAddMemberWillFail(err?)`, `apiDeleteChatItemsWillFail()` +- **Query helpers:** `sentTo(groupId)`, `lastSentTo(groupId)`, `sentDirect(contactId)` +- `apiSendTextMessage` returns `[{chatItem: {meta: {itemId: N}}}]` — auto-incrementing IDs +- `apiGetChat` returns from `chatItems` map with `chatInfo.groupInfo` from `groups` map +- `apiCreateMemberContact(groupId, groupMemberId)` — returns a contact object with auto-incrementing `contactId`. Tracks calls in `memberContacts` array. +- `apiSendMemberContactInvitation(contactId, msg)` — returns a contact object. Tracks calls in `memberContactInvitations` array. + +**`MockGrokApi`** — inline class: + +- `calls` array tracks `{history, message}` for each `chat()` call +- `willRespond(text)` / `willFail()` control responses +- Resets to default response `"Grok answer"` after each failure + +**Key design:** no `vi.mock()` hoisting — resolve aliases intercept all `require()`/`import()` before module evaluation. Console output silenced via `vi.spyOn(console, "log/error")`. + +### 20.2 Factory Helpers & Event Builders + +Tests construct events via composable helpers: + +```typescript +// Factory helpers +makeConfig(overrides?) // Config with defaults (team group, 2 team members, UTC) +makeGroupInfo(groupId, opts?) // GroupInfo with businessChat, customerId, etc. +makeUser(userId) // {userId, profile: {displayName}} +makeChatItem(opts) // ChatItem with dir/text/memberId/msgType +makeAChatItem(chatItem, groupId?) // AChatItem wrapping chatItem + groupInfo + +// Member factories — typed member objects +makeTeamMember(contactId, name?, groupMemberId?) // team member with standard memberId pattern +makeGrokMember(groupMemberId?) // Grok member (default groupMemberId=7777) +makeCustomerMember(status?) // customer member + +// Event builders — return full newChatItems events +customerMessage(text, groupId?) // from customer in customer group +customerNonTextMessage(groupId?) // non-text (image) from customer +teamMemberMessage(text, contactId?, groupId?) // from team member +grokResponseMessage(text, groupId?) // from Grok in customer group +directMessage(text, contactId) // from direct contact +teamGroupMessage(text, senderContactId?) // in team group +grokViewCustomerMessage(text, msgType?) // customer msg arriving in Grok's view + +// Event factories — return full lifecycle events +connectedEvent(groupId, member, memberContact?) // connectedToGroupMember +leftEvent(groupId, member) // leftMember (auto-sets Left status) +updatedEvent(groupId, chatItem, userId?) // chatItemUpdated +reactionEvent(groupId, added) // chatItemReaction +joinedEvent(groupId, member, userId?) // joinedGroupMember + +// History builders — add to mock chatItems map +addBotMessage(text, groupId?) +addCustomerMessageToHistory(text, groupId?) +addTeamMemberMessageToHistory(text, contactId?, groupId?) +addGrokMessageToHistory(text, groupId?) + +// Assertion helpers — intention-revealing, with debuggable failure messages +expectSentToGroup(groupId, substring) // message containing substring sent to group +expectNotSentToGroup(groupId, substring) // no message containing substring sent to group +expectDmSent(contactId, substring) // DM containing substring sent to contact +expectAnySent(substring) // any message (group or DM) containing substring +expectMemberAdded(groupId, contactId) // apiAddMember called with groupId + contactId +expectCardDeleted(cardItemId) // apiDeleteChatItems called with cardItemId +expectMemberContactCreated(groupId, memberId) // apiCreateMemberContact called +expectMemberContactInvSent(contactId) // apiSendMemberContactInvitation called +``` + +### 20.3 State Setup Helpers + +Each helper reaches a specific state, composing from simpler helpers: + +```typescript +async function reachQueue(groupId?) // send first msg → QUEUE (adds queue msg to history) +async function reachGrok(groupId?) // reachQueue → /grok → simulateGrokJoinSuccess → GROK +async function reachTeamPending(groupId?) // reachQueue → /team → TEAM-PENDING +async function reachTeam(groupId?) // reachTeamPending → add team member to mock → team msg → TEAM +``` + +**`simulateGrokJoinSuccess(mainGroupId?)`** — simulates the async Grok join flow: +1. Waits 10ms (lets `activateGrok` reach `waitForGrokJoin`) +2. Fires `onGrokGroupInvitation` (Grok accepts invite) +3. Fires `onGrokMemberConnected` (Grok fully connected → resolver called) + +Called as: `const p = simulateGrokJoinSuccess(); await bot.onNewChatItems(...); await p;` + +### 20.4 Test Catalog (154 tests, 31 suites) + +#### 1. Welcome & First Message (4 tests) +- first message → queue reply + card created with /join command +- non-text first message → no queue reply, no card +- second message → no duplicate queue reply +- unrecognized /command → treated as normal message (triggers queue) + +#### 2. /grok Activation (5 tests) +- /grok from QUEUE → Grok invited, grokActivatedMessage sent (after join confirms) +- /grok as first message → WELCOME→GROK, no queue message, card created +- /grok in TEAM → rejected with teamLockedMessage +- /grok when grokContactId is null → grokUnavailableMessage +- /grok as first message + Grok join fails → queue message sent as fallback + +#### 3. Grok Conversation (11 tests) +- Grok per-message: reads history, calls API, sends response +- customer non-text → no Grok API call +- Grok API error → grokErrorMessage sent +- Grok ignores bot commands from customer +- Grok ignores non-customer messages +- Grok ignores own messages (groupSnd) +- batch: multiple customer messages in one event → only last triggers Grok API call +- batch: messages from different groups → each group gets one response +- batch: non-customer messages filtered, only customer messages trigger response +- batch: across groups → Grok calls overlap in-flight (parallel `Promise.allSettled` dispatch, proven via gated `MockGrokApi.chat`) + +#### 4. /team Activation (4 tests) +- /team from QUEUE → ALL team members added, teamAddedMessage sent +- /team as first message → WELCOME→TEAM-PENDING, no queue message +- /team when already activated (members present) → teamAlreadyInvitedMessage +- /team with no team members → noTeamMembersMessage + +#### 5. One-Way Gate (5 tests) +- team member first TEXT → Grok removed if present +- team member empty text → Grok NOT removed +- /grok after gate → teamLockedMessage +- customer text in TEAM → no bot reply, card update scheduled +- /grok in TEAM-PENDING → invite Grok if not present + +#### 5b. One-Way Gate with Grok Disabled (2 tests) +- team text removes Grok even when grokApi is null +- Grok does not respond when disabled even if grokContactId is set + +#### 6. Team Member Lifecycle (6 tests) +- team member connected → promoted to Owner +- customer connected → NOT promoted +- Grok connected → NOT promoted +- all team members leave → reverts to QUEUE +- /team after all members left (TEAM-PENDING, no msg sent) → re-adds members +- /team after all members left (TEAM, msg was sent) → re-adds members + +#### 7. Card Dashboard (7 tests) +- first message creates card with customer name + /join +- card final line is `/'join '` (single-quoted, numeric id only, no `:name` suffix) +- card update deletes old, posts new +- apiDeleteChatItems failure → ignored, new card posted +- customData stores cardItemId through flush cycle +- concurrent `mergeCustomData` on same group → both patches survive (per-group `customDataMutex` serializes read-modify-write; without the mutex the second write clobbers the first) +- customer leaves → customData cleared + +#### 8. Card Debouncing (5 tests) +- rapid schedules → single card update on flush +- multiple groups pending → each reposted once +- card create is immediate (not debounced) +- flush with no pending → no-op +- flush on group with no `cardItemId` → `createCard` posts a new card (proves `flushOne` dispatches to create-path so a failed `createCard` retries) + +#### 9. Card Format & State Derivation (6 tests) +- QUEUE state derived (no Grok/team) +- WELCOME state derived (customData has no cardItemId) +- GROK state derived (Grok member present) +- TEAM-PENDING derived (team present, no team message) +- TEAM derived (team present + message sent) +- message count excludes bot's own + +#### 10. /join Command (6 tests) +- /join (the only accepted form) → team member added; `params` from `ciBotCommand` is parsed via `Number.parseInt`, no regex +- /join : (historic suffix) → still parses because `Number.parseInt(":", 10)` stops at the colon — handler does not strip the suffix deliberately; the suffix is never emitted by the card +- /join with non-numeric `params` (e.g. `/join abc`) → error reply in team group, no `apiAddMember` call +- /join non-business group → error +- /join non-existent groupId → error +- customer /join in customer group → treated as normal message + +#### 11. DM Handshake (6 tests) +- team member joins team group → DM with contact ID +- name with spaces → single-quoted +- pending DM delivered on contactConnected +- team member with no DM contact → creates member contact via `apiCreateMemberContact` and sends invitation via `apiSendMemberContactInvitation` +- joinedGroupMember in team group → creates member contact via `apiCreateMemberContact` and sends invitation via `apiSendMemberContactInvitation` +- no duplicate DM when sendTeamMemberDM succeeds AND onMemberContactReceivedInv fires + +#### 12. Direct Messages (3 tests) +- regular DM → business address link reply +- DM without business address → no reply +- non-message DM event (e.g. contactConnected) → no reply (rcvMsgContent guard) + +#### 13. Business Request (1 test) +- acceptingBusinessRequest → enables file uploads + visible history + +#### 14. chatItemUpdated Handler (3 tests) +- business group → card update scheduled +- non-business group → ignored +- wrong user → ignored + +#### 15. Reactions (2 tests) +- reaction added → card update scheduled +- reaction removed → no card update + +#### 16. Customer Leave (4 tests) +- customer leaves → customData cleared +- Grok leaves → maps cleaned, no crash +- team member leaves → logged, no crash +- leftMember in non-business group → ignored + +#### 17. Error Handling (3 tests) +- apiAddMember fails (Grok) → grokUnavailableMessage +- /grok while Grok already present (any non-terminal status, including `Invited`) → pre-check silent-returns, no `apiAddMember` call. Plus race coverage: simulated `groupDuplicateMember` thrown by `apiAddMember` → silent return, no further state change +- /team while team member already present (any non-terminal status, including `Invited`) → `apiAddMember` not called for that member + +#### 18. Profile / Event Filtering (4 tests) +- newChatItems from Grok profile → ignored by main handler +- Grok events from main profile → ignored by Grok handlers +- own messages (groupSnd) → ignored +- non-business group messages → ignored + +#### 19. Grok Join Flow (6 tests) +- receivedGroupInvitation → apiJoinGroup called (full async flow) +- unmatched Grok invitation → buffered (not joined until activateGrok drains) +- buffered invitation drained after pendingGrokJoins set → apiJoinGroup called +- per-message responses suppressed during activateGrok initial response (grokInitialResponsePending gate) +- per-message responses resume after activateGrok completes +- activateGrok `groupDuplicateMember` path → gate cleared by outer `finally` (subsequent per-message event still triggers Grok; proves the outer `try/finally` covers every exit path from the entry-time `gate.add`, not just the initial-response section) + +#### 20. Grok No-History Fallback (1 test) +- Grok joins but sees no customer messages → grokNoHistoryMessage + +#### 21. Non-customer card updates (2 tests) +- Grok response → card update scheduled +- team member message → card update scheduled + +#### 22. End-to-End Flows (3 tests) +- WELCOME → QUEUE → /team → TEAM-PENDING → team msg → TEAM +- WELCOME → /grok first msg → GROK +- multiple concurrent conversations are independent + +#### 23. Message Templates (5 tests) +- welcomeMessage includes/omits group links +- grokActivatedMessage content +- teamLockedMessage content +- queueMessage mentions hours + +#### 24. State persistence in customData (5 tests) +- `deriveState` returns `WELCOME` when `customData.state` is absent +- first customer non-command message → handler writes `customData.state = "QUEUE"` +- `/grok` handler → writes `customData.state = "GROK"` +- `/team` handler → writes `customData.state = "TEAM-PENDING"` immediately (before team member accepts) +- first team-member text message → gate writes `customData.state = "TEAM"`; state persists when team member subsequently leaves (not demoted to `QUEUE`) + +#### 25. Card Preview Sender Prefixes (14 tests) +- single customer message → name prefix +- consecutive same-sender → prefix only on first +- alternating senders → each run gets prefix +- Grok messages → "Grok:" prefix +- team member messages → display name prefix +- bot messages (groupSnd) → excluded +- non-text content → media label ([image], [voice], etc.) +- empty messages → skipped +- truncation at maxTotal and maxPer limits (newest messages kept, oldest truncated) +- customer identified by memberId (not contactId) +- newlines in message text → replaced with spaces +- newlines in customer display name → sanitized in card header (card header is the only place the display name appears; `/join` is numeric id only) + +#### 26. Restart Card Recovery (10 tests) +- refreshAllCards refreshes groups with active cards +- no active cards → no-op +- ignores groups without cardItemId in customData +- orders by cardItemId ascending (oldest first, newest last) +- skips cards marked complete +- deletes old card before reposting +- ignores delete failure (>24h old card) +- card flush writes complete: true for auto-completed conversations +- card flush clears complete flag when conversation becomes active again +- continues on individual card failure + +#### 27. joinedGroupMember Event Filtering (2 tests) +- joinedGroupMember in non-team group → ignored +- joinedGroupMember from wrong user → ignored + +#### 28. parseConfig Validation (6 tests) +- `--complete-hours` non-numeric → throws with message including the flag name and raw value +- `--complete-hours` negative → throws +- `--card-flush-seconds` non-numeric → throws +- `--timezone` invalid IANA → throws (probe `Intl.DateTimeFormat` at parse time) +- `--complete-hours 0` → accepted (disables auto-complete) +- valid IANA timezone → accepted + +#### 29. GrokApiClient HTTP timeout (1 test) +- `chat()` calls `AbortSignal.timeout(60_000)` and passes the signal to `fetch` (spies on `AbortSignal.timeout` and on `globalThis.fetch`; proves the timeout is wired through without waiting 60s of wall-clock) + +#### 30. Command sync in sendToGroup (5 tests) +Covers the lazy per-group commands sync introduced with `updateProfile: false`. `sendToGroup` unconditionally calls `syncGroupCommands(groupId)` before dispatching. That helper reads the group via `apiGetChat` (local-only) and issues `apiUpdateGroupProfile` with the merged `groupPreferences.commands` only if the current list doesn't match `desiredCommands`. Groups are cached in `syncedGroups: Set` per process, so later sends skip the read entirely. +- first send → one `apiUpdateGroupProfile` call with `groupPreferences.commands = desiredCommands`; existing `groupProfile.displayName` / `fullName` preserved in the payload; message still delivered (text content is irrelevant — sync always runs) +- group already has desired commands in DB → no `apiUpdateGroupProfile` call, but `syncedGroups` is still populated (next send with different DB state still skips — cache honored) +- cache: two sends to same group → sync fires only once; both messages delivered +- different groups → each synced independently +- existing `groupPreferences` fields (e.g. `files`, `reactions`) are preserved in the update payload; only `commands` changes + +### 20.5 Conventions + +- **File:** `bot.test.ts` (co-located with source, imports from `./src/*.js`) +- **Framework:** Vitest 1.x (Node 18 compatible) with `describe`/`test`/`beforeEach` +- **Mocking:** Vite resolve aliases (not `vi.mock`) — prevents native addon loading +- **Titles:** plain English, `→` separates action from outcome +- **Assertions:** verify observable effects only — messages, API calls, card content +- **No internal state assertions** — never peek at private fields +- **Each test is self-contained** — `beforeEach(() => setup())` creates fresh mocks +- **State helpers compose** — `reachTeam()` calls `reachTeamPending()` which calls `reachQueue()` +- **Grok join simulation** — `simulateGrokJoinSuccess()` uses 10ms setTimeout to fire events during `waitForGrokJoin` await. Tests call `await bot.flush()` after simulation to await fire-and-forget `activateGrok` completion. +- **No fake timers** — real timers everywhere; flush called explicitly via `cards.flush()` and `bot.flush()`. Suite 29 spies on `AbortSignal.timeout` rather than advancing a fake clock so it does not need fake timers either. + +### 20.6 Test Coverage Notes + +**Covered vs plan catalog:** +- §20.4 suites 1-13, 15, 17-30 plus 5b fully covered (154 tests across 31 suites) +- Weekend detection (`util.isWeekend`) — not unit-tested; depends on `Intl.DateTimeFormat(new Date())`, would need clock mocking. Not present in the §20.4 catalog. +- Profile Mutex serialization — not a standalone suite in §20.4; verified implicitly through all other tests (MockChatApi tracks activeUserId). +- Startup & state persistence (`index.ts` path) — not unit-tested; requires native ChatApi. Integration-test only. Includes `deleteInviteLink` (profileMutex + `apiSetActiveUser` before `apiDeleteGroupLink`), the conditional `apiUpdateGroupProfile` (compare `fullGroupPreferences` before calling), the best-effort `apiCreateGroupLink` (catch + log on SMP relay failure), the predicate-filtered `chat.wait("contactConnected", ...)` used to identify the Grok contact (§4), and the team-group `/join` command registration with `params: "groupId"` (§11). Not in §20.4 catalog. + +**Known plan items NOT implemented (conscious gaps, not test gaps):** +- Per-group Grok API call serialization (plan §10) — not implemented or tested +- Team member replacement on leave after sending — out of MVP scope. No plan section currently asserts it as a requirement; if added later, specify in SPEC §4.2 "Team replies" and implementation plan §15 "Error Handling" together. diff --git a/apps/simplex-support-bot/plans/20260207-support-bot.md b/apps/simplex-support-bot/plans/20260207-support-bot.md new file mode 100644 index 0000000000..6ba1380e07 --- /dev/null +++ b/apps/simplex-support-bot/plans/20260207-support-bot.md @@ -0,0 +1,513 @@ +# SimpleX Support Bot — Product Specification + +## Table of Contents + +1. [What](#1-what) +2. [Why](#2-why) +3. [Principles](#3-principles) +4. [Flows](#4-flows) + - [User flow](#41-user-flow) + - [Team flow](#42-team-flow) +5. [Architecture](#5-architecture) + - [CLI overview](#51-cli-overview) + - [Bot architecture](#52-bot-architecture) + - [Grok integration](#53-grok-integration) + - [Persistent state](#54-persistent-state) + +--- + +## 1. What + +A support bot for SimpleX Chat. Customers connect via a business address and get a private group where they can ask questions. The bot triages inquiries through AI (Grok) or human team members. The team sees all active conversations as cards in a single dashboard group. + +## 2. Why + +- **Instant answers.** Grok handles common questions about SimpleX Chat without team involvement. +- **Organized routing.** Every customer conversation appears as a card in the team group — the team sees everything in one place without joining individual conversations. +- **No external tooling.** Everything runs inside SimpleX Chat. No ticketing system, no separate dashboard. +- **Privacy.** Customers talk to the bot in private groups. Only the team sees the messages. + +--- + +## 3. Principles + +- **Opt-in**: Grok is never used unless the user explicitly chooses it. +- **User in control**: The user can switch to Grok or team before a team member replies. Once a team member sends a message, the conversation stays with the team. The user always knows who they are talking to. +- **Minimal friction**: No upfront choices or setup — the user just sends their question. +- **Ultimate transparency**: The user always knows whether they are talking to a bot, Grok, or a human, and what happens with their messages. + +--- + +## 4. Flows + +### 4.1 User Flow + +#### Step 1 — Welcome (on connect, no choices, no friction) + +When a user scans the support bot's QR code or clicks its address link, SimpleX creates a **business group** — a special group type where the customer is a fixed member identified by a stable `customerId`, and the bot is the host. The bot auto-accepts the connection and enables file uploads and visible history on the group. + +If a user contacts the bot via a regular direct-message address instead of the business address, the bot replies with the business address link and does not continue the conversation. Only actual text messages trigger this reply — system events (e.g. `contactConnected`) on the DM contact are ignored. + +Bot sends the welcome message automatically as part of the connection handshake — not triggered by a message: +> Hello! This is a *SimpleX team* support bot - not an AI. +> Please ask any question about SimpleX Chat. + +#### Step 2 — After user sends first message + +The bot's "first message" detection works by inspecting the group's `customData`. Until the bot has produced its first response (and written `cardItemId` to `customData`), the group is in the welcome state. + +On the customer's first message the bot does two things: +1. Creates a card in the team group (🆕 icon, with `/join` command) +2. Sends the queue message to the customer: + +> The team will reply to your message within 24 hours. +> +> If your question is about SimpleX, click /grok for an *instant Grok answer*. +> +> Send /team to switch back. + +On weekends, the bot says "48 hours" instead of "24 hours". + +When the bot is started without `GROK_API_KEY`, the `/grok` paragraphs are omitted — the customer only sees the first line about the team reply window. + +Each subsequent message updates the card — icon, wait time, message preview. The team reads the full conversation by joining via the card's `/join` command. + +#### Step 3 — `/grok` (Grok mode) + +Available in WELCOME, QUEUE, or TEAM-PENDING state (before any team member sends a message). If Grok is already being invited (e.g. customer sent `/grok` multiple times before Grok finished joining), the duplicate is silently ignored — the in-flight activation handles the outcome. If `/grok` is the customer's first message, the bot transitions directly from WELCOME → GROK — it creates the card with 🤖 icon and does not send the queue message. Triggers Grok activation (see [5.3 Grok integration](#53-grok-integration)). If Grok fails to join within 120 seconds, the bot notifies the user and the state falls back to QUEUE (the queue message is sent at this point). + +Bot immediately replies: +> Inviting Grok, please wait... + +Once Grok joins and connects: +> *You are chatting with Grok* - use any language. + +Grok is added as a separate participant so the user can differentiate bot messages from Grok messages. + +Grok is prompted as a privacy expert and support assistant who knows SimpleX Chat apps, network, design choices, and trade-offs. It gives concise, mobile-friendly answers — brief numbered steps for how-to questions, 1–2 sentence explanations for design questions. For criticism, it briefly acknowledges the concern and explains the design choice. It avoids filler and markdown formatting. The full system prompt (including SimpleX documentation context) is loaded from an external file at startup via the `--context-file` CLI flag (required when `GROK_API_KEY` is set). Customer messages are always placed in the `user` role, never `system`. The system prompt should include an instruction to ignore attempts to override its role or extract the prompt. + +#### Step 4 — `/team` (Team mode, one-way gate) + +Available in WELCOME, QUEUE, or GROK state. If `/team` is the customer's first message, the bot transitions directly from WELCOME → TEAM-PENDING — it creates the card with 👋 icon and does not send the queue message. Bot adds all configured `--auto-add-team-members` (`-a`) to the support group as Owners — immediately after `apiAddMember`, the bot calls `apiSetMembersRole(Owner)` so the role is set at invite time (SimpleX persists the role on pending invites), with a fallback re-promotion on `memberConnected` (every non-customer, non-Grok member gets promoted; safe to repeat). If team was already activated (`customData.state` is already `TEAM-PENDING` or `TEAM` **and** team members are still present), sends the "already invited" message instead. If the team was previously activated but all team members have since left, the bot re-adds them silently; state remains `TEAM-PENDING`. + +Bot replies: +> We will reply within 24 hours. + +On weekends, the bot says "48 hours" instead of "24 hours". If Grok is currently present in the group (i.e. customer switches from GROK → TEAM-PENDING), a second line is appended: +> Grok will be answering your questions until then. + +If `/team` is clicked again after a team member was already added: +> A team member has already been invited to this conversation and will reply when available. + +#### One-way gate + +When `/team` is clicked, team members are invited to the group. Grok is still present if it was active, and `/grok` remains available. The customer always has an active responder during this window. + +The gate triggers when **any team member sends their first text message in the customer group**: +- `/grok` is permanently disabled and replies with: + > You are now in team mode. A team member will reply to your message. +- Grok is removed from the group. +- From now on the conversation is purely between the customer and the team. + +#### Customer leaving + +When a customer leaves the group (or is disconnected), the bot cleans up all in-memory state for that group. The conversation card in the team group is not automatically removed (TBD). + +#### Commands + +`/grok` and `/team` are registered as **bot commands** in the SimpleX protocol, so they appear as tappable buttons in the customer's message input bar. The bot also accepts them as free-text (e.g., `/grok` typed manually). Unrecognized commands are treated as ordinary messages. + +When the bot is started without `GROK_API_KEY`, `/grok` is not registered as a bot command and Grok-related messaging paths are skipped entirely. A `/grok` typed manually by the customer is treated as an ordinary message. The customer-facing queue and "no team members available" messages also omit their `/grok` clause in this mode. + +#### Team replies + +When a team member sends a text message or reaction in the customer group, the bot resends the card (subject to debouncing). A conversation auto-completes (✅ icon, "done" wait time) when `completeHours` (default 3h, configurable via `--complete-hours`) pass after the last team/Grok message without any customer reply. The card flush cycle (`--card-flush-seconds`, default 300) checks elapsed time and transitions to ✅ when the threshold is met. If the customer sends a new message — including after ✅ — the conversation reverts to incomplete: the icon is derived from current state (👋 vs 💬 vs ⏰) and wait time counts from the customer's new message. + +### 4.2 Team Flow + +#### Setup + +The team group is created automatically on first run. Its name is set via the `--team-group` CLI argument. The group ID is written to the state file; subsequent runs reuse the same group. Group preferences (direct messages enabled, delete for everyone enabled, team commands registered as tappable buttons) are applied at creation time. On subsequent startups, the bot compares the existing `fullGroupPreferences` with the desired ones and only calls `apiUpdateGroupProfile` if they differ — avoiding unnecessary network round-trips to SMP relays. + +On every startup the bot attempts to generate a fresh invite link for the team group, prints it to stdout, and deletes it after 10 minutes (or on graceful shutdown). Any stale link from a previous run is deleted first. Link creation is best-effort — if the SMP relay is temporarily unreachable, the error is logged and the bot continues without an invite link. + +The operator shares the link with team members. They must join within the 10-minute window. When a team member joins, the bot automatically establishes a direct-message contact with them and sends: + +> Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is `N:name` + +This ID is needed for `--auto-add-team-members` (`-a`) config. The DM is sent as soon as the member joins the team group — the bot proactively creates a DM contact via `apiCreateMemberContact` and delivers the message with the invitation via `apiSendMemberContactInvitation`. If the contact already exists, the message is sent directly. Multiple delivery paths ensure the DM arrives regardless of connection timing. + +Team members are configured as a single comma-separated `--auto-add-team-members` flag (shortcut `-a`; e.g., `--auto-add-team-members "42:alice,55:bob"` or `-a "42:alice,55:bob"`), using the IDs from the DMs above. The bot validates every configured member against its contact list at startup and exits if any ID is missing or the display name does not match. + +Until team members are configured, `/team` commands from customers cannot add anyone to a conversation. The bot logs an error and notifies the customer. + +#### Dashboard — card-based live view + +The team group is **not a conversation stream**. It is a live dashboard of all active support conversations. The bot maintains exactly one message (a "card") per active conversation. Whenever anything changes — a new customer message, a state transition, an agent joining — the bot **deletes the existing card and posts a new one**. The group's message list is therefore always a current snapshot: scroll up to see everything open right now. + +**Trust assumption:** All team group members see all card previews, including customer message content. The team group is a trusted space — only authorized team members should be given access. + +#### Card format + +Each card is **one** message with five parts (the join command is the final line of the card text, not a separate message): + +``` +[ICON] *[Customer Name]* · [wait] · [N msgs] +[STATE][· agent1, agent2, ...] +"[last message(s), truncated]" +/'join [id]' +``` + +**Icon / urgency signal** + +| Icon | Condition | +|------|-----------| +| 🆕 | QUEUE — first message arrived < 5 min ago | +| 🟡 | QUEUE — waiting for team response < 2 h | +| 🔴 | QUEUE — waiting > 2 h with no team response | +| 🤖 | GROK — Grok is handling the conversation | +| 👋 | TEAM — team member added, no reply yet | +| 💬 | TEAM — team member has replied; conversation active | +| ⏰ | TEAM — customer sent a follow-up, team hasn't replied in > 2 h | +| ✅ | Done — no customer reply for `completeHours` (default 3h) after last team/Grok message | + +**Wait time** — time since the customer's last unanswered message. For ✅ (auto-completed) conversations, the wait field shows the literal string "done". For conversations where the team has replied and the customer hasn't followed up, time since last message from either side. + +**State label** + +| Value | Meaning | +|-------|---------| +| `Queue` | No agent or Grok yet | +| `Grok` | Grok is the active responder | +| `Team – pending` | Team member added, hasn't replied yet (takes priority over `Grok` if both are present) | +| `Team` | Team member engaged | + +**Agents** — comma-separated display names of all team members currently in the group. Omitted when no team member has joined. + +**Message preview** — the last several messages, most recent last, separated by a blue `/` (rendered via SimpleX markdown `!3 /!`). Newlines in message text are replaced with spaces to prevent card layout bloat. Newest messages are prioritized — when the total preview exceeds ~500 characters, the oldest messages are truncated (with `[truncated]` prepended) while the newest are always shown. Each message is prefixed with the sender's name (`Name: message`) on the first message in a consecutive run from that sender — subsequent messages from the same sender omit the prefix until a different sender's message appears. Sender identification: Grok is labeled "Grok"; the customer is labeled with their display name (newlines replaced with spaces for display); team members use their display name. The bot's own messages are excluded. Each individual message is truncated to ~200 characters with `[truncated]` appended. Media-only messages show a type label: `[image]`, `[file]`, `[voice]`, `[video]`. + +**Markdown escaping in previews** — SimpleX markdown interprets `!N` (where N is `1`–`6`, `r`, `g`, `b`, `y`, `c`, `m`, or `-`) as styled-text markup, closing at the next `!`. There is no escape mechanism in the parser. To prevent customer/agent message text from triggering false color formatting or interfering with the blue `/` separator, the bot inserts a zero-width space (U+200B) between `!` and any color-trigger character in preview text before joining with the separator. This is invisible to the user but breaks the parser trigger pattern. + +**Join command** — the final line of the card is `/'join '`. The single quotes around `join ` make the whole token clickable in SimpleX clients; when tapped, the client sends `/join ` back to the team group. The bot does not pattern-match the message text — it asks the framework for the structured command (`util.ciBotCommand` returns `{keyword: "join", params: ""}`) and converts `params` to a number with `Number.parseInt`. The numeric form is the only accepted form: there is no `/join :` legacy syntax and no regex fallback. + +The icon in line 1 is the sole urgency indicator — no reactions are used. + +#### Card examples + +--- + +**1. Brand new conversation** + +``` +🆕 *Alice Johnson* · just now · 1 msg +Queue +"Alice Johnson: I can't connect to my contacts after updating to 6.3." +/'join 42' +``` + +--- + +**2. Queue — short wait, two short messages combined in preview** + +``` +🟡 *Emma Webb* · 20m · 2 msgs +Queue +"Emma Webb: Hi" / "Is anyone there? I have an urgent question about my keys" +/'join 88' +``` + +Second message has no prefix because it's the same sender as the first. + +--- + +**3. Queue — urgent, no response in over 2 hours** + +``` +🔴 *Maria Santos* · 3h 20m · 6 msgs +Queue +"Maria Santos: I reset my phone and now all conversations are gone" / "I tried reinstalling but nothing changed" / "Please help, I've lost access to all my conversations after resetting my phone…" +/'join 38' +``` + +--- + +**4. Grok mode — alternating senders** + +``` +🤖 *David Kim* · 1h 5m · 8 msgs +Grok +"David Kim: Which encryption algorithm does SimpleX use for messages?" / "Grok: SimpleX uses double ratchet with NaCl crypto_box for end-to-end encryption…[truncated]" / "David Kim: And what about metadata protection?" +/'join 29' +``` + +Each sender change triggers a new name prefix. David and Grok alternate, so every message gets a prefix. + +--- + +**5. Team invited — no reply yet** + +``` +👋 *Sarah Miller* · 2h 10m · 5 msgs +Team – pending · evan +"Sarah Miller: Notifications completely stopped working after I updated my phone OS. I'm on Android 14…" +/'join 55' +``` + +--- + +**6. Team active — two agents, name with spaces** + +``` +💬 *François Dupont* · 30m · 14 msgs +Team · evan, alex +"François Dupont: OK merci, I will try this and let you know." +/'join 61' +``` + +--- + +**7. Team overdue — customer follow-up unanswered > 2 h** + +``` +⏰ *Wang Fang* · 4h · 19 msgs +Team · alex +"Wang Fang: The app crashes when I open large groups" / "I tried what you suggested but it still doesn't work. Any other ideas?" +/'join 73' +``` + +--- + +#### Card lifecycle + +**Tracking: group customData.** The bot stores the current card's team group message ID (`cardItemId`) in the customer group's `customData` via `apiSetGroupCustomData(groupId, {cardItemId})`. This is the single source of truth for which team group message is the card for a given customer. It survives restarts because `customData` is in the database. + +**Create** — when the customer sends their first message (triggering the Step 2 queue message) or `/grok` as their first message (WELCOME → GROK, skipping Step 2): +1. Bot composes the card as a single message (🆕 for first message, 🤖 for `/grok` as first message; customer name, message preview, `/'join '` as the final line) +2. Bot posts it to the team group via `apiSendTextMessage` → receives back the `chatItemId` +3. Bot writes `{cardItemId: chatItemId}` into the customer group's `customData` + +**Update** (delete + repost) — on every subsequent event: new customer message, team member reply in the customer group, state change (QUEUE → GROK, GROK → TEAM, GROK → QUEUE on join timeout, etc.), agent joining. Card updates are debounced globally — the bot collects all pending card changes and flushes them in a single batch at a configurable interval (default 300 seconds, set via `--card-flush-seconds`). Within a batch, each customer group's card is reposted at most once with the latest state. +1. Bot reads `cardItemId` from the customer group's `customData` +2. Bot deletes the old card in the team group via `apiDeleteChatItem(teamGroupId, cardItemId, "broadcast")` (delete for everyone) +3. Bot composes the new card (updated icon, wait time, message count, preview) +4. Bot posts new card to the team group → receives new `chatItemId` +5. Bot overwrites `customData` with the new `{cardItemId: newChatItemId}` + +If `apiDeleteChatItem` fails (e.g., card was already deleted due to a prior crash), the bot ignores the error and proceeds to post the new card. The new `cardItemId` overwrites `customData`, recovering the lifecycle. + +Because the old card is deleted and the new one is posted at the bottom, the most recently updated conversations always appear last in the team group. + +**Cleanup** — when the customer leaves the group: +1. Bot reads `cardItemId` from `customData` +2. Card is **not deleted** — it remains in the team group until a retention policy is added (resolved state TBD) +3. Bot clears the `cardItemId` from `customData` + +**Completion tracking:** When a card is composed with the ✅ icon (auto-completed), the bot writes `complete: true` into the group's `customData` alongside `cardItemId`. When a customer sends a new message and the card is recomposed as non-✅, the `complete` flag is omitted from the new `customData` (self-healing). This allows the bot to skip completed conversations on restart without re-reading chat history for every group. + +**Restart recovery** — on startup, the bot refreshes existing cards to update wait times, icons, and auto-complete status. It lists all groups, finds those with `customData.cardItemId` set and `customData.complete` not set, sorts by `cardItemId` ascending (higher IDs = more recently updated cards), and re-posts them oldest-first. This ensures the most recently active cards appear at the bottom of the team group (newest position). Completed cards are skipped — they remain as-is until a new customer message triggers the normal event-driven update. Old/pre-bot groups without `customData` are also skipped. The bot attempts to delete the old card message before reposting; deletion failures (e.g., card older than 24h) are silently ignored. Subsequent events resume the normal delete-repost cycle via `customData`. + +#### Team commands + +Team members use these commands in the team group: + +| Command | Effect | +|---------|--------| +| `/join ` | Join the specified customer group as Owner. Card emits the clickable form `/'join '`; the handler reads `groupId` from the framework's structured command (`util.ciBotCommand → {keyword, params}`), not from regex over the message text. | + +`/join` is **team-only** — it is registered as a bot command only in the team group. If a customer sends `/join` in a customer group, the bot treats it as an ordinary message (per the existing rule: unrecognized commands are treated as normal messages). + +#### Joining a customer group + +When a team member taps `/join`, the bot first verifies that the target `groupId` is a business group hosted by the main profile (i.e., has a `businessChat` property). If not, the bot replies with an error in the team group and does nothing. If valid, the bot adds the team member to the customer group (via the shared `addOrFindTeamMember` helper, which promotes to Owner at invite time via `apiSetMembersRole(Owner)`, with a fallback re-promotion on connect). From within the customer group, the team member chats directly with the customer. Their messages trigger card updates in the team group (icon change, wait time reset). The customer sees the team member as a real group participant. + +#### Edge cases + +| Situation | What happens | +|-----------|-------------| +| All team members leave before any sends a message | State stays `TEAM-PENDING` (customer is still waiting for a response). Next `/team` re-adds them silently. | +| Customer leaves | All in-memory state cleaned up; card remains (TBD) | +| No `--auto-add-team-members` (`-a`) configured | `/team` tells customer "no team members available yet" | +| Team member already in customer group | `apiListMembers` lookup finds existing member — no error | + +--- + +## 5. Architecture + +### 5.1 CLI Overview + +``` +GROK_API_KEY=... node dist/index.js --team-group "Support Team" [options] +``` + +**Environment variables:** + +| Var | Required | Purpose | +|-----|----------|---------| +| `GROK_API_KEY` | No | xAI API key for Grok. If unset or empty, the bot starts with Grok API disabled: it logs `"No GROK_API_KEY provided, disabling Grok support"`, the `/grok` command is not registered, customer-facing messages (`queueMessage`, `noTeamMembersMessage`) drop the `/grok` clause, and any `/grok` the customer types is treated as an unrecognized command. Note: `config.grokContactId` is still restored from the state file even when the API is disabled, so the one-way gate can identify and remove Grok members from groups when team takes over. When `GROK_API_KEY` is set, `--context-file` must also be provided — startup fails otherwise. | + +**CLI flags:** + +| Flag | Required | Default | Format | Purpose | +|------|----------|---------|--------|---------| +| `--db-prefix` | No | `./data/simplex` | path | Database file prefix (both profiles share it) | +| `--team-group` | Yes | — | `name` | Team group display name (auto-created if absent, resolved by persisted ID on restarts) | +| `--auto-add-team-members` / `-a` | No | `""` | `ID:name,...` | Comma-separated team member contacts. Validated at startup — exits on mismatch. Without this, `/team` tells customers no members available. | +| `--context-file` | Required when `GROK_API_KEY` set | — | path | Path to the Grok system-prompt / SimpleX documentation context file. Loaded at startup and passed as the `system` message on every Grok API call. Required when `GROK_API_KEY` is set — startup fails otherwise. When missing at runtime (file unreadable), a warning is logged and Grok runs with an empty system prompt. | +| `--timezone` | No | `"UTC"` | IANA tz | For weekend detection (24h vs 48h). Weekend is Saturday 00:00 through Sunday 23:59 in this timezone. | +| `--complete-hours` | No | `3` | number | Hours of customer inactivity after last team/Grok reply before auto-completing a conversation (✅ icon, "done" wait time). | +| `--card-flush-seconds` | No | `300` | number | Seconds between card dashboard update flushes. Lower values give faster updates; higher values reduce message churn. | + +**Why `--auto-add-team-members` (`-a`) uses `ID:name`:** Contact IDs are local to the bot's database — not discoverable externally. The bot DMs each team member their ID when they join the team group. The name is validated at startup to catch stale IDs pointing to the wrong contact. + +**Customer commands** (available as tappable buttons in customer business chats; see implementation plan §7 for the per-group lazy sync): + +| Command | Available | Effect | +|---------|-----------|--------| +| `/grok` | Before any team member sends a message, and only if `GROK_API_KEY` is set | Enter Grok mode | +| `/team` | QUEUE or GROK state | Add team members, permanently enter Team mode once any replies | + +**Unrecognized commands** are treated as normal messages in the current mode. When Grok is disabled (no `GROK_API_KEY`), `/grok` is not registered in the bot command list and, if typed manually, falls into this "unrecognized" path. + +**Team commands** (registered in team group via `groupPreferences`): + +| Command | Effect | +|---------|--------| +| `/join ` | Join the specified customer group as Owner. Card emits the clickable form `/'join '`; the handler reads `groupId` from the framework's structured command (`util.ciBotCommand → {keyword, params}`), not from regex over the message text. | + +### 5.2 Bot Architecture + +The bot process runs a single `ChatApi` instance with **two user profiles**: + +- **Main profile** — the support bot's account ("Ask SimpleX Team"). Owns the business address, hosts all business groups, communicates with customers, communicates with the team group, and controls group membership. On startup the bot checks the main profile for an existing business address via `apiGetUserAddress`; if none exists (first run), it creates one via `apiCreateBusinessAddress`. The address is stored in the SimpleX database as part of the profile — it survives restarts and state file loss without re-creation. The business address link is printed to stdout on every startup. +- **Grok profile** — the Grok agent's account (display name "Grok"). Is invited into customer groups as a Member. Sends Grok's responses so they appear to come from the Grok identity. The Grok user is created by the bot on first run via `apiCreateActiveUser` and its `userId` is persisted to `state.json` as `grokUserId`; subsequent runs look it up by ID (never by name — a renamed profile would silently break name-based matching). On startup, if the profile already exists, the bot compares its current profile (display name, image) against the desired values and calls `apiUpdateProfile()` if anything changed — this pushes the update to all Grok contacts so profile picture changes take effect immediately. + +``` +┌─────────────────────────────────────────────────┐ +│ Support Bot Process (Node.js) │ +│ │ +│ chat: ChatApi ← ChatApi.init("./data/simplex") │ +│ Single database, two user profiles │ +│ │ +│ mainUserId ← "Ask SimpleX Team" profile │ +│ • Business address, event routing, state mgmt │ +│ • Controls group membership │ +│ │ +│ grokUserId ← "Grok" profile │ +│ • Joins customer groups as Member │ +│ • Sends Grok responses into groups │ +│ │ +│ profileMutex: serialize apiSetActiveUser + call │ +│ GrokApiClient → api.x.ai/v1/chat/completions │ +└─────────────────────────────────────────────────┘ +``` + +Before each SimpleX API call, the bot switches to the appropriate profile via `apiSetActiveUser(userId)`. All profile-switching and SimpleX API calls are serialized through a mutex to prevent interleaving. The Grok HTTP API call (external network request to xAI) is made **outside** the mutex — only the profile switch + SimpleX read/send calls need serialization. This prevents a slow Grok response from blocking all other bot operations. + +**Event delivery is profile-independent.** ChatApi delivers events for all user profiles in the database, not just the active one. Every event includes a `user` field identifying which profile it belongs to. `apiSetActiveUser` only affects the context for write/send API calls — it does not filter event subscription. The bot routes events by checking `event.user`: main profile events go to the main handler, Grok profile events go to the Grok handler. + +The Grok profile is self-contained: it watches its own events (`newChatItems`, `receivedGroupInvitation`), calls the Grok HTTP API, and sends responses — all using group IDs from its own events. The main profile only controls Grok's group membership (invite/remove) and reflects Grok's responses in the team group card. + +### 5.3 Grok Integration + +Grok is not a service call hidden behind the bot's account. It is a **second user profile** within the same SimpleX Chat process and database. The customer sees messages from "Grok" as a real group participant — not from the support bot. This is what makes Grok transparent to the user. + +The Grok profile is **self-contained**: it watches its own events, reads group history through its own view, calls the Grok HTTP API, and sends responses — all using its own local group IDs from its own events. No cross-profile ID mapping is needed. + +#### Startup: establishing the bot↔Grok contact + +On first run (no state file), the bot must establish a SimpleX contact between the main and Grok profiles: + +1. Main profile creates a one-time invite link +2. Grok profile connects to it +3. The bot waits up to 60 seconds for `contactConnected` to fire +4. The resulting `grokContactId` is written to the state file + +On subsequent runs, the bot always looks up `grokContactId` from the state file and verifies it still exists in the main profile's contact list — even when `GROK_API_KEY` is not set. This ensures the one-way gate can identify and remove Grok members from groups when a team member sends a text message, preventing "phantom" Grok members that would cause dual responses if Grok is later re-enabled. If the contact is not found and Grok is enabled, it is re-established. + +#### Per-conversation: how Grok joins a group + +When a customer sends `/grok`: + +**Main profile side (failure detection):** +1. Bot sends "Inviting Grok, please wait..." to the customer group +2. Main profile: `apiAddMember(groupId, grokContactId, Member)` — invites the Grok contact to the customer's business group. If `groupDuplicateMember` (customer sent `/grok` again before join completed), the duplicate activation returns silently — the in-flight one handles the outcome. +3. The `member.memberId` is stored in an in-memory map `pendingGrokJoins: memberId → mainGroupId`. Any invitation event that arrived during the `apiAddMember` await (race condition) is drained from the buffer and processed immediately. +4. Main profile receives `connectedToGroupMember` for any member connecting in the group. The bot checks the event's `memberId` against `pendingGrokJoins` — only a match resolves the 120-second promise. This promise is only for failure detection — if it times out, the bot notifies the customer and falls back to QUEUE. + +**Grok profile side (independent, triggered by its own events):** +5. Grok profile receives a `receivedGroupInvitation` event. If a matching `pendingGrokJoins` entry exists, auto-accepts via `apiJoinGroup(groupId)`. If not (race: event arrived before step 3), buffers the event for the main profile to drain. +5. Grok profile reads visible history from the group — the last 100 messages — to build the initial Grok API context (customer messages → `user` role) +6. Grok profile calls the Grok HTTP API with this context +7. Grok profile sends the response into the group via `apiSendTextMessage([Group, groupId], response)` — visible to the customer as a message from "Grok" + +**Initial response gating:** When Grok joins a group, the message backlog may trigger per-message responses (via `newChatItems`) at the same time `activateGrok` is sending the initial combined response. To prevent duplicate replies, per-message responses are suppressed (via `grokInitialResponsePending`) until the initial combined response completes. The flag is set before `waitForGrokJoin` and cleared after the initial response is sent (or fails). Without this gate, customers would receive both individual per-message replies AND a combined initial reply — e.g. 3 replies for 2 messages. + +**Card update:** Main profile sees Grok's response as `groupRcv` and updates the team group card (same mechanism as ongoing Grok messages). + +**Visible history** must be enabled on customer groups (the bot enables it alongside file uploads in the business request handler). This allows Grok to read the full conversation history after joining, rather than only seeing messages sent after it joined. If Grok reads history and finds no customer messages (e.g., visible history was disabled or the API call failed), it sends a generic greeting asking the customer to repeat their question. + +#### Per-message: ongoing Grok conversation + +After the initial response, the Grok profile watches its own `newChatItems` events. It only triggers a Grok API call for `groupRcv` messages from the customer — identified via `businessChat.customerId` on the group's `groupInfo` (accessible to all members). Messages from the bot (main profile), from Grok itself (`groupSnd`), and from team members are ignored. Non-text messages (images, files, voice) do not trigger Grok API calls but still trigger a card update in the team group. + +**Batch deduplication:** When multiple customer messages arrive in a single `newChatItems` event (e.g., rapid messages delivered as a batch), only the last customer message per group triggers a Grok API call. Earlier messages are included in the history context via `apiGetChat`, so the single response addresses all messages in the batch. Without this, each message in the batch would trigger a separate API call, and the earlier calls would include later messages in their history — producing incoherent responses that reference messages "from the future." + +Every subsequent customer text message in a group where Grok is a member: +1. Triggers a card update in the team group (via the main profile, which sees the customer message as `groupRcv`) +2. Grok profile receives the message via its own event, rebuilds history by reading the last 100 messages from its own view of the group (Grok's messages → `assistant` role, customer's messages → `user` role) +3. Grok profile calls the Grok HTTP API and sends the response into the group using the group ID from its own event +4. Main profile sees Grok's response as `groupRcv` and updates the team group card + +In Grok mode, each customer message triggers two card updates — one on receipt (reflecting the new message and updated wait time) and one after Grok responds. This gives the team real-time visibility into active Grok conversations. + +If the Grok HTTP API call fails or times out for a per-message request, the Grok profile sends an error message into the group: "Sorry, I couldn't process that. Please try again or send /team for a human team member." Grok remains in the group and the state stays GROK — the customer can retry by sending another message. + +Grok API calls are NOT serialized per customer group in the MVP. If a new customer message arrives while a Grok API call is in flight, a second call runs concurrently — `apiGetChat` is re-read at the start of each call so history converges eventually, but two rapid messages in the same group can produce interleaved context. Cross-group calls run concurrently by design (see implementation plan §10 "Cross-group Grok parallelism"). Per-group serialization is a planned future improvement. + +#### Grok removal + +Grok is removed from the group (via main profile `apiRemoveMembers`) in three cases: +1. Team member sends their first text message in the customer group +2. Grok join fails (120-second timeout) — graceful fallback to QUEUE, bot notifies the customer +3. Customer leaves the group + +### 5.4 Persistent State + +The bot writes a single JSON file (`{dbPrefix}_state.json`) that survives restarts. It uses the same `--db-prefix` as the SimpleX database files, so the state file is always co-located with the database (e.g. `./data/simplex_state.json` alongside `./data/simplex_chat.db` and `./data/simplex_agent.db`). This ensures backups and migrations that copy the database directory also capture the bot state. + +#### Why a state file at all? + +SimpleX Chat's own database stores the full message history and group membership, but it does not store the bot's derived knowledge — things like which team group was created on first run, or which contact is the established bot↔Grok link. Per-conversation state (QUEUE/GROK/TEAM-PENDING/TEAM) is written into the customer group's `customData` at the moment the bot handles each transition — it observes its own `/grok` invite, `/team` add, team message, first customer message. Only display data (message counts, timestamps, sender names) is re-derived from chat history on demand. + +#### What is persisted and why + +| Key | Type | Why persisted | What breaks without it | +|-----|------|---------------|------------------------| +| `teamGroupId` | number | The bot creates the team group on first run; subsequent runs must find the same group | Bot creates a new empty team group on every restart; all team members lose their dashboard | +| `grokContactId` | number | Establishing a bot↔Grok contact takes up to 60 seconds and is a one-time setup | Every restart requires a 60-second re-connection; if it fails the bot exits | +| `grokUserId` | number | The bot creates the Grok user on first run; subsequent runs identify it by ID so a renamed profile cannot be silently mistaken for the main user | Startup restore (active-user recovery) and Grok profile resolution would fall back to display-name matching — fragile to any rename of the Grok profile | + +The `mainUserId` is **not** persisted — it is resolved at startup from `bot.run()`, which creates the main profile on a fresh DB and returns the user object. + +#### What is NOT persisted and why + +Per-group state (`state`, `cardItemId`, `complete`) lives in SimpleX's database as the group's `customData` — persisted there rather than in the bot's state file. + +| State | Where it lives instead | +|-------|----------------------| +| `state, cardItemId, complete` (per group) | Stored in the group's customData — conversation state, card message ID, auto-completed flag. `state` is written at event time (first customer message, `/grok`, `/team`, team's first message); the bot never re-derives it by scanning chat history. | +| Last customer message time | Derived from most recent customer message in chat history | +| Message count | Derived from message count in chat history (all messages except the bot's own) | +| Customer name | Always available from the group's display name | +| Who sent last message | Derived from recent chat history | +| `pendingGrokJoins` | In-flight during the 120-second join window only | +| Owner role promotion | Not tracked — the bot promotes team members to Owner at two idempotent points: (1) at invite time, immediately after `apiAddMember` in `addOrFindTeamMember` (skipped if the member is already in the group); (2) on every `memberConnected` in a customer group (unless the member is the customer or Grok). Survives restarts. | +| `pendingTeamDMs` | Messages queued to greet team members — simply not sent if lost | +| `grokJoinResolvers`, `grokFullyConnected` | Pure async synchronization primitives — always empty at startup | + +#### Failure modes + +If the state file is deleted or corrupted: +- A new team group is created. Team members must re-join it. +- The bot↔Grok contact is re-established (60-second startup delay). +- Grok remains in any groups it was already a member of. Since the Grok profile watches its own events, it will continue responding to customer messages in those groups without any additional recovery — no cross-profile state needs to be rebuilt. diff --git a/apps/simplex-support-bot/src/bot.ts b/apps/simplex-support-bot/src/bot.ts new file mode 100644 index 0000000000..a25c05ce8a --- /dev/null +++ b/apps/simplex-support-bot/src/bot.ts @@ -0,0 +1,920 @@ +import {api, util} from "simplex-chat" +import {T, CEvt} from "@simplex-chat/types" +import {Config} from "./config.js" +import {GrokMessage, GrokApiClient} from "./grok.js" +import {CardManager, ConversationState} from "./cards.js" +import { + queueMessage, grokInvitingMessage, grokActivatedMessage, teamAddedMessage, + teamAlreadyInvitedMessage, teamLockedMessage, noTeamMembersMessage, + grokUnavailableMessage, grokErrorMessage, grokNoHistoryMessage, +} from "./messages.js" +import {profileMutex, log, logError} from "./util.js" + +// True for any non-terminal status — invited but not yet accepted, through +// connected. Used to decide whether a contact is already in the group so we +// don't trigger a re-invite (the SimpleX API resends the invitation for a +// member in GSMemInvited). +function isInGroup(m: T.GroupMember): boolean { + switch (m.memberStatus) { + case T.GroupMemberStatus.Rejected: + case T.GroupMemberStatus.Removed: + case T.GroupMemberStatus.Left: + case T.GroupMemberStatus.Deleted: + case T.GroupMemberStatus.Unknown: + return false + default: + return true + } +} + +export class SupportBot { + // Card manager + cards: CardManager + + // Grok group mapping: memberId → mainGroupId (for pending joins) + private pendingGrokJoins = new Map() + // Buffered invitations that arrived before pendingGrokJoins was set (race condition) + private bufferedGrokInvitations = new Map() + // mainGroupId → grokLocalGroupId + private grokGroupMap = new Map() + // grokLocalGroupId → mainGroupId + private reverseGrokMap = new Map() + // mainGroupId → resolve fn for grok join + private grokJoinResolvers = new Map void>() + // mainGroupIds where Grok connectedToGroupMember fired + private grokFullyConnected = new Set() + // Suppress per-message Grok responses while activateGrok sends the initial combined response + private grokInitialResponsePending = new Set() + + // Pending DMs for team group members (contactId → message) + private pendingTeamDMs = new Map() + // Contacts that already received the team DM (dedup) + private sentTeamDMs = new Set() + + // Tracked fire-and-forget operations (for testing) + private _pendingOps: Promise[] = [] + + // Bot's business address link + businessAddress: string | null = null + + // Groups whose groupPreferences.commands we've already verified/synced + // in this process. Populated lazily by syncGroupCommands() on the first + // send to each group. + private syncedGroups = new Set() + + constructor( + private chat: api.ChatApi, + private grokApi: GrokApiClient | null, + private config: Config, + private mainUserId: number, + private grokUserId: number | null, + private desiredCommands: T.ChatBotCommand[], + ) { + this.cards = new CardManager(chat, config, mainUserId, config.cardFlushSeconds * 1000) + } + + private get grokEnabled(): boolean { + return this.grokApi !== null + } + + // Wait for all fire-and-forget operations to settle (for testing) + async flush(): Promise { + while (this._pendingOps.length > 0) { + const ops = this._pendingOps.splice(0) + await Promise.allSettled(ops) + } + } + + private fireAndForget(op: Promise): void { + const tracked = op.catch(err => logError("async operation error", err)) + this._pendingOps.push(tracked) + tracked.finally(() => { + const idx = this._pendingOps.indexOf(tracked) + if (idx >= 0) this._pendingOps.splice(idx, 1) + }) + } + + // --- Profile-switching helpers --- + + private async withMainProfile(fn: () => Promise): Promise { + return profileMutex.runExclusive(async () => { + await this.chat.apiSetActiveUser(this.mainUserId) + return fn() + }) + } + + // Ensure this group's groupPreferences.commands match desiredCommands, + // so commands in outgoing messages render as clickable for members of + // this group. Scoped to the group (apiUpdateGroupProfile broadcasts + // XGrpInfo/XGrpPrefs to group members only), and cached so we don't + // re-check on every send. Pre-checks local state via apiGetChat so we + // don't issue a no-op broadcast when the group already has the + // commands. + private async syncGroupCommands(groupId: number): Promise { + if (this.syncedGroups.has(groupId)) return + const desiredJSON = JSON.stringify(this.desiredCommands) + const chat = await this.chat.apiGetChat(T.ChatType.Group, groupId, 0) + const info = chat.chatInfo + if (info.type !== "group") return + const gp = info.groupInfo.groupProfile + const currentPrefs = gp.groupPreferences ?? {} + if (JSON.stringify(currentPrefs.commands ?? []) !== desiredJSON) { + await this.chat.apiUpdateGroupProfile(groupId, { + ...gp, + groupPreferences: {...currentPrefs, commands: this.desiredCommands}, + }) + log(`Pushed commands to group ${groupId}`) + } + this.syncedGroups.add(groupId) + } + + private async withGrokProfile(fn: () => Promise): Promise { + if (this.grokUserId === null) throw new Error("Grok is disabled (no GROK_API_KEY)") + const grokUserId = this.grokUserId + return profileMutex.runExclusive(async () => { + await this.chat.apiSetActiveUser(grokUserId) + return fn() + }) + } + + // --- Main profile event handlers --- + + async onBusinessRequest(evt: CEvt.AcceptingBusinessRequest): Promise { + const groupId = evt.groupInfo.groupId + try { + const profile = evt.groupInfo.groupProfile + await this.withMainProfile(() => + this.chat.apiUpdateGroupProfile(groupId, { + displayName: profile.displayName, + fullName: profile.fullName, + groupPreferences: { + ...profile.groupPreferences, + files: {enable: T.GroupFeatureEnabled.On}, + history: {enable: T.GroupFeatureEnabled.On}, + }, + }) + ) + // file uploads + history enabled + } catch (err) { + logError(`Failed to update business group ${groupId} preferences`, err) + } + } + + async onNewChatItems(evt: CEvt.NewChatItems): Promise { + // Only process events for main profile + if (evt.user.userId !== this.mainUserId) return + for (const ci of evt.chatItems) { + try { + await this.processMainChatItem(ci) + } catch (err) { + logError("Error processing chat item", err) + } + } + } + + async onChatItemUpdated(evt: CEvt.ChatItemUpdated): Promise { + if (evt.user.userId !== this.mainUserId) return + const {chatInfo} = evt.chatItem + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + if (!groupInfo.businessChat) return + this.cards.scheduleUpdate(groupInfo.groupId) + } + + async onChatItemReaction(evt: CEvt.ChatItemReaction): Promise { + if (evt.user.userId !== this.mainUserId) return + if (!evt.added) return + const chatInfo = evt.reaction.chatInfo + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + if (!groupInfo.businessChat) return + this.cards.scheduleUpdate(groupInfo.groupId) + } + + async onLeftMember(evt: CEvt.LeftMember): Promise { + if (evt.user.userId !== this.mainUserId) return + const groupId = evt.groupInfo.groupId + const member = evt.member + const bc = evt.groupInfo.businessChat + if (!bc) return + + if (member.memberId === bc.customerId) { + log(`Customer left group ${groupId}`) + this.cleanupGrokMaps(groupId) + try { await this.cards.clearCustomData(groupId) } catch {} + return + } + + if (this.config.grokContactId !== null && member.memberContactId === this.config.grokContactId) { + log(`Grok left group ${groupId}`) + this.cleanupGrokMaps(groupId) + return + } + + if (this.config.teamMembers.some(tm => tm.id === member.memberContactId)) { + log(`Team member left group ${groupId}`) + } + } + + async onJoinedGroupMember(evt: CEvt.JoinedGroupMember): Promise { + if (evt.user.userId !== this.mainUserId) return + if (evt.groupInfo.groupId === this.config.teamGroup.id) { + await this.sendTeamMemberDM(evt.member) + } + } + + async onMemberConnected(evt: CEvt.ConnectedToGroupMember): Promise { + if (evt.user.userId !== this.mainUserId) return + const groupId = evt.groupInfo.groupId + + // Team group → send DM (if not already sent by onJoinedGroupMember) + if (groupId === this.config.teamGroup.id) { + await this.sendTeamMemberDM(evt.member, evt.memberContact) + return + } + + // Customer group → promote to Owner (unless customer or Grok). Idempotent per plan §11. + const bc = evt.groupInfo.businessChat + if (bc) { + const isCustomer = evt.member.memberId === bc.customerId + const isGrok = this.config.grokContactId !== null + && evt.member.memberContactId === this.config.grokContactId + if (!isCustomer && !isGrok) { + try { + await this.withMainProfile(() => + this.chat.apiSetMembersRole(groupId, [evt.member.groupMemberId], T.GroupMemberRole.Owner) + ) + log(`Promoted member ${evt.member.groupMemberId} to Owner in group ${groupId}`) + } catch (err) { + logError(`Failed to promote member in group ${groupId}`, err) + } + } + } + } + + async onMemberContactReceivedInv(evt: CEvt.NewMemberContactReceivedInv): Promise { + if (evt.user.userId !== this.mainUserId) return + const {contact, groupInfo, member} = evt + if (groupInfo.groupId === this.config.teamGroup.id) { + if (this.sentTeamDMs.has(contact.contactId)) return + log(`DM contact from team group member: ${contact.contactId}:${member.memberProfile.displayName}`) + const name = member.memberProfile.displayName + const formatted = name.includes(" ") ? `'${name}'` : name + const msg = `Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is ${contact.contactId}:${formatted}` + // Try sending immediately — contact may already be usable + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Direct, contact.contactId], msg) + ) + this.sentTeamDMs.add(contact.contactId) + log(`Sent DM to team member ${contact.contactId}:${name}`) + } catch { + // Not ready yet — queue for contactConnected / contactSndReady + this.pendingTeamDMs.set(contact.contactId, msg) + log(`Queued DM for team member ${contact.contactId}:${name}`) + } + } + } + + async onContactConnected(evt: CEvt.ContactConnected): Promise { + if (evt.user.userId !== this.mainUserId) return + await this.deliverPendingDM(evt.contact.contactId) + } + + async onContactSndReady(evt: CEvt.ContactSndReady): Promise { + if (evt.user.userId !== this.mainUserId) return + await this.deliverPendingDM(evt.contact.contactId) + } + + private async deliverPendingDM(contactId: number): Promise { + if (this.sentTeamDMs.has(contactId)) { + this.pendingTeamDMs.delete(contactId) + return + } + const pendingMsg = this.pendingTeamDMs.get(contactId) + if (pendingMsg === undefined) return + this.pendingTeamDMs.delete(contactId) + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Direct, contactId], pendingMsg) + ) + this.sentTeamDMs.add(contactId) + log(`Sent DM to team member ${contactId}`) + } catch (err) { + logError(`Failed to send DM to team member ${contactId}`, err) + } + } + + // --- Grok profile event handlers --- + + async onGrokGroupInvitation(evt: CEvt.ReceivedGroupInvitation): Promise { + if (evt.user.userId !== this.grokUserId) return + const memberId = evt.groupInfo.membership.memberId + const mainGroupId = this.pendingGrokJoins.get(memberId) + if (mainGroupId === undefined) { + // Buffer: invitation may arrive before pendingGrokJoins is set (race with apiAddMember) + this.bufferedGrokInvitations.set(memberId, evt) + return + } + this.pendingGrokJoins.delete(memberId) + this.bufferedGrokInvitations.delete(memberId) + await this.processGrokInvitation(evt, mainGroupId) + } + + private async processGrokInvitation(evt: CEvt.ReceivedGroupInvitation, mainGroupId: number): Promise { + log(`Grok joining group: mainGroupId=${mainGroupId}, grokGroupId=${evt.groupInfo.groupId}`) + try { + await this.withGrokProfile(() => this.chat.apiJoinGroup(evt.groupInfo.groupId)) + } catch (err) { + logError(`Grok failed to join group ${evt.groupInfo.groupId}`, err) + return + } + this.grokGroupMap.set(mainGroupId, evt.groupInfo.groupId) + this.reverseGrokMap.set(evt.groupInfo.groupId, mainGroupId) + } + + async onGrokMemberConnected(evt: CEvt.ConnectedToGroupMember): Promise { + if (evt.user.userId !== this.grokUserId) return + const grokGroupId = evt.groupInfo.groupId + const mainGroupId = this.reverseGrokMap.get(grokGroupId) + if (mainGroupId === undefined) return + this.grokFullyConnected.add(mainGroupId) + const resolver = this.grokJoinResolvers.get(mainGroupId) + if (resolver) { + this.grokJoinResolvers.delete(mainGroupId) + log(`Grok fully connected: mainGroupId=${mainGroupId}, grokGroupId=${grokGroupId}`) + resolver() + } + } + + async onGrokNewChatItems(evt: CEvt.NewChatItems): Promise { + if (evt.user.userId !== this.grokUserId) return + // When multiple customer messages arrive in one batch, only respond to the + // last per group — earlier messages are included in its history context. + const lastPerGroup = new Map() + for (const ci of evt.chatItems) { + const {chatInfo, chatItem} = ci + if (chatInfo.type !== "group") continue + if (chatItem.chatDir.type !== "groupRcv") continue + if (!util.ciContentText(chatItem)?.trim()) continue + if (util.ciBotCommand(chatItem)) continue + const bc = chatInfo.groupInfo.businessChat + if (!bc) continue + if (chatItem.chatDir.groupMember.memberId !== bc.customerId) continue + lastPerGroup.set(chatInfo.groupInfo.groupId, ci) + } + // Groups are independent — avoid serializing one group's xAI latency across the others. + await Promise.allSettled( + [...lastPerGroup.values()].map((ci) => this.processGrokChatItem(ci)), + ) + } + + // --- Main profile message routing --- + + private async processMainChatItem(ci: T.AChatItem): Promise { + const {chatInfo, chatItem} = ci + + // 1. Direct text message → reply with business address + if (chatInfo.type === "direct" && chatItem.chatDir.type === "directRcv" + && (chatItem.content as any).type === "rcvMsgContent") { + if (this.businessAddress) { + const contactId = chatInfo.contact.contactId + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage( + [T.ChatType.Direct, contactId], + `Please use my business address to ask questions: ${this.businessAddress}`, + ) + ) + } catch (err) { + logError(`Failed to reply to direct message from contact ${contactId}`, err) + } + } + return + } + + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + const groupId = groupInfo.groupId + + // 2. Team group → handle /join + if (groupId === this.config.teamGroup.id) { + await this.processTeamGroupMessage(chatItem) + return + } + + // 3. Skip non-business groups + if (!groupInfo.businessChat) return + + // 4. Skip own messages + if (chatItem.chatDir.type === "groupSnd") return + if (chatItem.chatDir.type !== "groupRcv") return + + const sender = chatItem.chatDir.groupMember + const bc = groupInfo.businessChat + const isCustomer = sender.memberId === bc.customerId + + // 6. Non-customer message → one-way gate check + card update + if (!isCustomer) { + const isTeam = this.config.teamMembers.some(tm => tm.id === sender.memberContactId) + + if (isTeam && util.ciContentText(chatItem)?.trim()) { + // One-way gate: first team text → transition to TEAM + remove Grok + const data = await this.cards.getRawCustomData(groupId) + if (data?.state !== "TEAM") { + await this.cards.mergeCustomData(groupId, {state: "TEAM"}) + const {grokMember} = await this.cards.getGroupComposition(groupId) + if (grokMember) { + log(`One-way gate: team message in group ${groupId}, removing Grok`) + try { + await this.withMainProfile(() => + this.chat.apiRemoveMembers(groupId, [grokMember.groupMemberId]) + ) + } catch { + // may have already left + } + this.cleanupGrokMaps(groupId) + } + } + } + // Schedule card update for any non-customer message (team or Grok) + this.cards.scheduleUpdate(groupId) + return + } + + // 8. Customer message → derive state and dispatch + const state = await this.cards.deriveState(groupId) + const rawCmd = util.ciBotCommand(chatItem) + // When Grok is disabled, ignore /grok so it behaves like an unknown command + const cmd = rawCmd?.keyword === "grok" && !this.grokEnabled ? null : rawCmd + const text = util.ciContentText(chatItem)?.trim() || null + + switch (state) { + case "WELCOME": + if (cmd?.keyword === "grok") { + // WELCOME → GROK (skip queue msg). Write state optimistically so the + // card renders with GROK icon/label; activateGrok will revert via + // setStateOnFail if activation fails. + // Fire-and-forget: activateGrok awaits future events (waitForGrokJoin) + // which would deadlock the sequential event loop if awaited here. + await this.cards.mergeCustomData(groupId, {state: "GROK"}) + await this.cards.createCard(groupId, groupInfo) + this.fireAndForget(this.activateGrok(groupId, {sendQueueOnFail: true, setStateOnFail: "QUEUE"})) + return + } + if (cmd?.keyword === "team") { + // activateTeam writes state=TEAM-PENDING before the add loop + await this.activateTeam(groupId) + await this.cards.createCard(groupId, groupInfo) + return + } + // First regular message → QUEUE + if (text) { + await this.cards.mergeCustomData(groupId, {state: "QUEUE"}) + await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + await this.cards.createCard(groupId, groupInfo) + } + break + + case "QUEUE": + if (cmd?.keyword === "grok") { + // Write state optimistically; activateGrok reverts to QUEUE on failure + await this.cards.mergeCustomData(groupId, {state: "GROK"}) + this.fireAndForget(this.activateGrok(groupId, {setStateOnFail: "QUEUE"})) + } else if (cmd?.keyword === "team") { + await this.activateTeam(groupId) + } + this.cards.scheduleUpdate(groupId) + break + + case "GROK": + if (cmd?.keyword === "team") { + await this.activateTeam(groupId) + } else if (cmd?.keyword === "grok") { + // Already in grok mode — ignore + } else if (text) { + // Customer text → Grok responds (handled by Grok profile's onGrokNewChatItems) + // Just schedule card update for the customer message + } + this.cards.scheduleUpdate(groupId) + break + + case "TEAM-PENDING": + if (cmd?.keyword === "grok") { + // Invite Grok if not present; state stays TEAM-PENDING + const {grokMember} = await this.cards.getGroupComposition(groupId) + if (!grokMember) { + this.fireAndForget(this.activateGrok(groupId)) + } + // else: already present, ignore + } else if (cmd?.keyword === "team") { + // activateTeam handles "already invited" reply (team still present) + // or silent re-add (team has all left) + await this.activateTeam(groupId) + } + this.cards.scheduleUpdate(groupId) + break + + case "TEAM": + if (cmd?.keyword === "grok") { + await this.sendToGroup(groupId, teamLockedMessage) + } else if (cmd?.keyword === "team") { + // Team still present → "already invited"; team all left → silent re-add + await this.activateTeam(groupId) + } + this.cards.scheduleUpdate(groupId) + break + } + } + + // --- Grok profile message processing --- + + private async processGrokChatItem(ci: T.AChatItem): Promise { + if (!this.grokApi) return + const grokApi = this.grokApi + const {chatInfo, chatItem} = ci + if (chatInfo.type !== "group") return + const groupInfo = chatInfo.groupInfo + const grokGroupId = groupInfo.groupId + + // Skip while activateGrok is sending the initial combined response + const mainGroupId = this.reverseGrokMap.get(grokGroupId) + if (mainGroupId !== undefined && this.grokInitialResponsePending.has(mainGroupId)) return + + // Only process received text messages from customer + if (chatItem.chatDir.type !== "groupRcv") return + const text = util.ciContentText(chatItem)?.trim() + if (!text) return // ignore non-text + + // Ignore bot commands + if (util.ciBotCommand(chatItem)) return + + // Only respond in business groups (survives restart without in-memory maps) + const bc = groupInfo.businessChat + if (!bc) return + + // Only respond to customer messages, not bot or team messages + if (chatItem.chatDir.groupMember.memberId !== bc.customerId) return + + // Read history from Grok's own view + try { + const chat = await this.withGrokProfile(() => + this.chat.apiGetChat(T.ChatType.Group, grokGroupId, 100) + ) + const history: GrokMessage[] = [] + for (const histCi of chat.chatItems) { + const histText = util.ciContentText(histCi)?.trim() + if (!histText) continue + if (histCi.chatDir.type === "groupSnd") { + history.push({role: "assistant", content: histText}) + } else if (histCi.chatDir.type === "groupRcv" + && histCi.chatDir.groupMember.memberId === bc.customerId + && !util.ciBotCommand(histCi)) { + history.push({role: "user", content: histText}) + } + } + + // Don't include the current message in history — it's the userMessage + if (history.length > 0 && history[history.length - 1].role === "user" + && history[history.length - 1].content === text) { + history.pop() + } + + // Call Grok API (outside mutex) + const response = await grokApi.chat(history, text) + + // Send response via Grok profile + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokGroupId], response) + ) + } catch (err) { + logError(`Grok per-message error for grokGroup ${grokGroupId}`, err) + try { + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokGroupId], grokErrorMessage) + ) + } catch {} + } + + // Card update scheduled by main profile seeing the groupRcv events + } + + // --- Grok activation --- + + private async activateGrok( + groupId: number, + opts: {sendQueueOnFail?: boolean; setStateOnFail?: ConversationState} = {}, + ): Promise { + if (!this.grokApi) return + const grokApi = this.grokApi + const revertStateOnFail = async () => { + if (!opts.setStateOnFail) return + const current = await this.cards.getRawCustomData(groupId) + if (current?.state !== "GROK") return + await this.cards.mergeCustomData(groupId, {state: opts.setStateOnFail}) + } + if (this.config.grokContactId === null) { + await revertStateOnFail() + await this.sendToGroup(groupId, grokUnavailableMessage) + if (opts.sendQueueOnFail) await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + this.cards.scheduleUpdate(groupId) + return + } + + // Pre-check: silent return if Grok is already in the group in any + // non-terminal status. The apiAddMember/groupDuplicateMember catch below + // handles Connected/etc. but the SimpleX API resends the invitation for + // GSMemInvited (no error thrown), so without this check a /grok issued + // while a previous activation is still pending would re-trigger the invite. + const grokMembers = await this.withMainProfile(() => this.chat.apiListMembers(groupId)) + if (grokMembers.some(m => m.memberContactId === this.config.grokContactId && isInGroup(m))) { + return + } + + // Gate MUST be up before apiAddMember / pendingGrokJoins / reverseGrokMap — + // any later and onGrokNewChatItems can fire a duplicate per-message reply. + this.grokInitialResponsePending.add(groupId) + try { + await this.sendToGroup(groupId, grokInvitingMessage) + + let member: T.GroupMember + try { + member = await this.withMainProfile(() => + this.chat.apiAddMember(groupId, this.config.grokContactId!, T.GroupMemberRole.Member) + ) + } catch (err: unknown) { + const chatErr = err as {chatError?: {errorType?: {type?: string}}} + if (chatErr?.chatError?.errorType?.type === "groupDuplicateMember") { + // Grok already in group (e.g. customer sent /grok again before join completed) — + // the in-flight activation will handle the outcome, just return silently + return + } + logError(`Failed to invite Grok to group ${groupId}`, err) + await revertStateOnFail() + await this.sendToGroup(groupId, grokUnavailableMessage) + if (opts.sendQueueOnFail) await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + this.cards.scheduleUpdate(groupId) + return + } + + this.pendingGrokJoins.set(member.memberId, groupId) + + // Drain buffered invitation that arrived during the apiAddMember await + const buffered = this.bufferedGrokInvitations.get(member.memberId) + if (buffered) { + this.bufferedGrokInvitations.delete(member.memberId) + this.pendingGrokJoins.delete(member.memberId) + await this.processGrokInvitation(buffered, groupId) + } + + const joined = await this.waitForGrokJoin(groupId, 120_000) + if (!joined) { + this.pendingGrokJoins.delete(member.memberId) + try { + await this.withMainProfile(() => + this.chat.apiRemoveMembers(groupId, [member.groupMemberId]) + ) + } catch {} + this.cleanupGrokMaps(groupId) + await revertStateOnFail() + await this.sendToGroup(groupId, grokUnavailableMessage) + if (opts.sendQueueOnFail) await this.sendToGroup(groupId, queueMessage(this.config.timezone, this.grokEnabled)) + this.cards.scheduleUpdate(groupId) + return + } + + await this.sendToGroup(groupId, grokActivatedMessage) + + // Grok joined — send initial response based on customer's accumulated messages + try { + const grokLocalGId = this.grokGroupMap.get(groupId) + if (grokLocalGId === undefined) { + await this.sendToGroup(groupId, grokUnavailableMessage) + return + } + + // Read history from Grok's own view — only customer messages. + // The previous `grokBc && ...` short-circuit let bot and team + // messages through when Grok's view had no businessChat; require + // grokBc.customerId to be present and match strictly. + const chat = await this.withGrokProfile(() => + this.chat.apiGetChat(T.ChatType.Group, grokLocalGId, 100) + ) + const grokBc = chat.chatInfo.type === "group" ? chat.chatInfo.groupInfo.businessChat : null + const customerMessages: string[] = [] + for (const ci of chat.chatItems) { + if (ci.chatDir.type !== "groupRcv") continue + if (!grokBc || ci.chatDir.groupMember.memberId !== grokBc.customerId) continue + const t = util.ciContentText(ci)?.trim() + if (t && !util.ciBotCommand(ci)) customerMessages.push(t) + } + + if (customerMessages.length === 0) { + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokLocalGId], grokNoHistoryMessage) + ) + return + } + + const initialMsg = customerMessages.join("\n") + const response = await grokApi.chat([], initialMsg) + + await this.withGrokProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Group, grokLocalGId], response) + ) + } catch (err) { + logError(`Grok initial response failed for group ${groupId}`, err) + await this.sendToGroup(groupId, grokUnavailableMessage) + } + } finally { + this.grokInitialResponsePending.delete(groupId) + } + } + + // --- Team activation --- + + private async activateTeam(groupId: number): Promise { + if (this.config.teamMembers.length === 0) { + await this.sendToGroup(groupId, noTeamMembersMessage(this.grokEnabled)) + return + } + + const data = await this.cards.getRawCustomData(groupId) + const alreadyActivated = data?.state === "TEAM-PENDING" || data?.state === "TEAM" + if (alreadyActivated) { + const {teamMembers} = await this.cards.getGroupComposition(groupId) + if (teamMembers.length > 0) { + await this.sendToGroup(groupId, teamAlreadyInvitedMessage) + return + } + // Team previously activated but all team members have since left — + // re-add silently (no teamAddedMessage). State stays TEAM-PENDING/TEAM. + for (const tm of this.config.teamMembers) { + try { + await this.addOrFindTeamMember(groupId, tm.id) + } catch (err) { + logError(`Failed to add team member ${tm.id} to group ${groupId}`, err) + } + } + return + } + + // First activation — write state BEFORE add loop so concurrent customer + // events observing mid-flight see TEAM-PENDING rather than stale state. + await this.cards.mergeCustomData(groupId, {state: "TEAM-PENDING"}) + + for (const tm of this.config.teamMembers) { + try { + await this.addOrFindTeamMember(groupId, tm.id) + } catch (err) { + logError(`Failed to add team member ${tm.id} to group ${groupId}`, err) + } + } + + const {grokMember} = await this.cards.getGroupComposition(groupId) + await this.sendToGroup(groupId, teamAddedMessage(this.config.timezone, !!grokMember)) + } + + // --- Team group commands --- + + private async processTeamGroupMessage(chatItem: T.ChatItem): Promise { + if (chatItem.chatDir.type !== "groupRcv") return + const senderContactId = chatItem.chatDir.groupMember.memberContactId + if (!senderContactId) return + + const cmd = util.ciBotCommand(chatItem) + if (cmd?.keyword !== "join") return + + const targetGroupId = Number.parseInt(cmd.params, 10) + if (Number.isNaN(targetGroupId) || targetGroupId <= 0) { + await this.sendToGroup(this.config.teamGroup.id, `Error: invalid group id "${cmd.params}"`) + return + } + await this.handleJoinCommand(targetGroupId, senderContactId) + } + + private async handleJoinCommand(targetGroupId: number, senderContactId: number): Promise { + // Validate target is a business group + const groups = await this.withMainProfile(() => + this.chat.apiListGroups(this.mainUserId) + ) + const targetGroup = groups.find(g => g.groupId === targetGroupId) + if (!targetGroup?.businessChat) { + await this.sendToGroup(this.config.teamGroup.id, `Error: group ${targetGroupId} is not a business chat`) + return + } + + try { + const member = await this.addOrFindTeamMember(targetGroupId, senderContactId) + if (member) { + log(`Team member ${senderContactId} joined group ${targetGroupId} via /join`) + } + } catch (err) { + logError(`/join failed for group ${targetGroupId}`, err) + await this.sendToGroup(this.config.teamGroup.id, `Error joining group ${targetGroupId}`) + } + } + + // --- Helpers --- + + private async addOrFindTeamMember(groupId: number, teamContactId: number): Promise { + // Pre-check membership: skip apiAddMember entirely if the contact is in + // the group in any non-terminal status. The SimpleX API resends the + // invitation for a member in GSMemInvited, so calling apiAddMember on a + // pending invitee would re-trigger an invite notification. + const members = await this.withMainProfile(() => this.chat.apiListMembers(groupId)) + const existing = members.find(m => m.memberContactId === teamContactId && isInGroup(m)) + if (existing) return existing + const member = await this.withMainProfile(() => + this.chat.apiAddMember(groupId, teamContactId, T.GroupMemberRole.Member) + ) + try { + await this.withMainProfile(() => + this.chat.apiSetMembersRole(groupId, [member.groupMemberId], T.GroupMemberRole.Owner) + ) + } catch { + // Not yet connected — will be promoted in onMemberConnected + } + return member + } + + async sendToGroup(groupId: number, text: string): Promise { + try { + await this.withMainProfile(async () => { + await this.syncGroupCommands(groupId) + await this.chat.apiSendTextMessage([T.ChatType.Group, groupId], text) + }) + } catch (err) { + logError(`Failed to send message to group ${groupId}`, err) + } + } + + private waitForGrokJoin(groupId: number, timeout: number): Promise { + if (this.grokFullyConnected.has(groupId)) return Promise.resolve(true) + return new Promise((resolve) => { + const timer = setTimeout(() => { + this.grokJoinResolvers.delete(groupId) + resolve(false) + }, timeout) + this.grokJoinResolvers.set(groupId, () => { + clearTimeout(timer) + resolve(true) + }) + }) + } + + private async sendTeamMemberDM(member: T.GroupMember, memberContact?: T.Contact): Promise { + const name = member.memberProfile.displayName + const formatted = name.includes(" ") ? `'${name}'` : name + + let contactId = memberContact?.contactId ?? member.memberContactId + if (!contactId) { + // No DM contact yet — create one and send invitation with message + try { + const contact = await this.withMainProfile(() => + this.chat.apiCreateMemberContact(this.config.teamGroup.id, member.groupMemberId) + ) + contactId = contact.contactId as number + log(`Created DM contact ${contactId} for team member ${name}`) + } catch (err) { + logError(`Failed to create member contact for ${name}`, err) + return + } + if (this.sentTeamDMs.has(contactId)) return + const msg = `Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is ${contactId}:${formatted}` + try { + await this.withMainProfile(() => + this.chat.apiSendMemberContactInvitation(contactId!, msg) + ) + this.sentTeamDMs.add(contactId) + this.pendingTeamDMs.delete(contactId) + log(`Sent DM invitation to team member ${contactId}:${name}`) + } catch { + this.pendingTeamDMs.set(contactId, msg) + } + return + } + // Contact already exists — send via normal DM + if (this.sentTeamDMs.has(contactId)) return + const msg = `Added you to be able to invite you to customer chats later, keep this contact. Your contact ID is ${contactId}:${formatted}` + try { + await this.withMainProfile(() => + this.chat.apiSendTextMessage([T.ChatType.Direct, contactId], msg) + ) + this.sentTeamDMs.add(contactId) + this.pendingTeamDMs.delete(contactId) + log(`Sent DM to team member ${contactId}:${name}`) + } catch { + this.pendingTeamDMs.set(contactId, msg) + } + } + + private cleanupGrokMaps(groupId: number): void { + const grokLocalGId = this.grokGroupMap.get(groupId) + this.grokFullyConnected.delete(groupId) + this.grokInitialResponsePending.delete(groupId) + if (grokLocalGId === undefined) return + this.grokGroupMap.delete(groupId) + this.reverseGrokMap.delete(grokLocalGId) + } +} diff --git a/apps/simplex-support-bot/src/cards.ts b/apps/simplex-support-bot/src/cards.ts new file mode 100644 index 0000000000..3d27c036e9 --- /dev/null +++ b/apps/simplex-support-bot/src/cards.ts @@ -0,0 +1,473 @@ +import {T} from "@simplex-chat/types" +import {api, util} from "simplex-chat" +import {Mutex} from "async-mutex" +import {Config} from "./config.js" +import {profileMutex, log, logError} from "./util.js" + +// State derivation types +export type ConversationState = "WELCOME" | "QUEUE" | "GROK" | "TEAM-PENDING" | "TEAM" + +function isConversationState(x: unknown): x is ConversationState { + return x === "WELCOME" || x === "QUEUE" || x === "GROK" || x === "TEAM-PENDING" || x === "TEAM" +} + +export interface GroupComposition { + grokMember: T.GroupMember | undefined + teamMembers: T.GroupMember[] +} + +interface CardData { + state?: ConversationState + cardItemId?: number + complete?: boolean +} + +function isActiveMember(m: T.GroupMember): boolean { + return m.memberStatus === T.GroupMemberStatus.Connected + || m.memberStatus === T.GroupMemberStatus.Complete + || m.memberStatus === T.GroupMemberStatus.Announced +} + +// Prevent ! from triggering SimpleX markdown styled text (color/small). +// The parser treats !N as color markup (N: 1-6, r, g, b, y, c, m, -) +// and closes at the next !. No escape mechanism exists in the parser, +// so we insert a zero-width space to break the trigger pattern. +function escapeStyledMarkdown(text: string): string { + return text.replace(/!([1-6rgbycm-])/g, "!\u200B$1") +} + +// Truncate a single message to ~maxChars, appending [truncated] if needed +function truncateMsg(text: string, maxChars: number): string { + if (text.length <= maxChars) return text + return text.slice(0, maxChars) + "… [truncated]" +} + +// Describe non-text content types +function contentTypeLabel(ci: T.ChatItem): string | null { + const content = ci.content as T.CIContent + if (content.type !== "rcvMsgContent" && content.type !== "sndMsgContent") return null + const mc = content.msgContent + switch (mc.type) { + case "image": return "[image]" + case "video": return "[video]" + case "voice": return "[voice]" + case "file": return "[file]" + default: return null + } +} + +export class CardManager { + private pendingUpdates = new Set() + private flushInterval: NodeJS.Timeout + // Outer lock; profileMutex (via withMainProfile) is the inner lock. + private customDataMutexes = new Map() + + constructor( + private chat: api.ChatApi, + private config: Config, + private mainUserId: number, + flushIntervalMs = 300 * 1000, + ) { + this.flushInterval = setInterval(() => this.flush(), flushIntervalMs) + this.flushInterval.unref() + } + + private async withMainProfile(fn: () => Promise): Promise { + return profileMutex.runExclusive(async () => { + await this.chat.apiSetActiveUser(this.mainUserId) + return fn() + }) + } + + private getCustomDataMutex(groupId: number): Mutex { + let m = this.customDataMutexes.get(groupId) + if (!m) { + m = new Mutex() + this.customDataMutexes.set(groupId, m) + } + return m + } + + scheduleUpdate(groupId: number): void { + this.pendingUpdates.add(groupId) + } + + async createCard(groupId: number, groupInfo: T.GroupInfo): Promise { + const {text} = await this.composeCard(groupId, groupInfo) + const chatRef: T.ChatRef = {chatType: T.ChatType.Group, chatId: this.config.teamGroup.id} + const items = await this.withMainProfile(() => + this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + ) + await this.mergeCustomData(groupId, {cardItemId: items[0].chatItem.meta.itemId}) + } + + async flush(): Promise { + const groups = [...this.pendingUpdates] + this.pendingUpdates.clear() + for (const groupId of groups) { + try { + await this.flushOne(groupId) + } catch (err) { + logError(`Card flush failed for group ${groupId}`, err) + } + } + } + + // Dispatches to create-path when cardItemId is absent so a failed createCard retries. + private async flushOne(groupId: number): Promise { + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const groupInfo = groups.find(g => g.groupId === groupId) + if (!groupInfo) return + const data = groupInfo.customData as Record | undefined + if (typeof data?.cardItemId === "number") { + await this.updateCard(groupId) + } else { + await this.createCard(groupId, groupInfo) + } + } + + async refreshAllCards(): Promise { + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const activeCards: {groupId: number; cardItemId: number}[] = [] + for (const group of groups) { + const customData = group.customData as Record | undefined + if (customData && typeof customData.cardItemId === "number" && !customData.complete) { + activeCards.push({groupId: group.groupId, cardItemId: customData.cardItemId}) + } + } + if (activeCards.length === 0) return + + // Sort ascending by cardItemId — higher ID = more recently updated card. + // Oldest-updated cards refresh first; newest-updated refresh last, + // so the most recent cards end up at the bottom of the team group. + activeCards.sort((a, b) => a.cardItemId - b.cardItemId) + + log(`Startup: refreshing ${activeCards.length} card(s)`) + + for (const {groupId} of activeCards) { + try { + await this.updateCard(groupId) + } catch (err) { + logError(`Startup card refresh failed for group ${groupId}`, err) + } + } + } + + destroy(): void { + clearInterval(this.flushInterval) + } + + // --- State derivation --- + + async getGroupComposition(groupId: number): Promise { + const members = await this.withMainProfile(() => this.chat.apiListMembers(groupId)) + return { + grokMember: members.find(m => + this.config.grokContactId !== null + && m.memberContactId === this.config.grokContactId + && isActiveMember(m)), + teamMembers: members.filter(m => + this.config.teamMembers.some(tm => tm.id === m.memberContactId) + && isActiveMember(m)), + } + } + + async deriveState(groupId: number): Promise { + const data = await this.getRawCustomData(groupId) + return data?.state ?? "WELCOME" + } + + async getLastCustomerMessageTime(groupId: number, customerId: string): Promise { + const chat = await this.getChat(groupId, 20) + for (let i = chat.chatItems.length - 1; i >= 0; i--) { + const ci = chat.chatItems[i] + if (ci.chatDir.type === "groupRcv" && ci.chatDir.groupMember.memberId === customerId) { + return new Date(ci.meta.createdAt).getTime() + } + } + return undefined + } + + async getLastTeamOrGrokMessageTime(groupId: number): Promise { + const chat = await this.getChat(groupId, 20) + for (let i = chat.chatItems.length - 1; i >= 0; i--) { + const ci = chat.chatItems[i] + if (ci.chatDir.type === "groupRcv") { + const contactId = ci.chatDir.groupMember.memberContactId + const isTeam = this.config.teamMembers.some(tm => tm.id === contactId) + const isGrok = this.config.grokContactId !== null && contactId === this.config.grokContactId + if (isTeam || isGrok) return new Date(ci.meta.createdAt).getTime() + } + if (ci.chatDir.type === "groupSnd") { + // Bot's own messages don't count + } + } + return undefined + } + + // --- Custom data --- + + async getRawCustomData(groupId: number): Promise | null> { + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const group = groups.find(g => g.groupId === groupId) + if (!group?.customData) return null + const data = group.customData as Record + const result: Partial = {} + if (isConversationState(data.state)) result.state = data.state + if (typeof data.cardItemId === "number") result.cardItemId = data.cardItemId + if (data.complete === true) result.complete = true + return result + } + + async mergeCustomData(groupId: number, patch: Partial): Promise { + return this.getCustomDataMutex(groupId).runExclusive(async () => { + const current = (await this.getRawCustomData(groupId)) ?? {} + const merged: Partial = {...current, ...patch} + for (const key of Object.keys(merged) as (keyof CardData)[]) { + if (merged[key] === undefined) delete merged[key] + } + await this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId, merged)) + }) + } + + async clearCustomData(groupId: number): Promise { + return this.getCustomDataMutex(groupId).runExclusive(() => + this.withMainProfile(() => this.chat.apiSetGroupCustomData(groupId)) + ) + } + + // --- Chat history access --- + + async getChat(groupId: number, count: number): Promise { + return this.withMainProfile(() => this.chat.apiGetChat(T.ChatType.Group, groupId, count)) + } + + // --- Internal --- + + private async updateCard(groupId: number): Promise { + // Read customData and groupInfo in one apiListGroups call + const groups = await this.withMainProfile(() => this.chat.apiListGroups(this.mainUserId)) + const groupInfo = groups.find(g => g.groupId === groupId) + if (!groupInfo) return + + const customData = groupInfo.customData as Record | undefined + const cardItemId = customData?.cardItemId + if (typeof cardItemId !== "number") return + + try { + await this.withMainProfile(() => + this.chat.apiDeleteChatItems( + T.ChatType.Group, this.config.teamGroup.id, [cardItemId], T.CIDeleteMode.Broadcast + ) + ) + } catch { + // card may already be deleted + } + + const {text, complete} = await this.composeCard(groupId, groupInfo) + const chatRef: T.ChatRef = {chatType: T.ChatType.Group, chatId: this.config.teamGroup.id} + const items = await this.withMainProfile(() => + this.chat.apiSendMessages(chatRef, [ + {msgContent: {type: "text", text}, mentions: {}}, + ]) + ) + const patch: Partial = { + cardItemId: items[0].chatItem.meta.itemId, + complete: complete ? true : undefined, + } + await this.mergeCustomData(groupId, patch) + } + + private async composeCard(groupId: number, groupInfo: T.GroupInfo): Promise<{text: string, complete: boolean}> { + const rawName = groupInfo.groupProfile.displayName || `group-${groupId}` + const customerName = rawName.replace(/\n+/g, " ") + const bc = groupInfo.businessChat + const customerId = bc?.customerId + + const state = await this.deriveState(groupId) + const {teamMembers} = await this.getGroupComposition(groupId) + + const icon = await this.computeIcon(groupId, state, customerId ?? undefined) + const waitStr = await this.computeWaitTime(groupId, state, customerId ?? undefined) + + const chat = await this.getChat(groupId, 100) + const msgCount = chat.chatItems.filter((ci: T.ChatItem) => ci.chatDir.type !== "groupSnd").length + + const stateLabel = this.stateLabel(state) + + const agentNames = teamMembers.map(m => m.memberProfile.displayName) + const agentStr = agentNames.length > 0 ? ` · ${agentNames.join(", ")}` : "" + + const preview = this.buildPreview(chat.chatItems, customerName, customerId) + + // Final line uses /'join ' quoting so SimpleX clients render the full + // command (including the argument) as a single clickable token. + const joinCmd = `/'join ${groupId}'` + + const line1 = `${icon} *${customerName}* · ${waitStr} · ${msgCount} msgs` + const line2 = `${stateLabel}${agentStr}` + return {text: `${line1}\n${line2}\n${preview}\n${joinCmd}`, complete: icon === "✅"} + } + + private async computeIcon( + groupId: number, state: ConversationState, customerId?: string, + ): Promise { + const now = Date.now() + const completeMs = this.config.completeHours * 3600_000 + + // Check auto-complete: last team/Grok message time vs customer silence + const lastTeamGrokTime = await this.getLastTeamOrGrokMessageTime(groupId) + if (lastTeamGrokTime) { + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + // Auto-complete if team/grok replied and customer hasn't responded since, for completeHours + if (!lastCustTime || lastCustTime < lastTeamGrokTime) { + if (now - lastTeamGrokTime >= completeMs) return "✅" + } + } + + switch (state) { + case "QUEUE": { + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (!lastCustTime) return "🟡" + const waitMs = now - lastCustTime + if (waitMs < 5 * 60_000) return "🆕" + if (waitMs < 2 * 3600_000) return "🟡" + return "🔴" + } + case "GROK": + return "🤖" + case "TEAM-PENDING": + return "👋" + case "TEAM": { + // Check if customer follow-up unanswered > 2h + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (lastCustTime && lastTeamGrokTime && lastCustTime > lastTeamGrokTime) { + return (now - lastCustTime > 2 * 3600_000) ? "⏰" : "💬" + } + return "💬" + } + default: + return "🟡" + } + } + + private async computeWaitTime( + groupId: number, _state: ConversationState, customerId?: string, + ): Promise { + const now = Date.now() + const completeMs = this.config.completeHours * 3600_000 + + const lastTeamGrokTime = await this.getLastTeamOrGrokMessageTime(groupId) + if (lastTeamGrokTime) { + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (!lastCustTime || lastCustTime < lastTeamGrokTime) { + if (now - lastTeamGrokTime >= completeMs) return "done" + } + } + + const lastCustTime = customerId + ? await this.getLastCustomerMessageTime(groupId, customerId) + : undefined + if (!lastCustTime) return "<1m" + return this.formatDuration(now - lastCustTime) + } + + private stateLabel(state: ConversationState): string { + switch (state) { + case "QUEUE": return "Queue" + case "GROK": return "Grok" + case "TEAM-PENDING": return "Team – pending" + case "TEAM": return "Team" + default: return "Queue" + } + } + + private buildPreview(chatItems: T.ChatItem[], customerName: string, customerId?: string): string { + const maxTotal = 500 + const maxPer = 200 + + // Collect entries in chronological order (oldest first) + const entries: {senderId: string; name: string; text: string}[] = [] + for (const ci of chatItems) { + if (ci.chatDir.type === "groupSnd") continue + + let text = (util.ciContentText(ci)?.trim() || "").replace(/\n+/g, " ") + const mediaLabel = contentTypeLabel(ci) + if (mediaLabel && !text) text = mediaLabel + else if (mediaLabel) text = `${mediaLabel} ${text}` + if (!text) continue + + let senderId = "" + let name = "" + if (ci.chatDir.type === "groupRcv") { + const member = ci.chatDir.groupMember + const contactId = member.memberContactId + senderId = member.memberId + if (this.config.grokContactId !== null && contactId === this.config.grokContactId) { + name = "Grok" + } else if (customerId && member.memberId === customerId) { + name = customerName + } else { + name = member.memberProfile.displayName + } + } + + entries.push({senderId, name, text: truncateMsg(text, maxPer)}) + } + + // Compute prefixed lines in chronological order (sender prefix on first msg of each run) + const lines: {line: string; senderId: string; name: string}[] = [] + let lastSenderId = "" + for (const entry of entries) { + let line = entry.text + if (entry.senderId !== lastSenderId && entry.name) { + line = `${entry.name}: ${line}` + lastSenderId = entry.senderId + } + lines.push({line, senderId: entry.senderId, name: entry.name}) + } + + // Take from the end (newest) until maxTotal exceeded — oldest messages are truncated + const selected: string[] = [] + let totalLen = 0 + let firstSelectedIdx = lines.length + for (let i = lines.length - 1; i >= 0; i--) { + if (totalLen + lines[i].line.length > maxTotal && selected.length > 0) { + break + } + selected.push(lines[i].line) + totalLen += lines[i].line.length + firstSelectedIdx = i + } + selected.reverse() + + // If truncation happened, ensure the first visible message has a sender prefix + if (firstSelectedIdx > 0 && selected.length > 0) { + const first = lines[firstSelectedIdx] + if (first.name && !selected[0].startsWith(`${first.name}: `)) { + selected[0] = `${first.name}: ${selected[0]}` + } + selected.unshift("[truncated]") + } + + const preview = selected.map(escapeStyledMarkdown).join(" !3 /! ") + return preview ? `"${preview}"` : '""' + } + + private formatDuration(ms: number): string { + if (ms < 60_000) return "<1m" + if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m` + if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h` + return `${Math.floor(ms / 86_400_000)}d` + } +} diff --git a/apps/simplex-support-bot/src/config.ts b/apps/simplex-support-bot/src/config.ts new file mode 100644 index 0000000000..8fbd006aef --- /dev/null +++ b/apps/simplex-support-bot/src/config.ts @@ -0,0 +1,144 @@ +import {Command} from "commander" +import {api} from "simplex-chat" + +export interface IdName { + id: number + name: string +} + +export type Backend = "sqlite" | "postgres" + +export interface Config { + stateFile: string // local path to the bot's state JSON + db: api.DbConfig // passed to ChatApi.init / bot.run + teamGroup: IdName // name from CLI, id resolved at startup from state file + teamMembers: IdName[] // optional, empty if not provided + grokContactId: number | null // resolved at startup + timezone: string + completeHours: number + cardFlushSeconds: number + contextFile: string | null + grokApiKey: string | null +} + +// Mirrors packages/simplex-chat-nodejs/src/download-libs.js so runtime detection +// matches what was used at install time. Works whether the user installed via +// SIMPLEX_BACKEND env var, .npmrc (→ npm_config_simplex_backend), or the +// --simplex_backend=postgres CLI flag (also surfaced as npm_config_*). +export function detectBackend(): Backend { + const raw = (process.env.SIMPLEX_BACKEND || process.env.npm_config_simplex_backend || "sqlite").toLowerCase() + if (raw !== "sqlite" && raw !== "postgres") { + throw new Error(`Invalid SIMPLEX_BACKEND: "${raw}". Must be "sqlite" or "postgres".`) + } + return raw +} + +export function parseIdName(s: string): IdName { + const i = s.indexOf(":") + if (i < 1) throw new Error(`Invalid ID:name format: "${s}"`) + const id = parseInt(s.slice(0, i), 10) + if (isNaN(id)) throw new Error(`Invalid ID:name format (non-numeric ID): "${s}"`) + return {id, name: s.slice(i + 1)} +} + +function parseNonNegativeInt(flag: string) { + return (raw: string): number => { + const n = parseInt(raw, 10) + if (!Number.isFinite(n) || n < 0) { + throw new Error(`${flag} must be a non-negative integer, got "${raw}"`) + } + return n + } +} + +function buildCommand(): Command { + return new Command() + .name("simplex-chat-support-bot") + .description("business-address triage bot") + .requiredOption("--team-group ", "team group display name") + .option("--state-file ", "state JSON path", "./data/state.json") + .option("--sqlite-file-prefix ", "SQLite DB file prefix", "./data/simplex") + .option("--sqlite-key ", "SQLCipher encryption key (default: unencrypted)") + .option("--pg-conn ", "PostgreSQL connection string (required for postgres)") + .option("--pg-schema ", "PostgreSQL schema prefix (default: simplex_v1)") + .option("-a, --auto-add-team-members ", "comma-separated ID:name pairs (e.g. 1:Alice,2:Bob)") + .option("--timezone ", "IANA timezone for weekend detection", "UTC") + .option("--complete-hours ", "auto-complete chats after N hours idle (0 disables)", parseNonNegativeInt("--complete-hours"), 3) + .option("--card-flush-seconds ", "debounce card state writes", parseNonNegativeInt("--card-flush-seconds"), 300) + .option("--context-file ", "text file with Grok system context (required if GROK_API_KEY set)") + .addHelpText("after", "\nEnvironment:\n GROK_API_KEY xAI API key — enables Grok replies\n SIMPLEX_BACKEND sqlite | postgres — alternative to .npmrc for backend selection\n") +} + +interface RawOpts { + teamGroup: string + stateFile: string + sqliteFilePrefix: string + sqliteKey?: string + pgConn?: string + pgSchema?: string + autoAddTeamMembers?: string + timezone: string + completeHours: number + cardFlushSeconds: number + contextFile?: string +} + +export function parseConfig(args: string[]): Config { + const cmd = buildCommand().exitOverride() + try { + cmd.parse(args, {from: "user"}) + } catch (err) { + const code = (err as {code?: string}).code + if (code === "commander.helpDisplayed" || code === "commander.version") process.exit(0) + throw err + } + const opts = cmd.opts() + + const grokApiKey = process.env.GROK_API_KEY || null + + const backend = detectBackend() + let db: api.DbConfig + if (backend === "sqlite") { + db = opts.sqliteKey + ? {type: "sqlite", filePrefix: opts.sqliteFilePrefix, encryptionKey: opts.sqliteKey} + : {type: "sqlite", filePrefix: opts.sqliteFilePrefix} + } else { + if (!opts.pgConn) { + throw new Error("--pg-conn is required when backend is postgres (PostgreSQL connection string)") + } + db = opts.pgSchema + ? {type: "postgres", connectionString: opts.pgConn, schemaPrefix: opts.pgSchema} + : {type: "postgres", connectionString: opts.pgConn} + } + + const teamGroup: IdName = {id: 0, name: opts.teamGroup} + + const teamMembersRaw = opts.autoAddTeamMembers ?? "" + const teamMembers = teamMembersRaw + ? teamMembersRaw.split(",").map(parseIdName) + : [] + + try { + new Intl.DateTimeFormat("en-US", {timeZone: opts.timezone, weekday: "short"}) + } catch (err) { + throw new Error(`--timezone "${opts.timezone}" is not a valid IANA time zone: ${(err as Error).message}`) + } + + const contextFile = opts.contextFile ?? null + if (grokApiKey && !contextFile) { + throw new Error("GROK_API_KEY is set but --context-file is not provided. Grok requires a context file.") + } + + return { + stateFile: opts.stateFile, + db, + teamGroup, + teamMembers, + grokContactId: null, + timezone: opts.timezone, + completeHours: opts.completeHours, + cardFlushSeconds: opts.cardFlushSeconds, + contextFile, + grokApiKey, + } +} diff --git a/apps/simplex-support-bot/src/grok.ts b/apps/simplex-support-bot/src/grok.ts new file mode 100644 index 0000000000..b03108439a --- /dev/null +++ b/apps/simplex-support-bot/src/grok.ts @@ -0,0 +1,55 @@ +import {log, logError} from "./util.js" + +export interface GrokMessage { + role: "system" | "user" | "assistant" + content: string +} + +export class GrokApiClient { + private readonly apiKey: string + private readonly systemPrompt: string + + constructor(apiKey: string, systemPrompt: string) { + this.apiKey = apiKey + this.systemPrompt = systemPrompt + } + + async chatRaw(messages: GrokMessage[]): Promise { + const response = await fetch("https://api.x.ai/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${this.apiKey}`, + }, + body: JSON.stringify({ + model: "grok-3-mini", + messages, + temperature: 0.3, + max_tokens: 1024, + }), + signal: AbortSignal.timeout(60_000), + }) + + if (!response.ok) { + const body = await response.text() + logError(`Grok API HTTP ${response.status}`, body) + throw new Error(`Grok API error: HTTP ${response.status}`) + } + + const data = await response.json() as {choices: {message: {content: string}}[]} + const content = data.choices?.[0]?.message?.content + if (!content) throw new Error("Grok API returned empty response") + + log(`Grok API response: ${content.length} chars`) + return content + } + + async chat(history: GrokMessage[], userMessage: string): Promise { + log(`Grok API call: ${history.length} history msgs, user msg ${userMessage.length} chars`) + return this.chatRaw([ + {role: "system", content: this.systemPrompt}, + ...history, + {role: "user", content: userMessage}, + ]) + } +} diff --git a/apps/simplex-support-bot/src/index.ts b/apps/simplex-support-bot/src/index.ts new file mode 100644 index 0000000000..0d0c667750 --- /dev/null +++ b/apps/simplex-support-bot/src/index.ts @@ -0,0 +1,372 @@ +import {readFileSync, writeFileSync, existsSync} from "fs" +import {api, bot, util} from "simplex-chat" +import {T} from "@simplex-chat/types" +import {parseConfig} from "./config.js" +import {SupportBot} from "./bot.js" +import {GrokApiClient} from "./grok.js" +import {welcomeMessage} from "./messages.js" +import {profileMutex, log, logError} from "./util.js" + +interface BotState { + teamGroupId?: number + grokContactId?: number + grokUserId?: number +} + +function readState(path: string): BotState { + if (!existsSync(path)) return {} + try { return JSON.parse(readFileSync(path, "utf-8")) } catch { return {} } +} + +function writeState(path: string, state: BotState): void { + writeFileSync(path, JSON.stringify(state), "utf-8") +} + +async function main(): Promise { + const config = parseConfig(process.argv.slice(2)) + // Do not log config.db.connectionString — typically contains credentials. + log("Config parsed", { + stateFile: config.stateFile, + backend: config.db.type, + teamGroup: config.teamGroup, + teamMembers: config.teamMembers, + timezone: config.timezone, + completeHours: config.completeHours, + }) + const grokEnabled = config.grokApiKey !== null + if (!grokEnabled) log("No GROK_API_KEY provided, disabling Grok support") + + const stateFilePath = config.stateFile + const state = readState(stateFilePath) + + // Forward-reference for event handlers during init + let supportBot: SupportBot | undefined + + // On restart, the active user may be Grok (if the previous run was killed + // mid-profile-switch). bot.run() uses apiGetActiveUser() and would then + // operate against the Grok userId as if it were the main user. Restore + // the main user as active before bot.run(). Grok is identified by the + // userId persisted in state.json on first resolution — comparing by + // profile name is fragile to renames. + if (state.grokUserId !== undefined) { + const preChat = await api.ChatApi.init(config.db) + try { + const activeUser = await preChat.apiGetActiveUser() + if (activeUser && activeUser.userId === state.grokUserId) { + const users = await preChat.apiListUsers() + const mainCandidates = users.filter(u => u.user.userId !== state.grokUserId) + if (mainCandidates.length === 0) { + throw new Error( + `DB has only the Grok user (userId=${state.grokUserId}); no main user to restore. ` + + `Likely a corrupted migration or partial restore.` + ) + } + if (mainCandidates.length > 1) { + const names = mainCandidates.map(u => `${u.user.userId}:${u.user.profile.displayName}`).join(", ") + throw new Error( + `Ambiguous DB state: multiple non-Grok users [${names}]. ` + + `Refusing to guess which is main — remove extras manually.` + ) + } + const mainUserInfo = mainCandidates[0] + await preChat.apiSetActiveUser(mainUserInfo.user.userId) + log(`Restored active user to ${mainUserInfo.user.profile.displayName} (userId=${mainUserInfo.user.userId})`) + } + } finally { + await preChat.close() + } + } + + // Profile images (base64-encoded JPEG) + const supportImage = "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCACAAIADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD6pooooAKKKKACiignAyelABRQCGAIIIPIIooAKKKKACikjdZEDxsGU8gqcg0tAk01dBRRRQMKKKKACiiigAooooAK898ZeKftBew058Qj5ZZR/H7D29+9ehVxHjTwt5++/wBMT9996WFR9/8A2h7+3f69e/LnRVZe1+Xa587xNTxtTBNYP/t627Xl+vVr8c/wf4oNkyWWoPm1PCSH/ln7H/Z/lXo6kMAVIIPIIrwTdiuw8GeKjYsljqDk2h4SQ/8ALP2P+z/KvSzDLua9WkteqPmOGeJHQtg8Y/d+zLt5Py7Pp6bel1wXjHxRv32GmyfJ92WZT97/AGV9vU1H4z8ViTfYaZJ+7+7LMp+9/sqfT1NcOGqMvy61qtVeiNeJuJea+Dwb02lJfkv1Z1PhTxI+lSiC5JeyY8jqYz6j29RXp6MHRWU5VhkGuG8F+F8eXqGpx8/eihYdP9ph/IV3VcWZTpSq/u9+p7fCdDG0cHbFP3X8Ke6X+XZdAooorzj6kKKKKACiikYhVJYgAckmgBTxRXzJ8dPi6dUNx4d8LXGNPGY7u8jP+v8AVEP9z1P8XQcddL4E/F7/AI9/Dfiu49I7K+kbr2Ech/QN+B7Gu95dWVH2tvl1scqxdN1OQ+iaKKK4DqOG8b+FPPEmoaYn7770sKj7/wDtD39u/wBevnAas346/F77X9o8N+FLj/R+Y7y+jb/WdjHGf7vYt36DjJPnvgPxibXy9M1aT/R+FhnY/wCr9FY/3fQ9vp0+ty32qpJVvl3sfnPEmS051HiMItftJfmv1PVN1eheCPCvEeo6mmScNDC36M39BXm+6u18EeLTYMljqTk2h4jkP/LL2P8As/yrTMIVnRfsfn3t5Hh8PPB08ZF4xadOyfn/AF6nqNFIrBlDKQQeQR3pa+OP2IKRHV1DIwZT0IORXn/jjxdt8zTtLk+b7s0ynp6qp/maxPB3il9HmFvdFnsHPI6mM+o9vUV6cMqrTo+169F5HzNfinCUcYsM9Y7OXRP/AC7voeuUU2KRZY0kjIZGAZSO4NOrzD6VO+qCkZQylWAKkYIPelooGfMHxz+EZ0Zp/EPheAnTDl7q0jH/AB7eroP7nqP4fp08Lr9EmUMpVgCDwQa+Yfjn8Im0dp/EPhe3LaaSXurOMZNue7oP7nqP4fp09/L8w5rUqr16M8vF4S3vwNb4FfF7/j38N+K7jniOyvpG69hHIT+QY/Q9jVb47fF03RufDfhS4xbjMd7exn/WdjHGf7vYt36DjJPz/RXZ/Z9H23tbfLpfuc/1up7PkE6D0FfRnwK+EOw2/iTxXb/PxJZ2Mi/d7iSQevcL26nnAB8C/hD5Zt/Efiy3xJxJZ2Mq/d7iSQHv3C9up5wB9D1wZhmG9Kk/VnVhMJ9uZwPjvwj9o8zUtKj/AH33poVH3/8AaX39R3+vXzLdX0XXn3j3wd9o8zUtJj/f/emgUff/ANpR6+o7/XrpleZ2tRrPTo/0Z8xxFw5z3xeEWvVd/NfqjL8DeLzp7JYam5NmTiOQ/wDLL2P+z/KtDx14xAD6dpEuT0mnQ9P9lT/M15nu5pd1etLLKMq3tmvl0v3Pm4Z9jIYP6mpad+qXYn3V6D4E8ImXy9S1WP8Ad/ehgYfe9GYenoKj8A+EPOEWp6tH+74aCBh970Zh6eg716ZXl5nmVr0aL9X+iPe4d4cvbF4tecY/q/0QUUUV86ffhRRRQAV82/HX4vfa/tHhvwpcf6NzHeX0bf6zsY4z/d7Fu/QcZJPjr8XvtRuPDfhS4/0fmO8vo2/1nYxxkfw9i3foOMk/P/8AKvdy/L7Wq1V6I8zF4v7EBOn0pa+i/gX8INot/Efiy2+fiSzsZV+76SSA9/RT06nnAGP8dPhGdHa48Q+F4CdMJL3Vogybc93Qf3PUfw/Tp3rH0XV9lf59L9jleFqKn7Q1vgV8Xjm38N+LLnJ4js76VuvYRyE/kGP0PY19E1+dlfRXwJ+L3Nv4b8V3HPEdlfSN17COQn8g34Hsa8/MMv3q0l6o68Ji/sTPomvNfiB412mTS9Hl+blZ7hT09VU+vqaj+InjfYZdK0eX5uVnuFPT1VT6+p/CvMN1dOVZTe1euvRfqz5riDP98LhX6v8ARfqybdS7q9E+HngszeVqmsRfu+Ggt2H3vRmHp6DvVz4heC/tAk1PR4v3/wB6aBR9/wD2lHr6jv8AXr6TzTDqv7C/z6X7Hgx4dxcsJ9aS/wC3etu//AMrwD4zOnMmn6pITZE4jlY5MXsf9n+X0r1pWDKGUgqRkEd6+Zd2K7z4f+NDprR6dqrk2JOI5T/yx9j/ALP8vpXFmuU8961Ba9V3815/mevw/n7o2wuKfu9H28n5fl6bev0UisGUMpBUjII70tfKn3wVHdQRXVtLb3CCSGVCjoejKRgg/hUlFAHx98Z/hbceCrttQ0tXm8PTNhWPLWrHojn09G/A89e7+BXwh8v7P4k8V2/z8SWdjIv3e4kkB79wvbqecAfQc0Mc8TRzRpJG3VXUEH8DT69GeZVZ0vZ9e5yRwcI1Of8AAKRlDKVYAg8EGlorzjrPmD45/CM6O0/iHwvATphJe6tIx/x7+roP7nqP4fp04Hwh4aB2X+pR8feihYdf9ph/IV9EfErx2B52kaLKCeUuLhT09UU/zP4V5Tur7jKaFaVFTxHy728z4LPcxgpujhX6v9F+pPur074c+CDN5Wq6zF+64aC3cfe9GYenoO9eV7q9d+G/joXXlaVrUv8ApHCwXDH/AFnorH+96Hv9eumb/WI4duh8+9vI87IaeFeKX1n5dr+f6HptFFFfBn6ceb/ETwT9pEuqaNH/AKR96eBR/rPVlH971Hf69c34d+CTdmPU9ZiIth80MDj/AFn+0w/u+g7/AE6+tUV6kc2rxw/sE/n1t2PEnkGEnivrTXy6X7/8AAAAABgCiiivLPbCiiigAooooAK8n+Jnj7YZdI0OX5uUuLlD09UU+vqfwFerSossbxuMowKkeoNeBfETwTL4cuDd2QaTSpG4PUwk/wALe3ofwPPX2sjpYepiLVnr0XRv+uh4Wf1cTTw37hadX1S/rdnG7q9U+GngPzxFq2uRfueGt7Zx9/0dh6eg79TTPhj4B87ytY1yL91w9vbOPv8Ao7D09B36mvYK9POc4tfD4d+r/RHlZJkV7YnEr0X6v/I8U+JPgZtKaTVNIjLaeTuliXkwH1H+z/L6V52GxX1c6q6lWAKkYIIyDXiXxL8CNpLSapo8ZbTyd0sK9YPcf7P8vpV5PnHtLYfEPXo+/k/P8/XfLO8i9nfE4ZadV2815fl6bb/w18eC68rSdbl/0j7sFw5/1norH+96Hv8AXr6fXjXwy8Bm9MWr61ERajDQW7D/AFvozD+76Dv9OvsteLnMcPHENYf59r+R72RyxMsMnifl3t5/oFFFFeSeyFFFFABRRRQAUUUUAFMmijmjaOZFkjYYZXGQR7in0UJ2Bq+4UUUUAFIyh1KsAVIwQRwaWigAAAAAGAKKKKACiiigAooooA//2Q==" + const grokImage = "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4gKgSUNDX1BST0ZJTEUAAQEAAAKQbGNtcwQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwQVBQTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWxjbXMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAAADhjcHJ0AAABQAAAAE53dHB0AAABkAAAABRjaGFkAAABpAAAACxyWFlaAAAB0AAAABRiWFlaAAAB5AAAABRnWFlaAAAB+AAAABRyVFJDAAACDAAAACBnVFJDAAACLAAAACBiVFJDAAACTAAAACBjaHJtAAACbAAAACRtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABwAAAAcAHMAUgBHAEIAIABiAHUAaQBsAHQALQBpAG4AAG1sdWMAAAAAAAAAAQAAAAxlblVTAAAAMgAAABwATgBvACAAYwBvAHAAeQByAGkAZwBoAHQALAAgAHUAcwBlACAAZgByAGUAZQBsAHkAAAAAWFlaIAAAAAAAAPbWAAEAAAAA0y1zZjMyAAAAAAABDEoAAAXj///zKgAAB5sAAP2H///7ov///aMAAAPYAADAlFhZWiAAAAAAAABvlAAAOO4AAAOQWFlaIAAAAAAAACSdAAAPgwAAtr5YWVogAAAAAAAAYqUAALeQAAAY3nBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbcGFyYQAAAAAAAwAAAAJmZgAA8qcAAA1ZAAAT0AAACltwYXJhAAAAAAADAAAAAmZmAADypwAADVkAABPQAAAKW2Nocm0AAAAAAAMAAAAAo9cAAFR7AABMzQAAmZoAACZmAAAPXP/bAEMABQMEBAQDBQQEBAUFBQYHDAgHBwcHDwsLCQwRDxISEQ8RERMWHBcTFBoVEREYIRgaHR0fHx8TFyIkIh4kHB4fHv/bAEMBBQUFBwYHDggIDh4UERQeHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHv/AABEIAIAAgAMBIgACEQEDEQH/xAAdAAAABgMBAAAAAAAAAAAAAAAAAQQHCAkCAwUG/8QAOxAAAQIEBAQDBgUCBgMAAAAAAQIDAAQFEQYHITEIEkFRE2FxFCJCgZGhFTJSgrEJIzNDYnKS4VOy8P/EABYBAQEBAAAAAAAAAAAAAAAAAAABAv/EABsRAQEBAAMBAQAAAAAAAAAAAAABEQISIRMx/9oADAMBAAIRAxEAPwCINoAv3gCD3BjTIhpprA3gdTB2F9NoAE9oGm194IWMAQB/WB3gD7QNIAbwBtvA2PnBjtAF08oHe/ygDUwfTf6QA6wRsNYMX84NKSYDEHXYQY1jc5Kvty7Uw42pDTpUG1H4+U2JHcA6X76RpSn7bwBQB1gesADXvAD0gAaX0gDY2gWgCEGBcHSM2m1LNrR7LLrLvE+NqsKbhukTE++LFwpHK20D8S1n3Uj1OvS8B45DK1dI3JknSNEk/KJoZfcH0gyy3MY1xA666RdUpTEhKU+RdWCT8kiHbpHD3lHTmUtpwhLTShu5NvOPKPrdVvtE1cVrGReA/IfpGlcstF7pMWeTOReUswjkXgWkpHdtKmz9UkGPBYx4Tcv6o04uhTdSoUwfyAOe0Mj9q/e+ihDTFfikLSSCLQBa58+gh684uH7G+X8u7UH5NFVpCNTPyIKktju4gjmR66p84Z1EupblgmKjUy2pZ01hy8s8u2qjQ6hjjFSn5HBtII9odQeV2fe+GVl7/Go2BVskEnfb1vDVkXPZj1NNRqQeksMyrlpmYAsqYUN2mj3/AFK+H1hfxfYzkZrEUtl7hppqTwzhYezty7As2qZtZZ035fyi/XnPWIpjsW1h+t1l6oOsMSyFAIYlWE2almU6IabHRKRp3JuTckmOMBvGx1dyTbaMQSTfYHvFRqG0DWDtpA9IAvWM2Wys2jG149blphSoYuxbTcPUxF5uffS0gkXCBupZ8kpBUfIQHvuHHJepZmVwqWpySoUmoe2zoTrffwm76FZHySNT0BsBwVhSg4PoLFEw9TmZGTZGiUD3lq6qWrdSj1J1jXl9hSk4KwlIYcozIblZRvl5iPedWdVOK7qUbk/9Q2PExnhL5cyP4LRCzM4lmW+YBfvIk0G9nFjqo/Cn5nTfLRyscY6wngqRE3iauSlOSr/DQtV3HP8AagXUr5CGSxDxd4QlHy3RsPVapJH+Y84iXSfQe8r6gRC7FmKarXqs/U6tPzE9Ovqu4++sqWryv0HkNB0jgLmnCd4uJqbklxi0lbwTN4Km22ydVM1BCyB6FI/mPZJ4psrjQXKh49VTNIICZBUmfGWSOir8lvMqiu9Mw4NeYwol3XlmwJ1hhqRuanE7jTFCXqfh0Iw3TnAUnwD4kytJ6KcIsn9oHqY5PDdkfPZjVb8TqSXZTDUs5aZmBoqYUN2mj3/Ur4fWC4ackqjmNVBUakHpTDMq5aYmALKmFDdpo9/1K+H1ifFEpdPotJlqVSpRmTkpVsNsMtJslCR0H/2u8BwcUTNMy+yvqU1S5RiSkqLTXFyzDSLITyJPIkDzVb1vFXdemnpibdefcLjziytxZ3Uom5J9STFknFEVjIPFhRv7IkfLxUX+0Vp1TV5R13hAh6awBB2Fu5gCxiow+UEN9TB9ILQ9TAZsJ5nABEwv6fuEW3alXMYzDXMZRCZGVKhstfvOEefKED9xiIMgLvA+cWJ8E1PTJZEST4A5p2dmX1H0X4Y+yBEWHQzAxJKYQwVVsSzo5mafLKd5P1q2Sj9yiB84q+x7iSpYhxDP1mqTBfnZ19Tzyyd1HoOwAsAOgAibXHfWVyOVUhS21lJqNSQF2P5kNoUu3/Ll+kQEnlFTh1trCBOpRUreCFzA0vGxlsrVFRlLsqcVoIffhmyOn8xqt+IVAOyeGZRwCZmQLKfUP8lo9+6tkjzsI0cNGSU9mPVfxGoeJI4YknLTc2PdU8oalponrb8ytkjzsIezOLiFw7gajpwRlSxJKXJN+zicaSFS0oBpytDZ1d/iPu31PMbxFOxmJmPgXJvDMrSkNMpeaZCJCjydgvkGxP6E33Urc33MYZC5wyGaDFRaFONMqEhyKWx43ihbargLSbA6EWII007xXRX8QVGr1R+oVCcfnJyYWVvPvLKlrV3JO8PfwRVp6TzrkZXnIbqMpMSyx3sjxB92/vATMzmpBruVGKKUnVb9Lf8ADFt1hBUn7gRVxVB/cJI31EW5OJSttSFpCkqFiD1BiqPH0imn4lqkgkWTLTjzIHklxSR/EIPNA2Gt4I6bWN/tAG+kFtFQUA2gDaAOusAokD/eGoixrgym0TOQdJbQdZaYmWVeR8ZSv4UIrhl1crmneJt/0/sToeolfwk64A4y8ifl090qAQ59ClH/ACiLHT4/pB17L+gVBAJblqkptZ7c7Srf+kQSmxZ0xaTnrg046yurGH2kpM4toPSZV0fbPMj625f3RWPXZF6Vm3WnmlNOtqKVoULFKgbEEdCDpCDlNpufKHSyay6k68y9inGFSFBwTTnOWcn1aLmXN/ZpcbrcPWwPKPOwhvaEKaidS9Vg+5Kt+8phhXKt/sgK1CAeqrEgbAm0dfF+MKriVyWTOKal5GRb8Gn0+VSUSsk3+ltF9L7lRupR1USYqHSzgz2ma7R0YMwRJHDGC5VsMMybJ5XphA/8pB0B35AdSTzFRhjn5lTht0jUtalExjy7xFBIJVoYf3gqkXZvPOjLSklEozMPuEdAGlJH3WIYiTaUt0C0TZ4CsDOyNKqmN5xko9sAkpEkfmbSq7ix5FQSn9pgRKTpFVGZs0icxlW5ps3S/UZhxNuxdUR/MWW5vYkawllnX6+4vlXKyS/B11Lqhytj5qUmKtas4Vum6iT1N94Qc/vbeBt5QQMH6jSKgbwAL6wYFxeM0pBMASLhUPtwc1GYkM8aCGlK5ZrxZZ1I+JCmlHX5pSflDIMNErAF4k7wMYOfqWYLuKHGiJKisKCVkaKmHU8qU/JJUfp3gqb3w6xBHjepuCmMw/aMPTgNbfuqsSrSQWm19FlXRxXxJ9CbE6uxxH8QjVITM4VwLNpcnxducqbZBTL9Cho7FfdWyelztFGh0Ku4vrPsVIp07VZ51XMpDKC4o3OqlHprupR+cJDXiCyq5sD6wXgq1uDEucAcI9TnZMzOMK0ikrWj+3KyaA84k9OdR93Tsm/qIS17hExQw6s0au0ifav7vjhbC/mLKH3i+CKCWVfpjexKLWqwB17RJaQ4T8fuupS/MUKWRfVaptavsEQ6OX3ClhulvtzeLKq7WlpIPsrCCwxfso3K1D5pieCPPD3kvWMw662stuytCl3B7bPFNhbq23+pZ+idz0BsHodLkKJRpSk0yWblZKTZSyw0gWCEJFgP++sZ0qnSFJpzMhTZRiTlGEcjTLKAhCE9gBoIZ7iPzukMB0t+iUN9qaxM8jlABCkyQI/Ov/V+lHzOm8/Q1HHPmS1NzjGAKZMBbMksTFSUg3BeseRr9oJUfMjtEQ5lXOsnXeOxXJ5+em3pmYeceedWVuOOKupaibkk9STrHHKd4uDRbvpBRmU77aQQGveCM0J1jey2VaRggXjuYWos3Wqo1T5INBxV1LceXyNMtpF1OOKOiUJGpJ+5sDYOvlpgyr4zxLL0SjtJLzl1uvOaNS7Q/M64rolI+uw1MPLmDmnS8K4MRlflXNOIpTAUmpVpPuu1B0/4hQRsknQq6iwHui5b+v4skKVhp3BWCFuJpT1jVKmpBQ/WHB3G7cuPhb3O6tTYeHJKusWQ0TzqnDoIczJHN3E2W77jNMUxN0uYc55iQmE+4s7cyVD3kqt11HcGG0Si+to2oBTtGsY1PfA3EVl/X2EIqU07QJsgczc6Lt38nU6W9eWHRpldotTbDlNq8hOIOymJlDgP0MVgMzDiNlfO8KWZ9aNUmx7jQxn5tTktAfmpZhBW/MMtJHVawkfePG4rzay+w02v8QxPIuPJF/AlV+O6fLlRe3ztFerlTfcFlrUr/cSf5hM5NrUCL2HlDod0jc2OJ2q1Jl6m4LlnKRLKukzrpBmVD/SBdLfrqfSIzVWcenHnHnnVuuOKKlrWoqUpROpJOpJ7mMnFKXurbaNC081o1OOJ2c5xF7nWE6kWBjprb3hM63oTa0TFlc1aI1KBF4WuotftCVabEiM2K2N6bWjqSc0+1KuyrbikNPlJdSk25+U3APcA622vr0Ecxu28KmCBbpFgXNnmNjG9sX1hKyrvClCtI3GK3oAAt1gwBGKVXg0nS14qMgB1+0HbcQQMAGwMAYAtqNYHKTrGN/rBBWkAZA20jEp3tB30OojFStb94DW4nS94SuiFKyLbwldVeIsJHQLGEi0+kKnlXuN4SOG+/wBoxW4//9k=" + + const desiredCommands: T.ChatBotCommand[] = [ + ...(grokEnabled ? [{type: "command" as const, keyword: "grok", label: "Ask Grok"}] : []), + {type: "command", keyword: "team", label: "Switch to team"}, + ] + + // Step 1: Init main bot via bot.run() + log("Initializing main bot...") + const [chat, mainUser, mainAddress] = await bot.run({ + profile: {displayName: "Ask SimpleX Team", fullName: "", image: supportImage}, + dbOpts: config.db, + options: { + addressSettings: { + businessAddress: true, + autoAccept: true, + welcomeMessage, + }, + commands: desiredCommands, + useBotProfile: true, + updateProfile: false, + }, + events: { + acceptingBusinessRequest: (evt) => supportBot?.onBusinessRequest(evt), + newChatItems: (evt) => supportBot?.onNewChatItems(evt), + chatItemUpdated: (evt) => supportBot?.onChatItemUpdated(evt), + chatItemReaction: (evt) => supportBot?.onChatItemReaction(evt), + leftMember: (evt) => supportBot?.onLeftMember(evt), + joinedGroupMember: (evt) => supportBot?.onJoinedGroupMember(evt), + connectedToGroupMember: (evt) => supportBot?.onMemberConnected(evt), + newMemberContactReceivedInv: (evt) => supportBot?.onMemberContactReceivedInv(evt), + contactConnected: (evt) => supportBot?.onContactConnected(evt), + contactSndReady: (evt) => supportBot?.onContactSndReady(evt), + }, + }) + log(`Main bot user: ${mainUser.profile.displayName} (userId=${mainUser.userId})`) + + // Step 2: Resolve Grok profile from same ChatApi instance. + // Identify Grok strictly by the persisted userId in state.json. If no ID + // is persisted, this is a first-time run — create the user and persist. + let grokUser: T.User | null = null + if (grokEnabled) { + log("Resolving Grok profile...") + if (state.grokUserId !== undefined) { + const users = await chat.apiListUsers() + grokUser = users.find(u => u.user.userId === state.grokUserId)?.user ?? null + if (!grokUser) { + throw new Error( + `Persisted Grok userId=${state.grokUserId} not found in DB. ` + + `Either restore the user or delete state.json to re-create Grok.` + ) + } + } else { + log("Creating Grok profile...") + grokUser = await chat.apiCreateActiveUser({displayName: "Grok", fullName: "", image: grokImage}) + // apiCreateActiveUser sets Grok as active — switch back to main + await chat.apiSetActiveUser(mainUser.userId) + state.grokUserId = grokUser.userId + writeState(stateFilePath, state) + log(`Persisted Grok userId=${grokUser.userId}`) + } + + // Refresh Grok's profile if it has drifted from the canonical values. + const grokProfile: T.Profile = {displayName: "Grok", fullName: "", image: grokImage} + const currentProfile = util.fromLocalProfile(grokUser.profile) + if (currentProfile.image !== grokProfile.image || currentProfile.displayName !== grokProfile.displayName || currentProfile.fullName !== grokProfile.fullName) { + log("Grok profile changed, updating...") + await chat.apiSetActiveUser(grokUser.userId) + const summary = await chat.apiUpdateProfile(grokUser.userId, grokProfile) + await chat.apiSetActiveUser(mainUser.userId) + if (summary) { + log(`Grok profile updated: ${summary.updateSuccesses} contact(s) updated, ${summary.updateFailures} failed`) + } else { + log("Unexpected: Grok profile did not change") + } + } + log(`Grok profile: ${grokUser.profile.displayName} (userId=${grokUser.userId})`) + } + + // Step 3: Read state file + // Step 4: Enable auto-accept DM contacts + await chat.apiSetAutoAcceptMemberContacts(mainUser.userId, true) + log("Auto-accept member contacts enabled") + + // Step 5: List contacts, resolve Grok contact + const contacts = await chat.apiListContacts(mainUser.userId) + log(`Contacts: ${contacts.map(c => `${c.contactId}:${c.profile.displayName}`).join(", ") || "(none)"}`) + + // Always restore grokContactId so the one-way gate can find and remove + // Grok members even when Grok API is disabled. + if (typeof state.grokContactId === "number") { + const found = contacts.find(c => c.contactId === state.grokContactId) + if (found) { + config.grokContactId = found.contactId + log(`Grok contact from state: ID=${config.grokContactId}`) + } else { + log(`Persisted Grok contact ID=${state.grokContactId} not found`) + } + } + + if (grokEnabled) { + if (config.grokContactId === null) { + log("Establishing bot↔Grok contact...") + const invLink = await chat.apiCreateLink(mainUser.userId) + // Switch to Grok profile to connect + await profileMutex.runExclusive(async () => { + await chat.apiSetActiveUser(grokUser!.userId) + await chat.apiConnectActiveUser(invLink) + await chat.apiSetActiveUser(mainUser.userId) + }) + log("Grok connecting...") + + const grokProfileName = grokUser!.profile.displayName + const evt = await chat.wait( + "contactConnected", + (e) => + e.user.userId === mainUser.userId + && e.contact.profile.displayName === grokProfileName, + 60_000, + ) + if (!evt) { + console.error(`Timeout waiting for Grok contact (60s, displayName="${grokProfileName}"). Exiting.`) + process.exit(1) + } + config.grokContactId = evt.contact.contactId + state.grokContactId = config.grokContactId + writeState(stateFilePath, state) + log(`Grok contact established: ID=${config.grokContactId}`) + } + } + + // Step 6: Resolve team group + log("Resolving team group...") + const groups = await chat.apiListGroups(mainUser.userId) + + let existingGroup: T.GroupInfo | undefined + + if (typeof state.teamGroupId === "number") { + existingGroup = groups.find(g => g.groupId === state.teamGroupId) + if (existingGroup) { + config.teamGroup.id = existingGroup.groupId + log(`Team group from state: ${config.teamGroup.id}:${existingGroup.groupProfile.displayName}`) + } else { + log(`Persisted team group ID=${state.teamGroupId} not found, will create`) + } + } + + const teamGroupPreferences: T.GroupPreferences = { + directMessages: {enable: T.GroupFeatureEnabled.On}, + fullDelete: {enable: T.GroupFeatureEnabled.On}, + commands: [ + {type: "command", keyword: "join", label: "Join customer chat", params: "groupId"}, + ], + } + + if (config.teamGroup.id === 0) { + log(`Creating team group "${config.teamGroup.name}"...`) + const newGroup = await chat.apiNewGroup(mainUser.userId, { + displayName: config.teamGroup.name, + fullName: "", + groupPreferences: teamGroupPreferences, + }) + config.teamGroup.id = newGroup.groupId + state.teamGroupId = config.teamGroup.id + writeState(stateFilePath, state) + log(`Team group created: ${config.teamGroup.id}:${config.teamGroup.name}`) + } else if (existingGroup) { + // Only update profile if preferences or name changed + const prefs = existingGroup.fullGroupPreferences + const needsUpdate = + existingGroup.groupProfile.displayName !== config.teamGroup.name || + prefs.directMessages?.enable !== T.GroupFeatureEnabled.On || + prefs.fullDelete?.enable !== T.GroupFeatureEnabled.On || + JSON.stringify(prefs.commands) !== JSON.stringify(teamGroupPreferences.commands) + if (needsUpdate) { + await chat.apiUpdateGroupProfile(config.teamGroup.id, { + displayName: config.teamGroup.name, + fullName: "", + groupPreferences: teamGroupPreferences, + }) + log("Team group profile updated") + } + } + + // Step 7: Ensure direct messages enabled (done via groupPreferences above) + + // Step 8: Create team group invite link (best-effort — bot works without it) + let inviteLinkCreated = false + try { + try { await chat.apiDeleteGroupLink(config.teamGroup.id) } catch {} + const teamGroupInviteLink = await chat.apiCreateGroupLink( + config.teamGroup.id, T.GroupMemberRole.Member + ) + inviteLinkCreated = true + log("Team group invite link created") + console.log(`\nTeam group invite link (expires in 10 min):\n${teamGroupInviteLink}\n`) + } catch (err) { + logError("Failed to create team group invite link (SMP relay may be unreachable). Bot will continue without it.", err) + } + + let inviteLinkDeleted = false + async function deleteInviteLink(): Promise { + if (inviteLinkDeleted) return + inviteLinkDeleted = true + try { + await profileMutex.runExclusive(async () => { + await chat.apiSetActiveUser(mainUser.userId) + await chat.apiDeleteGroupLink(config.teamGroup.id) + }) + log("Team group invite link deleted") + } catch (err) { + logError("Failed to delete invite link", err) + } + } + let inviteLinkTimer: ReturnType | undefined + if (inviteLinkCreated) { + inviteLinkTimer = setTimeout(async () => { + log("10 minutes elapsed, deleting invite link...") + await deleteInviteLink() + }, 10 * 60 * 1000) + inviteLinkTimer.unref() + } + + // Step 9: Validate team members + if (config.teamMembers.length > 0) { + log("Validating team members...") + for (const member of config.teamMembers) { + const contact = contacts.find(c => c.contactId === member.id) + if (!contact) { + console.error(`Team member not found: ID=${member.id}. Available: ${contacts.map(c => `${c.contactId}:${c.profile.displayName}`).join(", ") || "(none)"}`) + process.exit(1) + } + if (contact.profile.displayName !== member.name) { + console.error(`Team member name mismatch: expected "${member.name}", got "${contact.profile.displayName}" (ID=${member.id})`) + process.exit(1) + } + log(`Team member validated: ${member.id}:${member.name}`) + } + } + + // Load Grok context and build API client only if enabled + let grokApi: GrokApiClient | null = null + if (grokEnabled) { + let contextFile = "" + if (config.contextFile) { + try { + contextFile = readFileSync(config.contextFile, "utf-8") + log(`Loaded Grok context: ${contextFile.length} chars from ${config.contextFile}`) + } catch { + log(`Warning: context file not found: ${config.contextFile}`) + } + } + grokApi = new GrokApiClient(config.grokApiKey!, contextFile) + } + + // Create SupportBot + supportBot = new SupportBot(chat, grokApi, config, mainUser.userId, grokUser?.userId ?? null, desiredCommands) + + if (mainAddress) { + supportBot.businessAddress = util.contactAddressStr(mainAddress.connLinkContact) + log(`Business address: ${supportBot.businessAddress}`) + } + + // Step 10: Register Grok event handlers (filtered by profile in handler) + if (grokEnabled) { + chat.on("receivedGroupInvitation", (evt) => supportBot?.onGrokGroupInvitation(evt)) + chat.on("connectedToGroupMember", (evt) => supportBot?.onGrokMemberConnected(evt)) + chat.on("newChatItems", (evt) => supportBot?.onGrokNewChatItems(evt)) + } + + // Step 10b: Refresh stale cards from before restart + await supportBot.cards.refreshAllCards() + + log("SupportBot initialized. Bot running.") + + // Step 11: Graceful shutdown + async function shutdown(signal: string): Promise { + log(`Received ${signal}, shutting down...`) + clearTimeout(inviteLinkTimer) + supportBot?.cards.destroy() + await deleteInviteLink() + process.exit(0) + } + process.on("SIGINT", () => shutdown("SIGINT")) + process.on("SIGTERM", () => shutdown("SIGTERM")) +} + +main().catch(err => { + logError("Fatal error", err) + process.exit(1) +}) diff --git a/apps/simplex-support-bot/src/messages.ts b/apps/simplex-support-bot/src/messages.ts new file mode 100644 index 0000000000..9952ccf8ce --- /dev/null +++ b/apps/simplex-support-bot/src/messages.ts @@ -0,0 +1,43 @@ +import {isWeekend} from "./util.js" + +export const welcomeMessage = `Hello! This is a *SimpleX team* support bot - not an AI. +Please ask any question about SimpleX Chat.` + +export function queueMessage(timezone: string, grokEnabled: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `The team will reply to your message within ${hours} hours.` + if (!grokEnabled) return base + return `${base} + +If your question is about SimpleX, click /grok for an *instant Grok answer*. + +Send /team to switch back.` +} + +export const grokActivatedMessage = `*You are chatting with Grok* - use any language.` + +export function teamAddedMessage(timezone: string, grokPresent: boolean): string { + const hours = isWeekend(timezone) ? "48" : "24" + const base = `We will reply within ${hours} hours.` + if (!grokPresent) return base + return `${base} +Grok will be answering your questions until then.` +} + +export const teamAlreadyInvitedMessage = "A team member has already been invited to this conversation and will reply when available." + +export const teamLockedMessage = "You are now in team mode. A team member will reply to your message." + +export function noTeamMembersMessage(grokEnabled: boolean): string { + return grokEnabled + ? "No team members are available yet. Please try again later or click /grok." + : "No team members are available yet. Please try again later." +} + +export const grokInvitingMessage = "Inviting Grok, please wait..." + +export const grokUnavailableMessage = "Grok is temporarily unavailable. Please try again later or send /team for a human team member." + +export const grokErrorMessage = "Sorry, I couldn't process that. Please try again or send /team for a human team member." + +export const grokNoHistoryMessage = "I just joined but couldn't see your earlier messages. Could you repeat your question?" diff --git a/apps/simplex-support-bot/src/util.ts b/apps/simplex-support-bot/src/util.ts new file mode 100644 index 0000000000..288a48d673 --- /dev/null +++ b/apps/simplex-support-bot/src/util.ts @@ -0,0 +1,22 @@ +import {Mutex} from "async-mutex" + +export const profileMutex = new Mutex() + +export function isWeekend(timezone: string): boolean { + const day = new Intl.DateTimeFormat("en-US", {timeZone: timezone, weekday: "short"}).format(new Date()) + return day === "Sat" || day === "Sun" +} + +export function log(msg: string, ...args: unknown[]): void { + const ts = new Date().toISOString() + if (args.length > 0) { + console.log(`[${ts}] ${msg}`, ...args) + } else { + console.log(`[${ts}] ${msg}`) + } +} + +export function logError(msg: string, err: unknown): void { + const ts = new Date().toISOString() + console.error(`[${ts}] ERROR: ${msg}`, err) +} diff --git a/apps/simplex-support-bot/test/__mocks__/simplex-chat-types.js b/apps/simplex-support-bot/test/__mocks__/simplex-chat-types.js new file mode 100644 index 0000000000..29fc3d01a4 --- /dev/null +++ b/apps/simplex-support-bot/test/__mocks__/simplex-chat-types.js @@ -0,0 +1,12 @@ +// Mock for @simplex-chat/types — lightweight stubs + +const ChatType = {Direct: "direct", Group: "group", Local: "local"} +const GroupMemberRole = {Member: "member", Owner: "owner", Admin: "admin", Relay: "relay", Observer: "observer", Author: "author", Moderator: "moderator"} +const GroupMemberStatus = {Connected: "connected", Complete: "complete", Announced: "announced", Left: "left", Removed: "removed", Invited: "invited"} +const GroupFeatureEnabled = {On: "on", Off: "off"} +const CIDeleteMode = {Broadcast: "broadcast", Internal: "internal"} + +module.exports = { + T: {ChatType, GroupMemberRole, GroupMemberStatus, GroupFeatureEnabled, CIDeleteMode}, + CEvt: {}, +} diff --git a/apps/simplex-support-bot/test/__mocks__/simplex-chat.js b/apps/simplex-support-bot/test/__mocks__/simplex-chat.js new file mode 100644 index 0000000000..64c9246f27 --- /dev/null +++ b/apps/simplex-support-bot/test/__mocks__/simplex-chat.js @@ -0,0 +1,26 @@ +// Mock for simplex-chat — prevents native addon from loading + +function ciContentText(chatItem) { + const c = chatItem.content + if (c.type === "sndMsgContent" || c.type === "rcvMsgContent") return c.msgContent.text + return undefined +} + +function ciBotCommand(chatItem) { + const text = ciContentText(chatItem)?.trim() + if (text) { + const r = text.match(/\/([^\s]+)(.*)/) + if (r && r.length >= 3) return {keyword: r[1], params: r[2].trim()} + } + return undefined +} + +function contactAddressStr(link) { + return link.connShortLink || link.connFullLink +} + +module.exports = { + api: {ChatApi: {}}, + bot: {}, + util: {ciContentText, ciBotCommand, contactAddressStr}, +} diff --git a/apps/simplex-support-bot/tsconfig.json b/apps/simplex-support-bot/tsconfig.json new file mode 100644 index 0000000000..821fa663e3 --- /dev/null +++ b/apps/simplex-support-bot/tsconfig.json @@ -0,0 +1,23 @@ +{ + "include": ["src"], + "compilerOptions": { + "declaration": true, + "forceConsistentCasingInFileNames": true, + "lib": ["ES2022"], + "module": "Node16", + "moduleResolution": "Node16", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noEmitOnError": true, + "outDir": "dist", + "sourceMap": true, + "strict": true, + "strictNullChecks": true, + "target": "ES2022", + "types": ["node"] + } +} diff --git a/apps/simplex-support-bot/vitest.config.ts b/apps/simplex-support-bot/vitest.config.ts new file mode 100644 index 0000000000..c143572a87 --- /dev/null +++ b/apps/simplex-support-bot/vitest.config.ts @@ -0,0 +1,22 @@ +import {defineConfig} from "vitest/config" +import path from "path" + +export default defineConfig({ + test: { + globals: true, + testTimeout: 10000, + // Clear backend signals — .npmrc next to package.json otherwise injects + // npm_config_simplex_backend into every test's env, breaking sqlite-default + // assumptions in parseConfig tests. + env: { + SIMPLEX_BACKEND: "", + npm_config_simplex_backend: "", + }, + }, + resolve: { + alias: { + "simplex-chat": path.resolve(__dirname, "test/__mocks__/simplex-chat.js"), + "@simplex-chat/types": path.resolve(__dirname, "test/__mocks__/simplex-chat-types.js"), + }, + }, +}) From 3d8548094483b2bf861facc7f068fb98c6249014 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 27 Apr 2026 11:46:08 +0100 Subject: [PATCH 29/77] ui: new onboarding (#6888) * ui: onboarding assets * android: fix gradle version check, pass assets dir to builds * desktop: pass assets dir to gradle builds * ui: new onboarding (#6872) * ios: improve onboarding * ios version condition * android strings * merge keys * refactor network conditions to old location * ios scroll headline * remove nav view * kotlin: refactor network commitments page to use existing view * remove unused keys * update why page * configure -> setup * padding for app bar in why page * fix why page * padding * copy translations from the website * export localizations * export again * kotlin: fix why page * fix * import localizations * custom layout * padding for system bars * paddings * more paddings * more padding 2 * update fonts * fonts * line height, padding * paddings * refactor notifications * refactor ios * notification icons in cards * restore profile field * padding * desktop layout create profile * fix * more layout * create profile layout * mobile padding * split mobile and desktop * layout * layout * background * refactor onboarding images * use DARK theme by default * page 3 and 4 layouts * restructure desktop onboarding to two panes * improve layout * improve * fonts, padding * link mobile on full page * fix, reduce noise * change to animation * fix animation * refactor * colors, animation * import * details * fix padding * fix icon * fix * button paddings * accept button on terms page * fix conditions button * close modal --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: shum Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- README.md | 4 +- apps/ios/Shared/Model/SimpleXAPI.swift | 2 +- .../Database/MigrateToAppGroupView.swift | 4 +- .../Shared/Views/Helpers/DetermineWidth.swift | 20 ++ .../Onboarding/ChooseServerOperators.swift | 241 ++++++++--------- .../Views/Onboarding/CreateProfile.swift | 183 +++++++++---- .../Shared/Views/Onboarding/HowItWorks.swift | 54 +++- .../Views/Onboarding/OnboardingView.swift | 18 +- .../Onboarding/SetNotificationsMode.swift | 79 ++---- .../Shared/Views/Onboarding/SimpleXInfo.swift | 141 +++++----- .../Shared/Views/Onboarding/YourNetwork.swift | 193 ++++++++++++++ .../NetworkAndServers/NetworkAndServers.swift | 15 -- .../bg.xcloc/Localized Contents/bg.xliff | 156 +++++++---- .../cs.xcloc/Localized Contents/cs.xliff | 160 ++++++++---- .../de.xcloc/Localized Contents/de.xliff | 166 ++++++++---- .../en.xcloc/Localized Contents/en.xliff | 192 ++++++++++---- .../es.xcloc/Localized Contents/es.xliff | 166 ++++++++---- .../fi.xcloc/Localized Contents/fi.xliff | 151 +++++++---- .../fr.xcloc/Localized Contents/fr.xliff | 156 +++++++---- .../hu.xcloc/Localized Contents/hu.xliff | 166 ++++++++---- .../it.xcloc/Localized Contents/it.xliff | 166 ++++++++---- .../ja.xcloc/Localized Contents/ja.xliff | 152 +++++++---- .../nl.xcloc/Localized Contents/nl.xliff | 157 +++++++---- .../pl.xcloc/Localized Contents/pl.xliff | 166 ++++++++---- .../ru.xcloc/Localized Contents/ru.xliff | 166 ++++++++---- .../th.xcloc/Localized Contents/th.xliff | 151 +++++++---- .../tr.xcloc/Localized Contents/tr.xliff | 157 +++++++---- .../uk.xcloc/Localized Contents/uk.xliff | 157 +++++++---- .../Localized Contents/zh-Hans.xliff | 166 ++++++++---- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 + apps/ios/bg.lproj/Localizable.strings | 33 +-- apps/ios/cs.lproj/Localizable.strings | 44 ++-- apps/ios/de.lproj/Localizable.strings | 59 ++--- apps/ios/es.lproj/Localizable.strings | 59 ++--- apps/ios/fi.lproj/Localizable.strings | 21 -- apps/ios/fr.lproj/Localizable.strings | 30 --- apps/ios/hu.lproj/Localizable.strings | 59 ++--- apps/ios/it.lproj/Localizable.strings | 59 ++--- apps/ios/ja.lproj/Localizable.strings | 24 -- apps/ios/nl.lproj/Localizable.strings | 36 +-- apps/ios/pl.lproj/Localizable.strings | 59 ++--- apps/ios/ru.lproj/Localizable.strings | 59 ++--- apps/ios/th.lproj/Localizable.strings | 21 -- apps/ios/tr.lproj/Localizable.strings | 36 +-- apps/ios/uk.lproj/Localizable.strings | 36 +-- apps/ios/zh-Hans.lproj/Localizable.strings | 59 ++--- .../views/onboarding/SimpleXInfo.android.kt | 2 +- .../kotlin/chat/simplex/common/App.kt | 78 +++--- .../chat/simplex/common/model/SimpleXAPI.kt | 2 +- .../chat/simplex/common/platform/Core.kt | 6 +- .../chat/simplex/common/ui/theme/Type.kt | 2 +- .../chat/simplex/common/views/WelcomeView.kt | 193 +++++++++++--- .../common/views/helpers/AppBarTitle.kt | 17 +- .../simplex/common/views/helpers/ModalView.kt | 8 +- .../views/onboarding/ChooseServerOperators.kt | 244 +++++++++++------- .../common/views/onboarding/HowItWorks.kt | 34 +-- .../views/onboarding/LinkAMobileView.kt | 3 +- .../views/onboarding/OnboardingLayout.kt | 161 ++++++++++++ .../common/views/onboarding/OnboardingView.kt | 5 +- .../views/onboarding/SetNotificationsMode.kt | 61 +++-- .../onboarding/SetupDatabasePassphrase.kt | 2 +- .../common/views/onboarding/SimpleXInfo.kt | 158 ++++++++---- .../common/views/onboarding/YourNetwork.kt | 226 ++++++++++++++++ .../networkAndServers/NetworkAndServers.kt | 11 +- .../commonMain/resources/MR/ar/strings.xml | 2 - .../commonMain/resources/MR/base/strings.xml | 36 ++- .../commonMain/resources/MR/bg/strings.xml | 2 - .../commonMain/resources/MR/ca/strings.xml | 2 - .../commonMain/resources/MR/cs/strings.xml | 11 +- .../commonMain/resources/MR/de/strings.xml | 11 +- .../commonMain/resources/MR/el/strings.xml | 2 - .../commonMain/resources/MR/es/strings.xml | 12 +- .../commonMain/resources/MR/fa/strings.xml | 2 - .../commonMain/resources/MR/fi/strings.xml | 1 - .../commonMain/resources/MR/fr/strings.xml | 1 - .../commonMain/resources/MR/hr/strings.xml | 1 - .../commonMain/resources/MR/hu/strings.xml | 12 +- .../commonMain/resources/MR/in/strings.xml | 2 - .../commonMain/resources/MR/it/strings.xml | 12 +- .../commonMain/resources/MR/iw/strings.xml | 2 - .../commonMain/resources/MR/ja/strings.xml | 2 - .../commonMain/resources/MR/ko/strings.xml | 1 - .../commonMain/resources/MR/ku/strings.xml | 1 - .../commonMain/resources/MR/lt/strings.xml | 1 - .../commonMain/resources/MR/lv/strings.xml | 2 - .../commonMain/resources/MR/ml/strings.xml | 1 - .../commonMain/resources/MR/nl/strings.xml | 2 - .../commonMain/resources/MR/pl/strings.xml | 11 +- .../resources/MR/pt-rBR/strings.xml | 2 - .../commonMain/resources/MR/ro/strings.xml | 2 - .../commonMain/resources/MR/ru/strings.xml | 11 +- .../commonMain/resources/MR/th/strings.xml | 1 - .../commonMain/resources/MR/tr/strings.xml | 2 - .../commonMain/resources/MR/uk/strings.xml | 2 - .../commonMain/resources/MR/vi/strings.xml | 2 - .../resources/MR/zh-rCN/strings.xml | 11 +- .../resources/MR/zh-rTW/strings.xml | 2 - .../assets/default/MR/images/intro.svg | 4 + .../assets/default/MR/images/intro_light.svg | 4 + .../default/MR/images/network_commitments.svg | 4 + .../MR/images/network_commitments_light.svg | 4 + .../assets/default/MR/images/your_network.svg | 4 + .../default/MR/images/your_network_light.svg | 4 + .../assets/default/MR/images/your_profile.svg | 4 + .../default/MR/images/your_profile_light.svg | 4 + assets/ASSETS_LICENSE.md | 18 ++ .../MR/images/banner_create_link@2x.png | Bin 0 -> 29172 bytes .../MR/images/banner_create_link@3x.png | Bin 0 -> 81066 bytes .../MR/images/banner_create_link_light@2x.png | Bin 0 -> 19614 bytes .../MR/images/banner_create_link_light@3x.png | Bin 0 -> 50187 bytes .../MR/images/banner_paste_link@2x.png | Bin 0 -> 27257 bytes .../MR/images/banner_paste_link@3x.png | Bin 0 -> 71865 bytes .../MR/images/banner_paste_link_light@2x.png | Bin 0 -> 20450 bytes .../MR/images/banner_paste_link_light@3x.png | Bin 0 -> 49027 bytes .../images/card_connect_via_link_alpha@2x.png | Bin 0 -> 29205 bytes .../images/card_connect_via_link_alpha@3x.png | Bin 0 -> 58865 bytes .../card_connect_via_link_alpha_light@2x.png | Bin 0 -> 26072 bytes .../card_connect_via_link_alpha_light@3x.png | Bin 0 -> 53471 bytes ...rd_create_your_public_address_alpha@2x.png | Bin 0 -> 58040 bytes ...rd_create_your_public_address_alpha@3x.png | Bin 0 -> 121949 bytes ...ate_your_public_address_alpha_light@2x.png | Bin 0 -> 48223 bytes ...ate_your_public_address_alpha_light@3x.png | Bin 0 -> 101981 bytes ...card_invite_someone_privately_alpha@2x.png | Bin 0 -> 28671 bytes ...card_invite_someone_privately_alpha@3x.png | Bin 0 -> 58047 bytes ...nvite_someone_privately_alpha_light@2x.png | Bin 0 -> 27441 bytes ...nvite_someone_privately_alpha_light@3x.png | Bin 0 -> 53719 bytes ...rd_let_someone_connect_to_you_alpha@2x.png | Bin 0 -> 22453 bytes ...rd_let_someone_connect_to_you_alpha@3x.png | Bin 0 -> 45528 bytes ..._someone_connect_to_you_alpha_light@2x.png | Bin 0 -> 20884 bytes ..._someone_connect_to_you_alpha_light@3x.png | Bin 0 -> 42243 bytes .../MR/images/connect_via_link@2x.png | Bin 0 -> 53785 bytes .../MR/images/connect_via_link@3x.png | Bin 0 -> 121620 bytes .../MR/images/connect_via_link_light@2x.png | Bin 0 -> 18584 bytes .../MR/images/connect_via_link_light@3x.png | Bin 0 -> 40486 bytes .../MR/images/connect_via_link_small@2x.png | Bin 0 -> 17618 bytes .../MR/images/connect_via_link_small@3x.png | Bin 0 -> 35925 bytes .../connect_via_link_small_light@2x.png | Bin 0 -> 7223 bytes .../connect_via_link_small_light@3x.png | Bin 0 -> 13002 bytes .../resources/MR/images/create_group@2x.png | Bin 0 -> 33037 bytes .../resources/MR/images/create_group@3x.png | Bin 0 -> 66181 bytes .../MR/images/create_group_light@2x.png | Bin 0 -> 31323 bytes .../MR/images/create_group_light@3x.png | Bin 0 -> 61914 bytes .../resources/MR/images/intro@2x.png | Bin 0 -> 155174 bytes .../resources/MR/images/intro@3x.png | Bin 0 -> 334390 bytes .../resources/MR/images/intro_light@2x.png | Bin 0 -> 141600 bytes .../resources/MR/images/intro_light@3x.png | Bin 0 -> 307488 bytes .../MR/images/network_commitments@2x.png | Bin 0 -> 37714 bytes .../MR/images/network_commitments@3x.png | Bin 0 -> 76579 bytes .../images/network_commitments_light@2x.png | Bin 0 -> 35903 bytes .../images/network_commitments_light@3x.png | Bin 0 -> 74248 bytes .../resources/MR/images/one_time_link@2x.png | Bin 0 -> 54383 bytes .../resources/MR/images/one_time_link@3x.png | Bin 0 -> 124289 bytes .../MR/images/one_time_link_light@2x.png | Bin 0 -> 29950 bytes .../MR/images/one_time_link_light@3x.png | Bin 0 -> 65712 bytes .../MR/images/one_time_link_small@2x.png | Bin 0 -> 17088 bytes .../MR/images/one_time_link_small@3x.png | Bin 0 -> 35733 bytes .../images/one_time_link_small_light@2x.png | Bin 0 -> 11283 bytes .../images/one_time_link_small_light@3x.png | Bin 0 -> 22624 bytes .../MR/images/simplex_address@2x.png | Bin 0 -> 29997 bytes .../MR/images/simplex_address@3x.png | Bin 0 -> 62343 bytes .../MR/images/simplex_address_light@2x.png | Bin 0 -> 32587 bytes .../MR/images/simplex_address_light@3x.png | Bin 0 -> 65330 bytes .../MR/images/simplex_address_small@2x.png | Bin 0 -> 16224 bytes .../MR/images/simplex_address_small@3x.png | Bin 0 -> 30956 bytes .../images/simplex_address_small_light@2x.png | Bin 0 -> 18175 bytes .../images/simplex_address_small_light@3x.png | Bin 0 -> 34483 bytes .../resources/MR/images/your_network@2x.png | Bin 0 -> 77123 bytes .../resources/MR/images/your_network@3x.png | Bin 0 -> 161831 bytes .../MR/images/your_network_light@2x.png | Bin 0 -> 64730 bytes .../MR/images/your_network_light@3x.png | Bin 0 -> 133590 bytes .../resources/MR/images/your_profile@2x.png | Bin 0 -> 22855 bytes .../resources/MR/images/your_profile@3x.png | Bin 0 -> 47771 bytes .../MR/images/your_profile_light@2x.png | Bin 0 -> 22008 bytes .../MR/images/your_profile_light@3x.png | Bin 0 -> 45086 bytes scripts/android/build-android-bundle.sh | 2 +- scripts/android/build-android.sh | 10 +- scripts/ci/build-desktop-mac.sh | 4 +- scripts/desktop/make-appimage-linux.sh | 2 +- scripts/desktop/make-deb-linux.sh | 2 +- website/README.md | 6 +- 181 files changed, 3813 insertions(+), 2048 deletions(-) create mode 100644 apps/ios/Shared/Views/Onboarding/YourNetwork.swift create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingLayout.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/YourNetwork.kt create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro_light.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments_light.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network_light.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile_light.svg create mode 100644 assets/ASSETS_LICENSE.md create mode 100644 assets/multiplatform/resources/MR/images/banner_create_link@2x.png create mode 100644 assets/multiplatform/resources/MR/images/banner_create_link@3x.png create mode 100644 assets/multiplatform/resources/MR/images/banner_create_link_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/banner_create_link_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/banner_paste_link@2x.png create mode 100644 assets/multiplatform/resources/MR/images/banner_paste_link@3x.png create mode 100644 assets/multiplatform/resources/MR/images/banner_paste_link_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/banner_paste_link_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha@3x.png create mode 100644 assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/card_let_someone_connect_to_you_alpha_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link@2x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link@3x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link_small@2x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link_small@3x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link_small_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/connect_via_link_small_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/create_group@2x.png create mode 100644 assets/multiplatform/resources/MR/images/create_group@3x.png create mode 100644 assets/multiplatform/resources/MR/images/create_group_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/create_group_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/intro@2x.png create mode 100644 assets/multiplatform/resources/MR/images/intro@3x.png create mode 100644 assets/multiplatform/resources/MR/images/intro_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/intro_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/network_commitments@2x.png create mode 100644 assets/multiplatform/resources/MR/images/network_commitments@3x.png create mode 100644 assets/multiplatform/resources/MR/images/network_commitments_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/network_commitments_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link@2x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link@3x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link_small@2x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link_small@3x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link_small_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/one_time_link_small_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address@2x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address@3x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address_small@2x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address_small@3x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address_small_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/simplex_address_small_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/your_network@2x.png create mode 100644 assets/multiplatform/resources/MR/images/your_network@3x.png create mode 100644 assets/multiplatform/resources/MR/images/your_network_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/your_network_light@3x.png create mode 100644 assets/multiplatform/resources/MR/images/your_profile@2x.png create mode 100644 assets/multiplatform/resources/MR/images/your_profile@3x.png create mode 100644 assets/multiplatform/resources/MR/images/your_profile_light@2x.png create mode 100644 assets/multiplatform/resources/MR/images/your_profile_light@3x.png diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c588f1f7a..fcf9455ea5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -675,7 +675,7 @@ jobs: export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo) scripts/desktop/build-lib-windows.sh cd apps/multiplatform - ./gradlew packageMsi + ./gradlew -Psimplex.assets.dir=../../assets packageMsi rm -rf dist-newstyle/src/direct-sq* path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g') echo "package_path=$path" >> $GITHUB_OUTPUT diff --git a/README.md b/README.md index 818ed7142f..252fc95708 100644 --- a/README.md +++ b/README.md @@ -425,9 +425,9 @@ Please do NOT report security vulnerabilities via GitHub issues. ## License -This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, and associated branding materials are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) file. +This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, associated branding materials, and application and website graphic assets (illustrations, images, visual designs, etc.) are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) and [ASSETS_LICENSE](./assets/ASSETS_LICENSE.md) files respectively. -Graphic designs, artworks and layouts are not licensed for re-use. If you want to use them in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source. +If you want to use any graphic assets in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source. [iOS app](https://apps.apple.com/us/app/simplex-chat/id1605771084)   diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 20653ab9db..85bb8a30b4 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2183,7 +2183,7 @@ func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws withAnimation { let savedOnboardingStage = onboardingStageDefault.get() m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1 - ? .step3_ChooseServerOperators + ? .step4_NetworkCommitments : savedOnboardingStage if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() { m.setDeliveryReceipts = true diff --git a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift index 76bdc898d5..56e343588d 100644 --- a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift +++ b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift @@ -110,8 +110,8 @@ struct MigrateToAppGroupView: View { do { resetChatCtrl() try initializeChat(start: true) - onboardingStageDefault.set(.step4_SetNotificationsMode) - chatModel.onboardingStage = .step4_SetNotificationsMode + onboardingStageDefault.set(.step4_NetworkCommitments) + chatModel.onboardingStage = .step4_NetworkCommitments setV3DBMigration(.ready) } catch let error { dbContainerGroupDefault.set(.documents) diff --git a/apps/ios/Shared/Views/Helpers/DetermineWidth.swift b/apps/ios/Shared/Views/Helpers/DetermineWidth.swift index b05ab17089..54e9fe0e80 100644 --- a/apps/ios/Shared/Views/Helpers/DetermineWidth.swift +++ b/apps/ios/Shared/Views/Helpers/DetermineWidth.swift @@ -21,6 +21,19 @@ struct DetermineWidth: View { } } +struct DetermineHeight: View { + typealias Key = MaximumHeightPreferenceKey + var body: some View { + GeometryReader { proxy in + Color.clear + .preference( + key: MaximumHeightPreferenceKey.self, + value: proxy.size.height + ) + } + } +} + struct DetermineWidthImageVideoItem: View { typealias Key = MaximumWidthImageVideoPreferenceKey var body: some View { @@ -41,6 +54,13 @@ struct MaximumWidthPreferenceKey: PreferenceKey { } } +struct MaximumHeightPreferenceKey: PreferenceKey { + static var defaultValue: CGFloat = 0 + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value = max(value, nextValue()) + } +} + struct MaximumWidthImageVideoPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { diff --git a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift index b5598c1f85..b61b81a46b 100644 --- a/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift +++ b/apps/ios/Shared/Views/Onboarding/ChooseServerOperators.swift @@ -44,160 +44,147 @@ struct OnboardingButtonStyle: ButtonStyle { } } -private enum OnboardingConditionsViewSheet: Identifiable { - case showConditions - case configureOperators - - var id: String { - switch self { - case .showConditions: return "showConditions" - case .configureOperators: return "configureOperators" - } - } -} - struct OnboardingConditionsView: View { @EnvironmentObject var theme: AppTheme - @State private var serverOperators: [ServerOperator] = [] - @State private var selectedOperatorIds = Set() - @State private var sheetItem: OnboardingConditionsViewSheet? = nil - @State private var notificationsModeNavLinkActive = false - @State private var justOpened = true + @Environment(\.colorScheme) var colorScheme: ColorScheme + @State private var showConditionsSheet = false + var selectedOperatorIds: Set var body: some View { GeometryReader { g in - let v = ScrollView { - VStack(alignment: .leading, spacing: 20) { - Text("Conditions of use") - .font(.largeTitle) - .bold() - .frame(maxWidth: .infinity, alignment: .center) - .padding(.top, 25) + VStack(alignment: .leading, spacing: 10) { + Spacer(minLength: 0) - Spacer() + heroImage().frame(maxWidth: .infinity, minHeight: 80) - VStack(alignment: .leading, spacing: 20) { - Text("Private chats, groups and your contacts are not accessible to server operators.") - .lineSpacing(2) - .frame(maxWidth: .infinity, alignment: .leading) - Text(""" - By using SimpleX Chat you agree to: - - send only legal content in public groups. - - respect other users – no spam. - """) - .lineSpacing(2) - .frame(maxWidth: .infinity, alignment: .leading) + Text("Network commitments") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .center) + .fixedSize(horizontal: false, vertical: true) - Button("Privacy policy and conditions of use.") { - sheetItem = .showConditions - } - .frame(maxWidth: .infinity, alignment: .leading) - } - .padding(.horizontal, 4) + Text("Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code") + .font(.callout) + .lineSpacing(2) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 4) + .padding(.top, 10) + .fixedSize(horizontal: false, vertical: true) - Spacer() + Text("You commit to:\n- Only legal content in public groups\n- Respect other users - no spam") + .font(.callout) + .lineSpacing(2) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 4) + .padding(.top, 10) + .fixedSize(horizontal: false, vertical: true) - VStack(spacing: 12) { - acceptConditionsButton() - - Button("Configure server operators") { - sheetItem = .configureOperators - } - .frame(minHeight: 40) - } + Button { + showConditionsSheet = true + } label: { + Text("Privacy policy and conditions of use.") + .fontWeight(.medium) + .fixedSize(horizontal: false, vertical: true) } - .padding(25) - .frame(minHeight: g.size.height) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading, 4) + .padding(.top, 10) + .padding(.bottom, 15) + + Spacer(minLength: 0) + + acceptButton() + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) } - .onAppear { - if justOpened { - serverOperators = ChatModel.shared.conditions.serverOperators - selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) - justOpened = false + .padding(.horizontal, 25) + .padding(.top, 25) + .padding(.bottom, 25) + .frame(minHeight: g.size.height) + } + .frame(maxHeight: .infinity) + .navigationBarHidden(true) + .sheet(isPresented: $showConditionsSheet) { + NavigationView { + VStack { + ConditionsTextView() + .padding() + acceptButton() + .padding(.horizontal, 25) + .padding(.bottom, 20) } - } - .sheet(item: $sheetItem) { item in - switch item { - case .showConditions: - SimpleConditionsView() - .modifier(ThemedBackground(grouped: true)) - case .configureOperators: - ChooseServerOperators(serverOperators: serverOperators, selectedOperatorIds: $selectedOperatorIds) - .modifier(ThemedBackground()) - } - } - .frame(maxHeight: .infinity, alignment: .top) - if #available(iOS 16.4, *) { - v.scrollBounceBehavior(.basedOnSize) - } else { - v + .navigationTitle("Conditions of use") + .navigationBarTitleDisplayMode(.large) + .toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) } + .modifier(ThemedBackground(grouped: true)) } } - .frame(maxHeight: .infinity, alignment: .top) - .navigationBarHidden(true) // necessary on iOS 15 } - private func continueToNextStep() { - onboardingStageDefault.set(.step4_SetNotificationsMode) - notificationsModeNavLinkActive = true - } - - func notificationsModeNavLinkButton(_ button: @escaping (() -> some View)) -> some View { + @ViewBuilder + private func heroImage() -> some View { + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "network-commitments" : "network-commitments-light") + .resizable() + .scaledToFit() + #else ZStack { - button() - - NavigationLink(isActive: $notificationsModeNavLinkActive) { - notificationsModeDestinationView() - } label: { - EmptyView() - } - .frame(width: 1, height: 1) - .hidden() + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.5, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end + ) + Image(systemName: "checkmark.shield") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) } + .aspectRatio(1.5, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + #endif } - private func notificationsModeDestinationView() -> some View { - SetNotificationsMode() - .navigationBarBackButtonHidden(true) - .modifier(ThemedBackground()) - } - - private func acceptConditionsButton() -> some View { - notificationsModeNavLinkButton { - Button { - Task { - do { - let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId - let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: Array(selectedOperatorIds)) + private func acceptButton() -> some View { + Button { + Task { + do { + let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId + let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: Array(selectedOperatorIds)) + await MainActor.run { + ChatModel.shared.conditions = r + } + if let enabledOps = enabledOperators(r.serverOperators) { + let r2 = try await setServerOperators(operators: enabledOps) await MainActor.run { - ChatModel.shared.conditions = r + ChatModel.shared.conditions = r2 + completeOnboarding() } - if let enabledOperators = enabledOperators(r.serverOperators) { - let r2 = try await setServerOperators(operators: enabledOperators) - await MainActor.run { - ChatModel.shared.conditions = r2 - continueToNextStep() - } - } else { - await MainActor.run { - continueToNextStep() - } - } - } catch let error { + } else { await MainActor.run { - showAlert( - NSLocalizedString("Error accepting conditions", comment: "alert title"), - message: responseError(error) - ) + completeOnboarding() } } + } catch let error { + await MainActor.run { + showAlert( + NSLocalizedString("Error accepting conditions", comment: "alert title"), + message: responseError(error) + ) + } } - } label: { - Text("Accept") } - .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) - .disabled(selectedOperatorIds.isEmpty) + } label: { + Text("Accept") } + .buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty)) + .disabled(selectedOperatorIds.isEmpty) + } + + private func completeOnboarding() { + let m = ChatModel.shared + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete } private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? { @@ -222,7 +209,7 @@ struct OnboardingConditionsView: View { if !haveXFTPProxy { op.xftpRoles.proxy = true } ops[firstEnabledIndex] = op return ops - } else { // Shouldn't happen - view doesn't let to proceed if no operators are enabled + } else { return nil } } else { @@ -405,5 +392,5 @@ struct ChooseServerOperatorsInfoView: View { } #Preview { - OnboardingConditionsView() + OnboardingConditionsView(selectedOperatorIds: []) } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 7301c0421d..35820fdbe0 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -133,61 +133,115 @@ struct CreateProfile: View { struct CreateFirstProfile: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Environment(\.dismiss) var dismiss + @Environment(\.colorScheme) var colorScheme: ColorScheme @State private var displayName: String = "" @FocusState private var focusDisplayName @State private var nextStepNavLinkActive = false + @State private var showMigrateSheet = false + @State private var fixedContentHeight: CGFloat = 300 var body: some View { - let v = VStack(alignment: .leading, spacing: 16) { - VStack(alignment: .center, spacing: 16) { - Text("Create profile") - .font(.largeTitle) - .bold() - .multilineTextAlignment(.center) - - Text("Your profile is stored on your device and only shared with your contacts.") - .font(.callout) - .foregroundColor(theme.colors.secondary) - .multilineTextAlignment(.center) - } - .fixedSize(horizontal: false, vertical: true) - .frame(maxWidth: .infinity) // Ensures it takes up the full width - .padding(.horizontal, 10) - .onTapGesture { focusDisplayName = false } - - HStack { - let name = displayName.trimmingCharacters(in: .whitespaces) - let validName = mkValidName(name) - ZStack(alignment: .trailing) { - TextField("Enter your name…", text: $displayName) - .focused($focusDisplayName) - .padding(.horizontal) - .padding(.trailing, 20) - .padding(.vertical, 10) - .background( - RoundedRectangle(cornerRadius: 10, style: .continuous) - .fill(Color(uiColor: .tertiarySystemFill)) + let spacing: CGFloat = 10 + let minImageHeight: CGFloat = 80 + let topPadding: CGFloat = 8 + let padding: CGFloat = 25 + GeometryReader { g in + let v = ScrollView { + VStack(alignment: .center, spacing: spacing) { + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "your-profile" : "your-profile-light") + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, minHeight: minImageHeight) + .layoutPriority(1) + #else + ZStack { + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end ) - if name != validName { - Button { - showAlert(.invalidNameError(validName: validName)) - } label: { - Image(systemName: "exclamationmark.circle") - .foregroundColor(.red) - .padding(.horizontal, 10) - } + Image(systemName: "person.crop.rectangle") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) } + .aspectRatio(1.0, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + .frame(maxWidth: .infinity, minHeight: minImageHeight) + .layoutPriority(1) + #endif + + VStack(alignment: .center, spacing: spacing) { + Text("Your profile") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + Text("On your phone, not on any server.") + .font(.title3) + .fontWeight(.medium) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + Text("No account. No phone. No email. No ID.\nThe most secure encryption.") + .font(.footnote) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + profileNameField() + .padding(.top) + .padding(.bottom, 5) + + Spacer(minLength: 0) + + createProfileButton() + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) + } + .overlay(DetermineHeight()) + .onPreferenceChange(DetermineHeight.Key.self) { fixedContentHeight = $0 } + } + .padding(.horizontal, padding) + .padding(.top, topPadding) + .padding(.bottom, padding) + .frame( + minHeight: g.size.height, + idealHeight: max(g.size.height, fixedContentHeight + minImageHeight + spacing + topPadding + padding) + ) + } + .onTapGesture { focusDisplayName = false } + .sheet(isPresented: $showMigrateSheet, onDismiss: { m.migrationState = nil }) { + NavigationView { + MigrateToDevice(migrationState: $m.migrationState) + .navigationTitle("Migrate here") + .modifier(ThemedBackground(grouped: true)) } } - .padding(.top) - - Spacer() - - VStack(spacing: 10) { - createProfileButton() - if !focusDisplayName { - onboardingButtonPlaceholder() + if #available(iOS 17, *) { + v.scrollBounceBehavior(.basedOnSize).defaultScrollAnchor(.bottom) + } else if #available(iOS 16.4.0, *) { + v.scrollBounceBehavior(.basedOnSize) + } else { + v + } + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + if m.migrationState == nil { + m.migrationState = .pasteOrScanLink + } + showMigrateSheet = true + } label: { + HStack(spacing: 4) { + Image(systemName: "tray.and.arrow.down") + Text("Migrate") + .fontWeight(.medium) + } } } } @@ -195,23 +249,40 @@ struct CreateFirstProfile: View { if #available(iOS 16, *) { focusDisplayName = true } else { - // it does not work before animation completes on iOS 15 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { focusDisplayName = true } } } - .padding(.horizontal, 25) - .padding(.bottom, 25) - .frame(maxWidth: .infinity, alignment: .leading) - if #available(iOS 16, *) { - return v.padding(.top, 10) - } else { - return v.padding(.top, 75).ignoresSafeArea(.all, edges: .top) + .frame(maxHeight: .infinity) + } + + private func profileNameField() -> some View { + let name = displayName.trimmingCharacters(in: .whitespaces) + let validName = mkValidName(name) + return ZStack(alignment: .trailing) { + TextField("Enter profile name...", text: $displayName) + .focused($focusDisplayName) + .padding(.horizontal) + .padding(.trailing, name != validName ? 20 : 0) + .padding(.vertical, 10) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(Color(uiColor: .tertiarySystemFill)) + ) + if name != validName { + Button { + showAlert(.invalidNameError(validName: validName)) + } label: { + Image(systemName: "exclamationmark.circle") + .foregroundColor(.red) + .padding(.horizontal, 10) + } + } } } - func createProfileButton() -> some View { + private func createProfileButton() -> some View { ZStack { Button { createProfile() @@ -236,7 +307,7 @@ struct CreateFirstProfile: View { } private func nextStepDestinationView() -> some View { - OnboardingConditionsView() + YourNetworkView() .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) } diff --git a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift index c881146ef5..e9b9c6b970 100644 --- a/apps/ios/Shared/Views/Onboarding/HowItWorks.swift +++ b/apps/ios/Shared/Views/Onboarding/HowItWorks.swift @@ -9,7 +9,7 @@ import SwiftUI -struct HowItWorks: View { +struct OldHowItWorks: View { @Environment(\.dismiss) var dismiss: DismissAction @EnvironmentObject var m: ChatModel var onboarding: Bool @@ -61,9 +61,57 @@ struct HowItWorks: View { } } -struct HowItWorks_Previews: PreviewProvider { +struct WhySimpleX: View { + @Environment(\.dismiss) var dismiss: DismissAction + @EnvironmentObject var m: ChatModel + var onboarding: Bool + @Binding var createProfileNavLinkActive: Bool + + var body: some View { + VStack(alignment: .leading) { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + Text("You were born without an account") + .font(.title) + .bold() + .padding(.top) + Text("Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life.") + Text("Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible.") + Text("There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected.") + Text("Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign.") + Text("Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public.") + Text("The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it.") + Text("Because we destroyed the power to know who you are. So that your power can never be taken.") + Text("Be free in your network.") + } + } + .padding(.bottom, 16) + + Spacer() + + if onboarding { + createFirstProfileButton() + } + } + .padding(onboarding ? 25 : 16) + .frame(maxHeight: .infinity, alignment: .top) + .modifier(ThemedBackground()) + } + + private func createFirstProfileButton() -> some View { + Button { + dismiss() + createProfileNavLinkActive = true + } label: { + Text("Get started") + } + .buttonStyle(OnboardingButtonStyle(isDisabled: false)) + } +} + +struct WhySimpleX_Previews: PreviewProvider { static var previews: some View { - HowItWorks( + WhySimpleX( onboarding: true, createProfileNavLinkActive: Binding.constant(false) ) diff --git a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift index daef95fbc6..39ccabce04 100644 --- a/apps/ios/Shared/Views/Onboarding/OnboardingView.swift +++ b/apps/ios/Shared/Views/Onboarding/OnboardingView.swift @@ -19,17 +19,18 @@ struct OnboardingView: View { case .step1_SimpleXInfo: SimpleXInfo(onboarding: true) .modifier(ThemedBackground()) - case .step2_CreateProfile: // deprecated + case .step2_CreateProfile: CreateFirstProfile() .modifier(ThemedBackground()) case .step3_CreateSimpleXAddress: // deprecated CreateSimpleXAddress() - case .step3_ChooseServerOperators: - OnboardingConditionsView() + case .step3_ChooseServerOperators, + .step4_SetNotificationsMode: // deprecated + YourNetworkView() .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) - case .step4_SetNotificationsMode: - SetNotificationsMode() + case .step4_NetworkCommitments: + OnboardingConditionsView(selectedOperatorIds: Set(ChatModel.shared.conditions.serverOperators.filter { $0.enabled }.map { $0.operatorId })) .navigationBarBackButtonHidden(true) .modifier(ThemedBackground()) case .onboardingComplete: EmptyView() @@ -45,10 +46,11 @@ func onboardingButtonPlaceholder() -> some View { // Spec: spec/client/navigation.md#onboardingStage enum OnboardingStage: String, Identifiable { case step1_SimpleXInfo - case step2_CreateProfile // deprecated + case step2_CreateProfile case step3_CreateSimpleXAddress // deprecated - case step3_ChooseServerOperators // changed to simplified conditions - case step4_SetNotificationsMode + case step3_ChooseServerOperators + case step4_SetNotificationsMode // deprecated + case step4_NetworkCommitments case onboardingComplete public var id: Self { self } diff --git a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift index 717405b03b..1a1f1bb68c 100644 --- a/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift +++ b/apps/ios/Shared/Views/Onboarding/SetNotificationsMode.swift @@ -11,45 +11,39 @@ import SwiftUI import SimpleXChat struct SetNotificationsMode: View { - @EnvironmentObject var m: ChatModel - @State private var notificationMode = NotificationsMode.instant - @State private var showAlert: NotificationAlert? - @State private var showInfo: Bool = false + @Environment(\.dismiss) var dismiss + @Binding var notificationMode: NotificationsMode + @State private var showInfo = false var body: some View { GeometryReader { g in - let v = ScrollView { + ScrollView { VStack(alignment: .center, spacing: 20) { Text("Push notifications") .font(.largeTitle) .bold() .padding(.top, 25) - - infoText() - + + Button { + showInfo = true + } label: { + Label("How it affects privacy", systemImage: "info.circle") + .font(.headline) + } + Spacer() ForEach(NotificationsMode.values) { mode in NtfModeSelector(mode: mode, selection: $notificationMode) } - + Spacer() - + VStack(spacing: 10) { Button { - if let token = m.deviceToken { - setNotificationsMode(token, notificationMode) - } else { - AlertManager.shared.showAlertMsg(title: "No device token!") - } - onboardingStageDefault.set(.onboardingComplete) - m.onboardingStage = .onboardingComplete + dismiss() } label: { - if case .off = notificationMode { - Text("Use chat") - } else { - Text("Enable notifications") - } + Text("OK") } .buttonStyle(OnboardingButtonStyle()) onboardingButtonPlaceholder() @@ -58,50 +52,11 @@ struct SetNotificationsMode: View { .padding(25) .frame(minHeight: g.size.height) } - if #available(iOS 16.4, *) { - v.scrollBounceBehavior(.basedOnSize) - } else { - v - } } .frame(maxHeight: .infinity) .sheet(isPresented: $showInfo) { NotificationsInfoView() } - .navigationBarHidden(true) // necessary on iOS 15 - } - - private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) { - switch mode { - case .off: - m.tokenStatus = .new - m.notificationMode = .off - default: - Task { - do { - let status = try await apiRegisterToken(token: token, notificationMode: mode) - await MainActor.run { - m.tokenStatus = status - m.notificationMode = mode - } - } catch let error { - let a = getErrorAlert(error, "Error enabling notifications") - AlertManager.shared.showAlertMsg( - title: a.title, - message: a.message - ) - } - } - } - } - - private func infoText() -> some View { - Button { - showInfo = true - } label: { - Label("How it affects privacy", systemImage: "info.circle") - .font(.headline) - } } } @@ -180,6 +135,6 @@ struct NotificationsInfoView: View { struct NotificationsModeView_Previews: PreviewProvider { static var previews: some View { - SetNotificationsMode() + SetNotificationsMode(notificationMode: .constant(.instant)) } } diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index 80f35c1190..15b8e05b5e 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -12,68 +12,84 @@ import SimpleXChat struct SimpleXInfo: View { @EnvironmentObject var m: ChatModel + @EnvironmentObject var theme: AppTheme @Environment(\.colorScheme) var colorScheme: ColorScheme - @State private var showHowItWorks = false + @State private var showWhyBuilt = false @State private var createProfileNavLinkActive = false var onboarding: Bool var body: some View { GeometryReader { g in - let v = ScrollView { - VStack(alignment: .leading) { - VStack(alignment: .center, spacing: 10) { - Image(colorScheme == .light ? "logo" : "logo-light") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: g.size.width * 0.67) - .padding(.bottom, 8) - .padding(.leading, 4) - .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) - - Button { - showHowItWorks = true - } label: { - Label("The future of messaging", systemImage: "info.circle") - .font(.headline) - } - } + VStack(alignment: .center, spacing: 10) { + Image(colorScheme == .light ? "logo" : "logo-light") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: (g.size.width - 50) * 0.55) + .padding(.leading, 4) + .frame(maxWidth: .infinity, minHeight: 48, alignment: .top) - Spacer() + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "intro" : "intro-light") + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + #else + ZStack { + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end + ) + Image(systemName: "bubble.left.and.bubble.right") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) + } + .aspectRatio(1.0, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + .frame(maxWidth: .infinity) + #endif - VStack(alignment: .leading) { - onboardingInfoRow("privacy", "Privacy redefined", - "No user identifiers.", width: 48) - onboardingInfoRow("shield", "Immune to spam", - "You decide who can connect.", width: 46) - onboardingInfoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized", - "Anybody can host servers.", width: 46) - } - .padding(.leading, 16) + Text("Be free\nin your network") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) - Spacer() + Text("Private and secure messaging.") + .font(.title3) + .fontWeight(.medium) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) - if onboarding { - VStack(spacing: 10) { - createFirstProfileButton() + Text("The first network where you own\nyour contacts and groups.") + .font(.footnote) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) - Button { - m.migrationState = .pasteOrScanLink - } label: { - Label("Migrate from another device", systemImage: "tray.and.arrow.down") - .font(.system(size: 17, weight: .semibold)) - .frame(minHeight: 40) - } - .frame(maxWidth: .infinity) - } + if onboarding { + Spacer(minLength: 0) + + createFirstProfileButton() + .padding(.vertical, 10) + + Button { + showWhyBuilt = true + } label: { + Label("Why SimpleX is built.", systemImage: "info.circle") + .font(.headline) } } - .padding(.horizontal, 25) - .padding(.top, 75) - .padding(.bottom, 25) - .frame(minHeight: g.size.height) } + .padding(.horizontal, 25) + .padding(.top, 28) + .padding(.bottom, 20) + .frame(minHeight: g.size.height) .sheet(isPresented: Binding( - get: { m.migrationState != nil }, + get: { m.migrationState != nil && !createProfileNavLinkActive }, set: { _ in m.migrationState = nil MigrationToDeviceState.save(nil) } @@ -86,17 +102,12 @@ struct SimpleXInfo: View { .modifier(ThemedBackground(grouped: true)) } } - .sheet(isPresented: $showHowItWorks) { - HowItWorks( + .sheet(isPresented: $showWhyBuilt) { + WhySimpleX( onboarding: onboarding, createProfileNavLinkActive: $createProfileNavLinkActive ) } - if #available(iOS 16.4, *) { - v.scrollBounceBehavior(.basedOnSize) - } else { - v - } } .onAppear() { setLastVersionDefault() @@ -105,32 +116,12 @@ struct SimpleXInfo: View { .navigationBarHidden(true) // necessary on iOS 15 } - private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View { - HStack(alignment: .top) { - Image(image) - .resizable() - .scaledToFit() - .frame(width: width, height: 54) - .frame(width: 54) - .padding(.trailing, 10) - VStack(alignment: .leading, spacing: 4) { - Text(title).font(.headline) - Text(text).frame(minHeight: 40, alignment: .top) - .font(.callout) - .lineLimit(3) - .fixedSize(horizontal: false, vertical: true) - } - .padding(.top, 4) - } - .padding(.bottom, 12) - } - private func createFirstProfileButton() -> some View { ZStack { Button { createProfileNavLinkActive = true } label: { - Text("Create your profile") + Text("Get started") } .buttonStyle(OnboardingButtonStyle(isDisabled: false)) diff --git a/apps/ios/Shared/Views/Onboarding/YourNetwork.swift b/apps/ios/Shared/Views/Onboarding/YourNetwork.swift new file mode 100644 index 0000000000..d3727e196e --- /dev/null +++ b/apps/ios/Shared/Views/Onboarding/YourNetwork.swift @@ -0,0 +1,193 @@ +// +// YourNetwork.swift +// SimpleX (iOS) +// +// Created by Evgeny on 22/04/2026. +// Copyright © 2026 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +private enum YourNetworkSheet: Identifiable { + case configureOperators + case configureNotifications + + var id: String { + switch self { + case .configureOperators: return "configureOperators" + case .configureNotifications: return "configureNotifications" + } + } +} + +struct YourNetworkView: View { + @EnvironmentObject var theme: AppTheme + @Environment(\.colorScheme) var colorScheme: ColorScheme + @State private var serverOperators: [ServerOperator] = [] + @State private var selectedOperatorIds = Set() + @State private var notificationMode: NotificationsMode = .instant + @State private var sheetItem: YourNetworkSheet? = nil + @State private var nextStepNavLinkActive = false + @State private var justOpened = true + + var body: some View { + GeometryReader { g in + VStack(alignment: .center, spacing: 10) { + Spacer(minLength: 0) + + #if SIMPLEX_ASSETS + Image(colorScheme == .light ? "your-network" : "your-network-light") + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + #else + ZStack { + let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5) + LinearGradient( + stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops, + startPoint: gp.start, + endPoint: gp.end + ) + Image(systemName: "network") + .font(.system(size: 72)) + .foregroundColor(theme.colors.primary) + } + .aspectRatio(1.0, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .padding(.horizontal, 25) + .frame(maxWidth: .infinity) + #endif + + Text("Your network") + .font(.largeTitle) + .bold() + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + .padding(.top, 15) + + Text("Network routers cannot know\nwho talks to whom") + .font(.title3) + .fontWeight(.medium) + .foregroundColor(theme.colors.secondary) + .multilineTextAlignment(.center) + .fixedSize(horizontal: false, vertical: true) + + VStack(alignment: .leading, spacing: 20) { + configureRoutersButton() + configureNotificationsButton() + } + .padding(.top, 15) + .padding(.bottom, 15) + + Spacer(minLength: 0) + + continueButton() + .padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0) + } + .padding(.horizontal, 25) + .padding(.top, 8) + .padding(.bottom, 20) + .frame(minHeight: g.size.height) + } + .onAppear { + if justOpened { + serverOperators = ChatModel.shared.conditions.serverOperators + selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId }) + justOpened = false + } + } + .sheet(item: $sheetItem) { item in + switch item { + case .configureOperators: + ChooseServerOperators(serverOperators: serverOperators, selectedOperatorIds: $selectedOperatorIds) + .modifier(ThemedBackground()) + case .configureNotifications: + SetNotificationsMode(notificationMode: $notificationMode) + .modifier(ThemedBackground()) + } + } + .frame(maxHeight: .infinity) + .navigationBarHidden(true) + } + + private func configureRoutersButton() -> some View { + Button { + sheetItem = .configureOperators + } label: { + HStack(spacing: 6) { + Text("Setup routers") + .fontWeight(.medium) + ForEach(serverOperators.reversed()) { op in + Image(op.logo(colorScheme)) + .resizable() + .scaledToFit() + .frame(width: 22, height: 22) + .grayscale(selectedOperatorIds.contains(op.operatorId) ? 0.0 : 1.0) + } + } + } + } + + private func configureNotificationsButton() -> some View { + Button { + sheetItem = .configureNotifications + } label: { + HStack(spacing: 4) { + Text("Setup notifications") + .fontWeight(.medium) + Image(systemName: notificationMode.icon) + } + } + } + + private func continueButton() -> some View { + ZStack { + Button { + applyNotificationMode() + onboardingStageDefault.set(.step4_NetworkCommitments) + nextStepNavLinkActive = true + } label: { + Text("Continue") + } + .buttonStyle(OnboardingButtonStyle()) + + NavigationLink(isActive: $nextStepNavLinkActive) { + OnboardingConditionsView(selectedOperatorIds: selectedOperatorIds) + .navigationBarBackButtonHidden(true) + .modifier(ThemedBackground()) + } label: { + EmptyView() + } + .frame(width: 1, height: 1) + .hidden() + } + } + + private func applyNotificationMode() { + let m = ChatModel.shared + if let token = m.deviceToken { + switch notificationMode { + case .off: + m.tokenStatus = .new + m.notificationMode = .off + default: + Task { + do { + let status = try await apiRegisterToken(token: token, notificationMode: notificationMode) + await MainActor.run { + m.tokenStatus = status + m.notificationMode = notificationMode + } + } catch let error { + let a = getErrorAlert(error, "Error enabling notifications") + AlertManager.shared.showAlertMsg( + title: a.title, + message: a.message + ) + } + } + } + } + } +} diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift index c1f2470669..f10b945dc0 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift @@ -351,21 +351,6 @@ private func regularConditionsHeader() -> some View { } } -struct SimpleConditionsView: View { - - var body: some View { - VStack(alignment: .leading, spacing: 20) { - regularConditionsHeader() - .padding(.top) - .padding(.top) - ConditionsTextView() - .padding(.bottom) - .padding(.bottom) - } - .padding(.horizontal, 25) - .frame(maxHeight: .infinity) - } -} func validateServers_( _ userServers: Binding<[UserOperatorServers]>, diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index c8c564ada7..ce993ecb02 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -1117,11 +1117,6 @@ swipe action Отговор на повикване No comment provided by engineer. - - Anybody can host servers. - Протокол и код с отворен код – всеки може да оперира собствени сървъри. - No comment provided by engineer. - App build: %@ Компилация на приложението: %@ @@ -1326,6 +1321,19 @@ swipe action Лош хеш на съобщението No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls По-добри обаждания @@ -1509,15 +1517,6 @@ swipe action Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - С използването на SimpleX Chat вие се съгласявате със: -- изпращане само на легално съдържание в публични групи. -- уважение към другите потребители – без спам. - No comment provided by engineer. - Call already ended! Разговорът вече приключи! @@ -2048,11 +2047,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Конфигуриране на сървърни оператори - No comment provided by engineer. - Confirm Потвърди @@ -2650,11 +2644,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - Децентрализиран - No comment provided by engineer. - Decode link relay test step @@ -3461,6 +3450,10 @@ chat item action Въведете парола по-горе, за да се покаже! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4297,6 +4290,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4560,11 +4557,6 @@ Error: %2$@ Веднага No comment provided by engineer. - - Immune to spam - Защитен от спам и злоупотреби - No comment provided by engineer. - Import Импортиране @@ -5402,16 +5394,15 @@ This is your link for group %@! Съобщенията, файловете и разговорите са защитени чрез **квантово устойчиво e2e криптиране** с перфектна секретност при препращане, правдоподобно опровержение и възстановяване при взлом. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Мигрирай устройството No comment provided by engineer. - - Migrate from another device - Мигриране от друго устройство - No comment provided by engineer. - Migrate here Мигрирай тук @@ -5524,6 +5515,11 @@ This is your link for group %@! Име swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Мрежа и сървъри @@ -5555,6 +5551,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Мрежови настройки @@ -5664,6 +5665,11 @@ This is your link for group %@! Не No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -5808,15 +5814,18 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - Първата платформа без никакви потребителски идентификатори – поверителна по дизайн. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -5891,6 +5900,10 @@ new chat action Стара база данни No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Линк за еднократна покана @@ -6117,6 +6130,13 @@ alert button Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -6420,17 +6440,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Поверителността преосмислена - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -7720,6 +7735,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Променете формата на профилните изображения @@ -8435,9 +8458,9 @@ It can happen because of some bug or when the connection is compromised.Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване! No comment provided by engineer. - - The future of messaging - Ново поколение поверителни съобщения + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8472,6 +8495,10 @@ It can happen because of some bug or when the connection is compromised.Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8512,6 +8539,14 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -9335,6 +9370,10 @@ To connect, please ask your contact to create another connection link and check Когато споделяте инкогнито профил с някого, този профил ще се използва за групите, в които той ви кани. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9581,6 +9620,12 @@ Repeat join request? Не може да изпращате съобщения! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9590,11 +9635,6 @@ Repeat join request? Не можахте да бъдете потвърдени; Моля, опитайте отново. No comment provided by engineer. - - You decide who can connect. - Хората могат да се свържат с вас само чрез ликовете, които споделяте. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9658,6 +9698,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9789,6 +9833,10 @@ Repeat connection request? Вашите контакти ще останат свързани. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9807,6 +9855,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Вашите настройки diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 1a0a53b884..2f2059edd3 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -1102,11 +1102,6 @@ swipe action Přijmout hovor No comment provided by engineer. - - Anybody can host servers. - Servery může provozovat kdokoli. - No comment provided by engineer. - App build: %@ Sestavení aplikace: %@ @@ -1301,6 +1296,21 @@ swipe action Špatný hash zprávy No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Buďte svobodní ve své síti. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Protože jsme zničili sílu vědět, kdo jste. Aby vám vaši moc nikdo nemohl vzít. + No comment provided by engineer. + Better calls Lepší volání @@ -1471,12 +1481,6 @@ swipe action Podle profilu chatu (výchozí) nebo [podle připojení](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! Hovor již skončil! @@ -1974,10 +1978,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm Potvrdit @@ -2540,11 +2540,6 @@ Toto je váš vlastní jednorázový odkaz! Debug delivery No comment provided by engineer. - - Decentralized - Decentralizované - No comment provided by engineer. - Decode link relay test step @@ -3330,6 +3325,10 @@ chat item action Zadejte heslo do hledání! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4145,6 +4144,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4404,11 +4407,6 @@ Error: %2$@ Ihned No comment provided by engineer. - - Immune to spam - Odolná vůči spamu a zneužití - No comment provided by engineer. - Import Import @@ -5218,12 +5216,12 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device + + Migrate device No comment provided by engineer. @@ -5332,6 +5330,11 @@ This is your link for group %@! Jméno swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Síť a servery @@ -5361,6 +5364,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Nastavení sítě @@ -5469,6 +5477,11 @@ This is your link for group %@! Ne No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -5612,15 +5625,20 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - Bez uživatelských identifikátorů. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nikdo nesledoval vaše konverzace. Nikdo nevytvořil mapu, kde jste byli. Soukromí nikdy nebylo funkcí - byl to způsob života. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nejde o to mít lepší zámek na dveřích někoho jiného. Ani o to mít nájemce, který respektuje vaše soukromí, ale vede evidenci všech vašich návštěvníků. Nejste host. Jste doma. Ani král k vám nemůže vstoupit - jste suverén. + No comment provided by engineer. + Not all relays connected alert title @@ -5693,6 +5711,10 @@ new chat action Stará databáze No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Jednorázový zvací odkaz @@ -5916,6 +5938,13 @@ alert button Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -6207,17 +6236,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Nové vymezení soukromí - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -7486,6 +7510,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -8187,9 +8219,9 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení! No comment provided by engineer. - - The future of messaging - Nová generace soukromých zpráv + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8224,6 +8256,11 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Stará databáze nebyla během přenášení odstraněna, lze ji smazat. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Nejstarší lidská svoboda - mluvit s druhým člověkem, aniž by byl sledován - postavena na infrastruktuře, která ji nemůže zradit. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8263,6 +8300,16 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Pak jsme se přesunuli na internet a každá platforma chtěla o vás něco vědět - vaše jméno, vaše číslo, vaše přátele. Smířili jsme se s tím, že cenou za komunikaci s ostatními je dát někomu vědět, s kým mluvíme. Každá generace, lidská i technická, to tak měla - telefon, e-mail, komunikátory, sociální sítě. Zdálo se, že je to jediný možný způsob. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Existuje i jiný způsob. Síť bez telefonních čísel. Bez uživatelských jmen. Bez účtů. Bez jakékoli uživatelské identity. Síť, která spojuje lidi a přenáší šifrované zprávy, aniž by bylo známo, kdo je připojen. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -9055,6 +9102,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Pokud s někým sdílíte inkognito profil, bude tento profil použit pro skupiny, do kterých vás pozve. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -9285,6 +9336,12 @@ Repeat join request? Nemůžete posílat zprávy! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9294,11 +9351,6 @@ Repeat join request? Nemohli jste být ověřeni; Zkuste to prosím znovu. No comment provided by engineer. - - You decide who can connect. - Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9360,6 +9412,11 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + Narodili jste se bez účtu. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9490,6 +9547,11 @@ Repeat connection request? Vaše kontakty zůstanou připojeny. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Vaše konverzace patří vám, jako tomu bylo vždy před internetem. Síť není místo, které navštěvujete. Je to místo, které vytváříte a vlastníte. A nikdo vám ho nemůže vzít, ať už je soukromé, nebo veřejné. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9508,6 +9570,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Vaše preference diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 6a603c8254..8755323ace 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1127,11 +1127,6 @@ swipe action Anruf annehmen No comment provided by engineer. - - Anybody can host servers. - Jeder kann seine eigenen Server aufsetzen. - No comment provided by engineer. - App build: %@ App Build: %@ @@ -1337,6 +1332,21 @@ swipe action Ungültiger Nachrichten-Hash No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Genießen Sie die Freiheit in Ihrem Netzwerk. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Weil wir die Macht zerstört haben, zu wissen, wer Sie sind. Damit Ihnen Ihre Macht niemals genommen werden kann. + No comment provided by engineer. + Better calls Verbesserte Anrufe @@ -1522,15 +1532,6 @@ swipe action Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden: -- nur legale Inhalte in öffentlichen Gruppen zu versenden. -- andere Nutzer zu respektieren - kein Spam. - No comment provided by engineer. - Call already ended! Anruf ist bereits beendet! @@ -2079,11 +2080,6 @@ chat toolbar Relais konfigurieren No comment provided by engineer. - - Configure server operators - Server-Betreiber konfigurieren - No comment provided by engineer. - Confirm Bestätigen @@ -2705,11 +2701,6 @@ Das ist Ihr eigener Einmal-Link! Debugging-Zustellung No comment provided by engineer. - - Decentralized - Dezentral - No comment provided by engineer. - Decode link Link dekodieren @@ -3569,6 +3560,10 @@ chat item action Für die Anzeige das Passwort im Suchfeld eingeben! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… Relais-Name eingeben… @@ -4480,6 +4475,10 @@ Fehler: %2$@ Bei Erwähnung benachrichtigt werden. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Guten Nachmittag! @@ -4755,11 +4754,6 @@ Fehler: %2$@ Sofort No comment provided by engineer. - - Immune to spam - Immun gegen Spam und Missbrauch - No comment provided by engineer. - Import Importieren @@ -5656,16 +5650,15 @@ Das ist Ihr Link für die Gruppe %@! Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Abstreitbarkeit und Wiederherstellung nach einer Kompromittierung geschützt. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Gerät migrieren No comment provided by engineer. - - Migrate from another device - Von einem anderen Gerät migrieren - No comment provided by engineer. - Migrate here Hierher migrieren @@ -5781,6 +5774,11 @@ Das ist Ihr Link für die Gruppe %@! Name swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Netzwerk & Server @@ -5815,6 +5813,11 @@ Das ist Ihr Link für die Gruppe %@! Netzwerk-Betreiber No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Netzwerkeinstellungen @@ -5934,6 +5937,11 @@ Das ist Ihr Link für die Gruppe %@! Nein No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6098,15 +6106,20 @@ Das ist Ihr Link für die Gruppe %@! Keine ungelesenen Chats No comment provided by engineer. - - No user identifiers. - Keine Benutzerkennungen. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Niemand verfolgte Ihre Gespräche. Niemand erstellte eine Karte, wo Sie sich aufgehalten haben. Privatsphäre war nie ein Feature - sie war selbstverständlich. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nicht ein besseres Schloss an der Tür eines Anderen. Kein freundlicher Vermieter, der Ihre Privatsphäre respektiert, aber dennoch jeden Besucher registriert. Sie sind kein Gast. Sie sind zu Hause. Kein Vermieter, kein Fremder kann es betreten - Sie sind souverän. + No comment provided by engineer. + Not all relays connected Es sind nicht alle Relais verbunden @@ -6188,6 +6201,10 @@ new chat action Alte Datenbank No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Einmal-Einladungslink @@ -6435,6 +6452,13 @@ alert button Betreiber-Server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Oder importieren Sie eine Archiv-Datei @@ -6762,18 +6786,12 @@ Fehler: %@ Datenschutz- und Nutzungsbedingungen. No comment provided by engineer. - - Privacy redefined - Datenschutz neu definiert - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. + + Private and secure messaging. No comment provided by engineer. @@ -8186,6 +8204,14 @@ chat item action Die Einstellungen wurden geändert. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Form der Profil-Bilder @@ -8972,9 +8998,9 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen! No comment provided by engineer. - - The future of messaging - Die nächste Generation von privatem Messaging + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -9012,6 +9038,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Die älteste Freiheit des Menschen - mit einem anderen Menschen sprechen zu können, ohne beobachtet zu werden - gestützt auf einer Infrastruktur, die Sie nicht verraten kann. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**. @@ -9057,6 +9088,16 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Design No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Dann sind wir online gegangen, und jede Plattform wollte Etwas von Ihnen - Ihren Namen, Ihre Nummer, Ihre Freunde. Wir akzeptierten, dass es der Preis mit Anderen zu kommunizieren ist, Jemandem preiszugeben, mit wem und wie wir miteinander kommunizieren. Jede Generation, Menschen und Technologien, kannten es nur so - Telefon, E-Mail, Messenger, soziale Medien. Es schien der einzig mögliche Weg zu sein. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Es gibt einen anderen Weg. Ein Netzwerk ohne Telefonnummern, ohne Benutzernamen, ohne Benutzerkennungen und ohne jegliche Benutzeridentität. Ein Netzwerk, welches Menschen verbindet und verschlüsselte Nachrichten überträgt, ohne zu wissen, wer mit wem verbunden ist. + No comment provided by engineer. + These conditions will also apply for: **%@**. Diese Nutzungsbedingungen gelten auch für: **%@**. @@ -9947,6 +9988,10 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wenn Sie ein Inkognito-Profil mit Jemandem teilen, wird dieses Profil auch für die Gruppen verwendet, für die Sie von diesem Kontakt eingeladen werden. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10209,6 +10254,12 @@ Verbindungsanfrage wiederholen? Sie können keine Nachrichten versenden! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. Sie haben sich über diesen Relais‑Link mit dem Kanal verbunden. @@ -10219,11 +10270,6 @@ Verbindungsanfrage wiederholen? Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut. No comment provided by engineer. - - You decide who can connect. - Sie entscheiden, wer sich mit Ihnen verbinden kann. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10291,6 +10337,11 @@ Verbindungsanfrage wiederholen? Sie sollten Benachrichtigungen erhalten. token info + + You were born without an account + Sie wurden ohne eine Benutzerkennung geboren. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Sie können erst dann Nachrichten versenden, **sobald Ihre Anfrage angenommen wurde**. @@ -10431,6 +10482,11 @@ Verbindungsanfrage wiederholen? Ihre Kontakte bleiben weiterhin verbunden. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Ihre Kommunikation gehört Ihnen, so wie es immer war, bevor es das Internet gab. Das Netzwerk ist kein Ort, den Sie besuchen. Es ist ein Ort, den Sie erschaffen und besitzen und Niemand kann es Ihnen nehmen, egal ob Sie es privat oder öffentlich machen. + No comment provided by engineer. + Your credentials may be sent unencrypted. Ihre Anmeldeinformationen können unverschlüsselt versendet werden. @@ -10451,6 +10507,10 @@ Verbindungsanfrage wiederholen? Ihre Gruppe No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Ihre Präferenzen diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 9ebdad1759..d7941b7981 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -1146,11 +1146,6 @@ swipe action Answer call No comment provided by engineer. - - Anybody can host servers. - Anybody can host servers. - No comment provided by engineer. - App build: %@ App build: %@ @@ -1356,6 +1351,23 @@ swipe action Bad message hash No comment provided by engineer. + + Be free +in your network + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Better calls @@ -1541,15 +1553,6 @@ swipe action By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! Call already ended! @@ -2105,11 +2108,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Configure server operators - No comment provided by engineer. - Confirm Confirm @@ -2735,11 +2733,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - Decentralized - No comment provided by engineer. - Decode link Decode link @@ -3603,6 +3596,11 @@ chat item action Enter password above to show! No comment provided by engineer. + + Enter profile name... + Enter profile name... + No comment provided by engineer. + Enter relay name… Enter relay name… @@ -4516,6 +4514,11 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + Get started + No comment provided by engineer. + Good afternoon! Good afternoon! @@ -4791,11 +4794,6 @@ Error: %2$@ Immediately No comment provided by engineer. - - Immune to spam - Immune to spam - No comment provided by engineer. - Import Import @@ -5698,16 +5696,16 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. + + Migrate + Migrate + No comment provided by engineer. + Migrate device Migrate device No comment provided by engineer. - - Migrate from another device - Migrate from another device - No comment provided by engineer. - Migrate here Migrate here @@ -5823,6 +5821,13 @@ This is your link for group %@! Name swipe action + + Network +commitments + Network +commitments + No comment provided by engineer. + Network & servers Network & servers @@ -5858,6 +5863,13 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Network settings @@ -5978,6 +5990,13 @@ This is your link for group %@! No No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No active relays @@ -6143,9 +6162,9 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - No user identifiers. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. @@ -6153,6 +6172,11 @@ This is your link for group %@! Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected Not all relays connected @@ -6234,6 +6258,11 @@ new chat action Old database No comment provided by engineer. + + On your phone, not on any server. + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link One-time invitation link @@ -6484,6 +6513,17 @@ alert button Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Or import archive file @@ -6815,19 +6855,14 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Privacy redefined - No comment provided by engineer. - Privacy: for owners and subscribers. Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. + Private and secure messaging. No comment provided by engineer. @@ -8250,6 +8285,16 @@ chat item action Settings were changed. alert message + + Setup notifications + Setup notifications + No comment provided by engineer. + + + Setup routers + Setup routers + No comment provided by engineer. + Shape profile images Shape profile images @@ -9044,9 +9089,11 @@ It can happen because of some bug or when the connection is compromised.The encryption is working and the new encryption agreement is not required. It may result in connection errors! No comment provided by engineer. - - The future of messaging - The future of messaging + + The first network where you own +your contacts and groups. + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -9084,6 +9131,11 @@ It can happen because of some bug or when the connection is compromised.The old database was not removed during the migration, it can be deleted. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. The same conditions will apply to operator **%@**. @@ -9129,6 +9181,16 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. These conditions will also apply for: **%@**. @@ -10023,6 +10085,11 @@ To connect, please ask your contact to create another connection link and check When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. No comment provided by engineer. + + Why SimpleX is built. + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10285,6 +10352,15 @@ Repeat join request? You can't send messages! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. You connected to the channel via this relay link. @@ -10295,11 +10371,6 @@ Repeat join request? You could not be verified; please try again. No comment provided by engineer. - - You decide who can connect. - You decide who can connect. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10367,6 +10438,11 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. You will be able to send messages **only after your request is accepted**. @@ -10507,6 +10583,11 @@ Repeat connection request? Your contacts will remain connected. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Your credentials may be sent unencrypted. @@ -10527,6 +10608,11 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + Your network + No comment provided by engineer. + Your preferences Your preferences diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 07004c5432..acd97f2947 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1127,11 +1127,6 @@ swipe action Responder llamada No comment provided by engineer. - - Anybody can host servers. - Cualquiera puede alojar servidores. - No comment provided by engineer. - App build: %@ Compilación app: %@ @@ -1337,6 +1332,21 @@ swipe action Hash de mensaje incorrecto No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Se libre en tu red. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Porque hemos destruido el poder de saber quien eres. De manera que tu poder nunca se pueda arrebatar. + No comment provided by engineer. + Better calls Llamadas mejoradas @@ -1522,15 +1532,6 @@ swipe action Mediante perfil (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Al usar SimpleX Chat, aceptas: -- enviar únicamente contenido legal en los grupos públicos. -- respetar a los demás usuarios – spam prohibido. - No comment provided by engineer. - Call already ended! ¡La llamada ha terminado! @@ -2079,11 +2080,6 @@ chat toolbar Configurar servidores No comment provided by engineer. - - Configure server operators - Configurar operadores de servidores - No comment provided by engineer. - Confirm Confirmar @@ -2705,11 +2701,6 @@ This is your own one-time link! Informe debug No comment provided by engineer. - - Decentralized - Descentralizada - No comment provided by engineer. - Decode link Decodificar enlace @@ -3569,6 +3560,10 @@ chat item action ¡Introduce la contraseña arriba para mostrar! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… Introduce el nombre del servidor… @@ -4480,6 +4475,10 @@ Error: %2$@ Las menciones ahora se notifican. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! ¡Buenas tardes! @@ -4755,11 +4754,6 @@ Error: %2$@ Inmediatamente No comment provided by engineer. - - Immune to spam - Inmune a spam y abuso - No comment provided by engineer. - Import Importar @@ -5656,16 +5650,15 @@ This is your link for group %@! Los mensajes, archivos y llamadas están protegidos mediante **cifrado de extremo a extremo resistente a tecnología cuántica** con secreto perfecto hacía adelante, repudio y recuperación tras ataque. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Migrar dispositivo No comment provided by engineer. - - Migrate from another device - Migrar desde otro dispositivo - No comment provided by engineer. - Migrate here Migrar aquí @@ -5781,6 +5774,11 @@ This is your link for group %@! Nombre swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Servidores y Redes @@ -5815,6 +5813,11 @@ This is your link for group %@! Operador de red No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Configuración de red @@ -5934,6 +5937,11 @@ This is your link for group %@! No No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6098,15 +6106,20 @@ This is your link for group %@! Ningún chat sin leer No comment provided by engineer. - - No user identifiers. - Sin identificadores de usuario. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nadie monitorizaba tus conversaciones. Nadie registraba tus ubicaciones. La privacidad nunca fue un lujo, era la manera de vivir. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No un candado mejorado en la puerta de otro. No un terrateniente que respeta tu privacidad pero sigue guardando un registro de tus visitantes. Tu no eres el invitado. Estás en tu casa y ningún rey podrá entrar. Tu eres el soberano. + No comment provided by engineer. + Not all relays connected Hay servidores no conectados @@ -6188,6 +6201,10 @@ new chat action Base de datos antigua No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Enlace de invitación de un solo uso @@ -6435,6 +6452,13 @@ alert button Servidor del operador alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file O importa desde un archivo @@ -6762,18 +6786,12 @@ Error: %@ Política de privacidad y condiciones de uso. No comment provided by engineer. - - Privacy redefined - Privacidad redefinida - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. + + Private and secure messaging. No comment provided by engineer. @@ -8186,6 +8204,14 @@ chat item action La configuración ha sido modificada. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Dar forma a las imágenes de perfil @@ -8972,9 +8998,9 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. El cifrado funciona y un cifrado nuevo no es necesario. ¡Podría dar lugar a errores de conexión! No comment provided by engineer. - - The future of messaging - La nueva generación de mensajería privada + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -9012,6 +9038,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. La base de datos antigua no se eliminó durante la migración, puede eliminarse. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + La libertad más antigua del ser humano, la de hablar con otra persona sin ser observado, materializada sobre una infraestructura que no puede traicionarla. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Las mismas condiciones se aplicarán al operador **%@**. @@ -9057,6 +9088,16 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Temas No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Después pasamos a internet y cada plataforma pedía una parte de tí: tu nombre, tu número, tus amistades. Aceptamos que el precio de hablar con los demás es informar a alguien de quién es interlocutor. Cada generación, personas y tecnología, ha funcionado así: teléfono, email, mensajería, redes sociales. Parecía el único camino. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Existe otro camino. Una red sin números de teléfono. Sin nombres de usuario. Sin cuentas. Sin identificadores de ningún tipo. Una red que conecta las personas y entrega mensajes cifrados sin saber quien está conectado. + No comment provided by engineer. + These conditions will also apply for: **%@**. Estas condiciones también se aplican para: **%@**. @@ -9947,6 +9988,10 @@ Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10209,6 +10254,12 @@ Repeat join request? ¡No puedes enviar mensajes! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. Te conectaste al canal mediante este enlace de servidor. @@ -10219,11 +10270,6 @@ Repeat join request? No has podido ser autenticado. Inténtalo de nuevo. No comment provided by engineer. - - You decide who can connect. - Tu decides quién se conecta. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10291,6 +10337,11 @@ Repeat connection request? Deberías recibir notificaciones. token info + + You were born without an account + Naciste sin una cuenta. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Podrás enviar mensajes **después de que tu solicitud sea aceptada**. @@ -10431,6 +10482,11 @@ Repeat connection request? Tus contactos permanecerán conectados. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Tus conversaciones te pertenecen, tal como ha sido siempre antes de la llegada de internet. Tu red no es un lugar que visitas. Es un lugar que has creado, te pertenece y nadie te la podrá quitar, ya sea pública o privada. + No comment provided by engineer. + Your credentials may be sent unencrypted. Tus credenciales podrían ser enviadas sin cifrar. @@ -10451,6 +10507,10 @@ Repeat connection request? Mi grupo No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Mis preferencias diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 93e0c2e3a7..89c71ec5ca 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -1033,11 +1033,6 @@ swipe action Vastaa puheluun No comment provided by engineer. - - Anybody can host servers. - Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia. - No comment provided by engineer. - App build: %@ Sovellusversio: %@ @@ -1224,6 +1219,19 @@ swipe action Virheellinen viestin tarkiste No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls No comment provided by engineer. @@ -1379,12 +1387,6 @@ swipe action Chat-profiilin mukaan (oletus) tai [yhteyden mukaan](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! Puhelu on jo päättynyt! @@ -1867,10 +1869,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm Vahvista @@ -2429,11 +2427,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - Hajautettu - No comment provided by engineer. - Decode link relay test step @@ -3218,6 +3211,10 @@ chat item action Kirjoita yllä oleva salasana näyttääksesi! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4031,6 +4028,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4290,11 +4291,6 @@ Error: %2$@ Heti No comment provided by engineer. - - Immune to spam - Immuuni roskapostille ja väärinkäytöksille - No comment provided by engineer. - Import Tuo @@ -5104,12 +5100,12 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device + + Migrate device No comment provided by engineer. @@ -5218,6 +5214,11 @@ This is your link for group %@! Nimi swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Verkko ja palvelimet @@ -5247,6 +5248,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Verkkoasetukset @@ -5354,6 +5360,11 @@ This is your link for group %@! Ei No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -5497,15 +5508,18 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -5578,6 +5592,10 @@ new chat action Vanha tietokanta No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Kertakutsulinkki @@ -5800,6 +5818,13 @@ alert button Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -6091,17 +6116,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - Yksityisyys uudelleen määritettynä - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -7369,6 +7389,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -8069,9 +8097,9 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin! No comment provided by engineer. - - The future of messaging - Seuraavan sukupolven yksityisviestit + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8106,6 +8134,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8145,6 +8177,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -8936,6 +8976,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Kun jaat inkognitoprofiilin jonkun kanssa, tätä profiilia käytetään ryhmissä, joihin tämä sinut kutsuu. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -9166,6 +9210,12 @@ Repeat join request? Et voi lähettää viestejä! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9175,11 +9225,6 @@ Repeat join request? Sinua ei voitu todentaa; yritä uudelleen. No comment provided by engineer. - - You decide who can connect. - Kimin bağlanabileceğine siz karar verirsiniz. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9241,6 +9286,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9371,6 +9420,10 @@ Repeat connection request? Kontaktisi pysyvät yhdistettyinä. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9389,6 +9442,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Asetuksesi diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 1de6a95c2b..7d5864718e 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1117,11 +1117,6 @@ swipe action Répondre à l'appel No comment provided by engineer. - - Anybody can host servers. - N'importe qui peut heberger un serveur. - No comment provided by engineer. - App build: %@ Build de l'app : %@ @@ -1325,6 +1320,19 @@ swipe action Mauvais hash de message No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Appels améliorés @@ -1503,15 +1511,6 @@ swipe action Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - En utilisant SimpleX Chat, vous acceptez de : -- n'envoyer que du contenu légal dans les groupes publics. -- respecter les autres utilisateurs - pas de spam. - No comment provided by engineer. - Call already ended! Appel déjà terminé ! @@ -2037,11 +2036,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Configurer les opérateurs de serveur - No comment provided by engineer. - Confirm Confirmer @@ -2656,11 +2650,6 @@ Il s'agit de votre propre lien unique ! Livraison de débogage No comment provided by engineer. - - Decentralized - Décentralisé - No comment provided by engineer. - Decode link relay test step @@ -3504,6 +3493,10 @@ chat item action Entrez ci-dessus le mot de passe pour afficher le profil ! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4394,6 +4387,10 @@ Erreur : %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Bonjour ! @@ -4662,11 +4659,6 @@ Erreur : %2$@ Immédiatement No comment provided by engineer. - - Immune to spam - Protégé du spam et des abus - No comment provided by engineer. - Import Importer @@ -5531,16 +5523,15 @@ Voici votre lien pour le groupe %@ ! Les messages, fichiers et appels sont protégés par un chiffrement **e2e résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Transférer l'appareil No comment provided by engineer. - - Migrate from another device - Transférer depuis un autre appareil - No comment provided by engineer. - Migrate here Transférer ici @@ -5654,6 +5645,11 @@ Voici votre lien pour le groupe %@ ! Nom swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Réseau et serveurs @@ -5688,6 +5684,11 @@ Voici votre lien pour le groupe %@ ! Opérateur de réseau No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Paramètres réseau @@ -5803,6 +5804,11 @@ Voici votre lien pour le groupe %@ ! Non No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -5957,15 +5963,18 @@ Voici votre lien pour le groupe %@ ! No unread chats No comment provided by engineer. - - No user identifiers. - Aucun identifiant d'utilisateur. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -6043,6 +6052,10 @@ new chat action Ancienne base de données No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Lien d'invitation unique @@ -6275,6 +6288,13 @@ alert button Serveur de l'opérateur alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Ou importer un fichier d'archive @@ -6592,17 +6612,12 @@ Erreur : %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - La vie privée redéfinie - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -7958,6 +7973,14 @@ chat item action Les paramètres ont été modifiés. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Images de profil modelable @@ -8708,9 +8731,9 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion ! No comment provided by engineer. - - The future of messaging - La nouvelle génération de messagerie privée + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8747,6 +8770,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Les mêmes conditions s'appliquent à l'opérateur **%@**. @@ -8792,6 +8819,14 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Thèmes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Ces conditions s'appliquent également aux : **%@**. @@ -9650,6 +9685,10 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -9908,6 +9947,12 @@ Répéter la demande d'adhésion ? Vous ne pouvez pas envoyer de messages ! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9917,11 +9962,6 @@ Répéter la demande d'adhésion ? Vous n'avez pas pu être vérifié·e ; veuillez réessayer. No comment provided by engineer. - - You decide who can connect. - Vous choisissez qui peut se connecter. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9988,6 +10028,10 @@ Répéter la demande de connexion ? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -10122,6 +10166,10 @@ Répéter la demande de connexion ? Vos contacts resteront connectés. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Vos informations d'identification peuvent être envoyées non chiffrées. @@ -10141,6 +10189,10 @@ Répéter la demande de connexion ? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Vos préférences diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 7e52f853f2..44bf045166 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -1127,11 +1127,6 @@ swipe action Hívás fogadása No comment provided by engineer. - - Anybody can host servers. - Bárki üzemeltethet kiszolgálókat. - No comment provided by engineer. - App build: %@ Alkalmazás összeállítási száma: %@ @@ -1337,6 +1332,21 @@ swipe action Hibás az üzenet kivonata No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Legyen szabad a saját hálózatában. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe. + No comment provided by engineer. + Better calls Továbbfejlesztett hívásélmény @@ -1522,15 +1532,6 @@ swipe action A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - A SimpleX Chat használatával Ön elfogadja, hogy: -- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban. -- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek. - No comment provided by engineer. - Call already ended! A hívás már véget ért! @@ -2079,11 +2080,6 @@ chat toolbar Átjátszók konfigurálása No comment provided by engineer. - - Configure server operators - Kiszolgálóüzemeltetők beállítása - No comment provided by engineer. - Confirm Megerősítés @@ -2705,11 +2701,6 @@ Ez a saját egyszer használható meghívója! Kézbesítési hibák felderítése No comment provided by engineer. - - Decentralized - Decentralizált - No comment provided by engineer. - Decode link Hivatkozás dekódolása @@ -3569,6 +3560,10 @@ chat item action Adja meg a jelszót fentebb a megjelenítéshez! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… Adja meg az átjátszó nevét… @@ -4480,6 +4475,10 @@ Hiba: %2$@ Kapjon értesítést, ha megemlítik. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Jó napot! @@ -4755,11 +4754,6 @@ Hiba: %2$@ Azonnal No comment provided by engineer. - - Immune to spam - Védett a kéretlen tartalmakkal szemben - No comment provided by engineer. - Import Importálás @@ -5656,16 +5650,15 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Eszköz átköltöztetése No comment provided by engineer. - - Migrate from another device - Átköltöztetés egy másik eszközről - No comment provided by engineer. - Migrate here Átköltöztetés ide @@ -5781,6 +5774,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Név swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Hálózat és kiszolgálók @@ -5815,6 +5813,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Hálózatüzemeltető No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Hálózati beállítások @@ -5934,6 +5937,11 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nem No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6098,15 +6106,20 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz! Nincsenek olvasatlan csevegések No comment provided by engineer. - - No user identifiers. - Nincsenek felhasználói azonosítók. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide - Ön itt szuverén. + No comment provided by engineer. + Not all relays connected Nem minden átjátszó kapcsolódott @@ -6188,6 +6201,10 @@ new chat action Régi adatbázis No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Egyszer használható meghívó @@ -6435,6 +6452,13 @@ alert button Kiszolgáló-üzemeltető alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Vagy archívumfájl importálása @@ -6762,18 +6786,12 @@ Hiba: %@ Adatvédelmi szabályzat és felhasználási feltételek. No comment provided by engineer. - - Privacy redefined - Újraértelmezett adatvédelem - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára. + + Private and secure messaging. No comment provided by engineer. @@ -8186,6 +8204,14 @@ chat item action A beállítások módosultak. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Profilkép alakzata @@ -8972,9 +8998,9 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! No comment provided by engineer. - - The future of messaging - Az üzenetváltás jövője + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -9012,6 +9038,11 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + A legrégebbi emberi szabadság - beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének - olyan infrastruktúrán alapul, amely nem tudja elárulni. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**. @@ -9057,6 +9088,16 @@ Ez valamilyen hiba vagy sérült kapcsolat esetén fordulhat elő. Témák No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot - nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött - telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá. + No comment provided by engineer. + These conditions will also apply for: **%@**. Ezek a feltételek lesznek elfogadva a következő számára is: **%@**. @@ -9947,6 +9988,10 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso Ha egy inkognitóprofilt oszt meg valamelyik partnerével, a rendszer ezt az inkognitóprofilt fogja használni azokban a csoportokban, ahová az adott partnere meghívja Önt. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi Wi-Fi @@ -10209,6 +10254,12 @@ Megismétli a csatlakozási kérést? Ön nem tud üzeneteket küldeni! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. Ön ezen az átjátszóhivatkozáson keresztül kapcsolódott a csatornához. @@ -10219,11 +10270,6 @@ Megismétli a csatlakozási kérést? Nem sikerült ellenőrizni; próbálja meg újra. No comment provided by engineer. - - You decide who can connect. - Ön dönti el, hogy kivel beszélget. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10291,6 +10337,11 @@ Megismétli a kapcsolódási kérést? Ön megkapja az értesítéseket. token info + + You were born without an account + Fiók nélkül születtünk. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Csak azután tud üzeneteket küldeni, **miután a kérését elfogadták**. @@ -10431,6 +10482,11 @@ Megismétli a kapcsolódási kérést? A partnereivel továbbra is kapcsolatban marad. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános. + No comment provided by engineer. + Your credentials may be sent unencrypted. A hitelesítési adatai titkosítatlanul is elküldhetők. @@ -10451,6 +10507,10 @@ Megismétli a kapcsolódási kérést? Saját csoport No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Beállítások diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 7cf7b418a8..8b40a78c98 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1127,11 +1127,6 @@ swipe action Rispondi alla chiamata No comment provided by engineer. - - Anybody can host servers. - Chiunque può installare i server. - No comment provided by engineer. - App build: %@ Build dell'app: %@ @@ -1337,6 +1332,21 @@ swipe action Hash del messaggio errato No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Vivi libero nella tua rete. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Perché abbiamo distrutto il potere di sapere chi sei. In modo che il tuo potere non possa mai esserti sottratto. + No comment provided by engineer. + Better calls Chiamate migliorate @@ -1522,15 +1532,6 @@ swipe action Per profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Usando SimpleX Chat accetti di: -- inviare solo contenuto legale nei gruppi pubblici. -- rispettare gli altri utenti - niente spam. - No comment provided by engineer. - Call already ended! Chiamata già terminata! @@ -2079,11 +2080,6 @@ chat toolbar Configura i relay No comment provided by engineer. - - Configure server operators - Configura gli operatori dei server - No comment provided by engineer. - Confirm Conferma @@ -2705,11 +2701,6 @@ Questo è il tuo link una tantum! Debug della consegna No comment provided by engineer. - - Decentralized - Decentralizzato - No comment provided by engineer. - Decode link Decodifica il link @@ -3569,6 +3560,10 @@ chat item action Inserisci la password sopra per mostrare! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… Inserisci il nome del relay… @@ -4480,6 +4475,10 @@ Errore: %2$@ Ricevi una notifica quando menzionato. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Buon pomeriggio! @@ -4755,11 +4754,6 @@ Errore: %2$@ Immediatamente No comment provided by engineer. - - Immune to spam - Immune a spam e abusi - No comment provided by engineer. - Import Importa @@ -5656,16 +5650,15 @@ Questo è il tuo link per il gruppo %@! I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente alla quantistica** con perfect forward secrecy, ripudio e recupero da intrusione. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Migra dispositivo No comment provided by engineer. - - Migrate from another device - Migra da un altro dispositivo - No comment provided by engineer. - Migrate here Migra qui @@ -5781,6 +5774,11 @@ Questo è il tuo link per il gruppo %@! Nome swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Rete e server @@ -5815,6 +5813,11 @@ Questo è il tuo link per il gruppo %@! Operatore di rete No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Impostazioni di rete @@ -5934,6 +5937,11 @@ Questo è il tuo link per il gruppo %@! No No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6098,15 +6106,20 @@ Questo è il tuo link per il gruppo %@! Nessuna chat non letta No comment provided by engineer. - - No user identifiers. - Nessun identificatore utente. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nessuno monitorava le tue conversazioni. Nessuno disegnava una mappa delle tue posizioni. La privacy non era mai stata una caratteristica, era uno stile di vita. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Non una serratura migliore sulla porta di qualcun altro. Non un padrone di casa più gentile che rispetta la tua privacy, ma che continua a tenere traccia di tutti i visitatori. Non sei un ospite. Sei a casa tua. Nessun re può entrarvi: sei tu il sovrano. + No comment provided by engineer. + Not all relays connected Non tutti i relay sono connessi @@ -6188,6 +6201,10 @@ new chat action Database vecchio No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Link di invito una tantum @@ -6435,6 +6452,13 @@ alert button Server dell'operatore alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file O importa file archivio @@ -6762,18 +6786,12 @@ Errore: %@ Informativa sulla privacy e condizioni d'uso. No comment provided by engineer. - - Privacy redefined - Privacy ridefinita - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server. + + Private and secure messaging. No comment provided by engineer. @@ -8186,6 +8204,14 @@ chat item action Le impostazioni sono state cambiate. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Forma delle immagini del profilo @@ -8972,9 +8998,9 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione! No comment provided by engineer. - - The future of messaging - La nuova generazione di messaggistica privata + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -9012,6 +9038,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + La più antica libertà umana, parlare con un'altra persona senza essere osservati, si basa su un'infrastruttura che non può tradirla. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Le stesse condizioni si applicheranno all'operatore **%@**. @@ -9057,6 +9088,16 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Temi No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Poi ci siamo trasferiti online e ogni piattaforma ha chiesto un pezzo di noi: il nome, il numero, gli amici. Abbiamo accettato che il prezzo da pagare per comunicare con gli altri fosse quello di far sapere a qualcuno con chi parliamo. Ogni generazione, sia di persone che di tecnologia, ha funzionato così: telefono, email, messenger, social media. Sembrava l'unico modo possibile. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + C'è un'altra via. Una rete senza numeri di telefono. Senza nomi utente. Senza account. Senza identificatori utente di alcun tipo. Una rete che connette le persone e trasferisce messaggi crittografati senza sapere chi è connesso. + No comment provided by engineer. + These conditions will also apply for: **%@**. Queste condizioni si applicheranno anche per: **%@**. @@ -9947,6 +9988,10 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Quando condividi un profilo in incognito con qualcuno, questo profilo verrà utilizzato per i gruppi a cui ti invitano. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10209,6 +10254,12 @@ Ripetere la richiesta di ingresso? Non puoi inviare messaggi! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. Ti sei connesso/a al canale attraverso questo link del relay. @@ -10219,11 +10270,6 @@ Ripetere la richiesta di ingresso? Non è stato possibile verificarti, riprova. No comment provided by engineer. - - You decide who can connect. - Sei tu a decidere chi può connettersi. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10291,6 +10337,11 @@ Ripetere la richiesta di connessione? Dovresti ricevere le notifiche. token info + + You were born without an account + Sei nato senza un account. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Potrai inviare messaggi **solo dopo che la tua richiesta verrà accettata**. @@ -10431,6 +10482,11 @@ Ripetere la richiesta di connessione? I tuoi contatti resteranno connessi. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Le tue conversazioni appartengono a te, come è sempre stato prima dell'avvento di internet. La rete non è un luogo che visiti. È un luogo che crei e possiedi. E nessuno può portartelo via, che tu lo renda privato o pubblico. + No comment provided by engineer. + Your credentials may be sent unencrypted. Le credenziali potrebbero essere inviate in chiaro. @@ -10451,6 +10507,10 @@ Ripetere la richiesta di connessione? Il tuo gruppo No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Le tue preferenze diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 3498c8757f..6dcedf7d5b 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -1097,11 +1097,6 @@ swipe action 通話に応答 No comment provided by engineer. - - Anybody can host servers. - プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。 - No comment provided by engineer. - App build: %@ アプリのビルド: %@ @@ -1295,6 +1290,19 @@ swipe action メッセージのハッシュ値問題 No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls No comment provided by engineer. @@ -1451,12 +1459,6 @@ swipe action チャット プロファイル経由 (デフォルト) または [接続経由](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! 通話は既に終了してます! @@ -1951,10 +1953,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm 確認 @@ -2527,11 +2525,6 @@ This is your own one-time link! 配信のデバッグ No comment provided by engineer. - - Decentralized - 分散型 - No comment provided by engineer. - Decode link relay test step @@ -3319,6 +3312,10 @@ chat item action 上にパスワードを入力すると表示されます! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4132,6 +4129,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4391,11 +4392,6 @@ Error: %2$@ 即座に No comment provided by engineer. - - Immune to spam - スパムや悪質送信を防止 - No comment provided by engineer. - Import 読み込む @@ -5206,13 +5202,12 @@ This is your link for group %@! メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。 No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device - 別の端末から移行 + + Migrate device No comment provided by engineer. @@ -5321,6 +5316,11 @@ This is your link for group %@! 名前 swipe action + + Network +commitments + No comment provided by engineer. + Network & servers ネットワークとサーバ @@ -5350,6 +5350,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings ネットワーク設定 @@ -5458,6 +5463,11 @@ This is your link for group %@! いいえ No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -5601,15 +5611,18 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - 世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。 + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -5682,6 +5695,10 @@ new chat action 古いデータベース No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link 使い捨ての招待リンク @@ -5905,6 +5922,13 @@ alert button Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -6196,17 +6220,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - プライバシーの基準を新境地に - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -7467,6 +7486,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -8168,9 +8195,9 @@ It can happen because of some bug or when the connection is compromised.暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります! No comment provided by engineer. - - The future of messaging - 次世代のプライバシー・メッセンジャー + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8205,6 +8232,10 @@ It can happen because of some bug or when the connection is compromised.古いデータベースは移行時に削除されなかったので、削除することができます。 No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8244,6 +8275,14 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -9034,6 +9073,10 @@ To connect, please ask your contact to create another connection link and check 連絡相手にシークレットモードのプロフィールを共有すると、その連絡相手に招待されたグループでも同じプロフィールが使われます。 No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -9265,6 +9308,12 @@ Repeat join request? メッセージを送信できませんでした! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9274,11 +9323,6 @@ Repeat join request? 確認できませんでした。 もう一度お試しください。 No comment provided by engineer. - - You decide who can connect. - あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。 - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9340,6 +9384,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9470,6 +9518,10 @@ Repeat connection request? 連絡先は接続されたままになります。 No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9488,6 +9540,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences あなたの設定 diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 6c5316a2b1..426be0eebc 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1113,11 +1113,6 @@ swipe action Beantwoord oproep No comment provided by engineer. - - Anybody can host servers. - Iedereen kan servers hosten. - No comment provided by engineer. - App build: %@ App build: %@ @@ -1322,6 +1317,19 @@ swipe action Onjuiste bericht hash No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Betere gesprekken @@ -1500,15 +1508,6 @@ swipe action Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Door SimpleX Chat te gebruiken, gaat u ermee akkoord: -- alleen legale content te versturen in openbare groepen. -- andere gebruikers te respecteren – geen spam. - No comment provided by engineer. - Call already ended! Oproep al beëindigd! @@ -2037,11 +2036,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Serveroperators configureren - No comment provided by engineer. - Confirm Bevestigen @@ -2656,11 +2650,6 @@ Dit is uw eigen eenmalige link! Foutopsporing bezorging No comment provided by engineer. - - Decentralized - Gedecentraliseerd - No comment provided by engineer. - Decode link relay test step @@ -3505,6 +3494,10 @@ chat item action Voer hier boven het wachtwoord in om weer te geven! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4400,6 +4393,10 @@ Fout: %2$@ Ontvang een melding als u vermeld wordt. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Goedemiddag! @@ -4671,11 +4668,6 @@ Fout: %2$@ Onmiddellijk No comment provided by engineer. - - Immune to spam - Immuun voor spam en misbruik - No comment provided by engineer. - Import Importeren @@ -5556,16 +5548,15 @@ Dit is jouw link voor groep %@! Berichten, bestanden en oproepen worden beschermd door **kwantumbestendige e2e encryptie** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Apparaat migreren No comment provided by engineer. - - Migrate from another device - Migreer vanaf een ander apparaat - No comment provided by engineer. - Migrate here Migreer hierheen @@ -5681,6 +5672,11 @@ Dit is jouw link voor groep %@! Naam swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Netwerk & servers @@ -5715,6 +5711,11 @@ Dit is jouw link voor groep %@! Netwerkbeheerder No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Netwerk instellingen @@ -5832,6 +5833,11 @@ Dit is jouw link voor groep %@! Nee No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -5993,15 +5999,18 @@ Dit is jouw link voor groep %@! Geen ongelezen chats No comment provided by engineer. - - No user identifiers. - Geen gebruikers-ID's. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -6082,6 +6091,10 @@ new chat action Oude database No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Eenmalige uitnodiging link @@ -6317,6 +6330,13 @@ alert button Operatorserver alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Of importeer archiefbestand @@ -6640,18 +6660,12 @@ Fout: %@ Privacybeleid en gebruiksvoorwaarden. No comment provided by engineer. - - Privacy redefined - Privacy opnieuw gedefinieerd - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. + + Private and secure messaging. No comment provided by engineer. @@ -8032,6 +8046,14 @@ chat item action Instellingen zijn gewijzigd. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Vorm profiel afbeeldingen @@ -8788,9 +8810,9 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De versleuteling werkt en de nieuwe versleutelingsovereenkomst is niet vereist. Dit kan leiden tot verbindingsfouten! No comment provided by engineer. - - The future of messaging - De volgende generatie privéberichten + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8827,6 +8849,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Dezelfde voorwaarden gelden voor operator **%@**. @@ -8872,6 +8898,14 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Thema's No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Deze voorwaarden zijn ook van toepassing op: **%@**. @@ -9739,6 +9773,10 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi Wifi @@ -9998,6 +10036,12 @@ Deelnameverzoek herhalen? Je kunt geen berichten versturen! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -10007,11 +10051,6 @@ Deelnameverzoek herhalen? U kon niet worden geverifieerd; probeer het opnieuw. No comment provided by engineer. - - You decide who can connect. - Jij bepaalt wie er verbinding mag maken. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10079,6 +10118,10 @@ Verbindingsverzoek herhalen? U zou meldingen moeten ontvangen. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -10213,6 +10256,10 @@ Verbindingsverzoek herhalen? Uw contacten blijven verbonden. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Uw inloggegevens worden mogelijk niet-versleuteld verzonden. @@ -10232,6 +10279,10 @@ Verbindingsverzoek herhalen? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Jouw voorkeuren diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 65a5c638fe..8aa9d4d9ff 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1118,11 +1118,6 @@ swipe action Odbierz połączenie No comment provided by engineer. - - Anybody can host servers. - Każdy może hostować serwery. - No comment provided by engineer. - App build: %@ Kompilacja aplikacji: %@ @@ -1328,6 +1323,21 @@ swipe action Zły hash wiadomości No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Ciesz się swobodą w swojej sieci. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Ponieważ zniszczyliśmy moc pozwalającą poznać, kim jesteś. Więc twoja moc nigdy nie będzie Ci odebrana. + No comment provided by engineer. + Better calls Lepsze połączenia @@ -1511,15 +1521,6 @@ swipe action Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Korzystając z SimpleX Chat, zgadzasz się: -- wysyłać tylko legalne treści w grupach publicznych. -- szanować innych użytkowników – nie spamować. - No comment provided by engineer. - Call already ended! Połączenie już zakończone! @@ -2050,11 +2051,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Skonfiguruj operatorów serwerów - No comment provided by engineer. - Confirm Potwierdź @@ -2673,11 +2669,6 @@ To jest twój jednorazowy link! Dostarczenie debugowania No comment provided by engineer. - - Decentralized - Zdecentralizowane - No comment provided by engineer. - Decode link relay test step @@ -3528,6 +3519,10 @@ chat item action Wprowadź hasło powyżej, aby pokazać! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4434,6 +4429,10 @@ Błąd: %2$@ Otrzymuj powiadomienia, gdy ktoś wspomni o Tobie. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Dzień dobry! @@ -4708,11 +4707,6 @@ Błąd: %2$@ Natychmiast No comment provided by engineer. - - Immune to spam - Odporność na spam i nadużycia - No comment provided by engineer. - Import Importuj @@ -5603,16 +5597,15 @@ To jest twój link do grupy %@! Wiadomości, pliki i połączenia są chronione przez **kwantowo odporne szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Zmigruj urządzenie No comment provided by engineer. - - Migrate from another device - Zmigruj z innego urządzenia - No comment provided by engineer. - Migrate here Zmigruj tutaj @@ -5728,6 +5721,11 @@ To jest twój link do grupy %@! Nazwa swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Sieć i serwery @@ -5762,6 +5760,11 @@ To jest twój link do grupy %@! Operator sieci No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Ustawienia sieci @@ -5880,6 +5883,11 @@ To jest twój link do grupy %@! Nie No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6042,15 +6050,20 @@ To jest twój link do grupy %@! Brak nieprzeczytanych czatów No comment provided by engineer. - - No user identifiers. - Brak identyfikatorów użytkownika. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Nikt nie śledził twoich rozmów. Nikt nie rysował mapy miejsc, w których byłeś. Prywatność nigdy nie była funkcją - była sposobem na życie. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Nie chodzi o lepszy zamek w drzwiach kogoś innego. Nie chodzi o milszego właściciela, który szanuje twoją prywatność, ale nadal prowadzi rejestr wszystkich odwiedzających. Nie jesteś gościem. Jesteś w domu. Żaden król nie może do niego wejść - jesteś suwerenem. + No comment provided by engineer. + Not all relays connected alert title @@ -6131,6 +6144,10 @@ new chat action Stara baza danych No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Jednorazowy link zaproszenia @@ -6376,6 +6393,13 @@ alert button Serwer Operatora alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Lub zaimportuj plik archiwalny @@ -6699,18 +6723,12 @@ Błąd: %@ Polityka prywatności i warunki korzystania. No comment provided by engineer. - - Privacy redefined - Redefinicja prywatności - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów. + + Private and secure messaging. No comment provided by engineer. @@ -8109,6 +8127,14 @@ chat item action Ustawienia zostały zmienione. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Kształtuj obrazy profilowe @@ -8876,9 +8902,9 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu! No comment provided by engineer. - - The future of messaging - Następna generacja prywatnych wiadomości + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8916,6 +8942,11 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Stara baza danych nie została usunięta podczas migracji, można ją usunąć. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Najstarsza ludzka wolność - możliwość rozmowy z inną osobą bez bycia obserwowanym - opiera się na infrastrukturze, która nie może jej zdradzić. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Te same warunki będą miały zastosowanie do operatora **%@**. @@ -8961,6 +8992,16 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Motywy No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Następnie przenieśliśmy się do sieci, a każda platforma prosiła o podanie danych osobowych - imienia i nazwiska, numeru telefonu, znajomych. Zaakceptowaliśmy fakt, że ceną za możliwość komunikowania się z innymi jest ujawnienie komuś, z kim rozmawiamy. Tak było w przypadku każdego pokolenia, ludzi i technologii - telefonu, poczty elektronicznej, komunikatorów, mediów społecznościowych. Wydawało się to jedyną możliwą opcją. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Jest jeszcze inny sposób. Sieć bez numerów telefonów. Bez nazw użytkowników. Bez kont. Bez jakichkolwiek tożsamości użytkowników. Sieć, która łączy ludzi i przesyła zaszyfrowane wiadomości, nie wiedząc, kto jest podłączony. + No comment provided by engineer. + These conditions will also apply for: **%@**. Warunki te będą miały również zastosowanie w przypadku: **%@**. @@ -9842,6 +9883,10 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10103,6 +10148,12 @@ Powtórzyć prośbę dołączenia? Nie możesz wysyłać wiadomości! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -10112,11 +10163,6 @@ Powtórzyć prośbę dołączenia? Nie można zweryfikować użytkownika; proszę spróbować ponownie. No comment provided by engineer. - - You decide who can connect. - Ty decydujesz, kto może się połączyć. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10184,6 +10230,11 @@ Powtórzyć prośbę połączenia? Powinieneś otrzymywać powiadomienia. token info + + You were born without an account + Urodziłeś się bez konta. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Będziesz mógł wysyłać wiadomości **dopiero po zaakceptowaniu Twojej prośby**. @@ -10322,6 +10373,11 @@ Powtórzyć prośbę połączenia? Twoje kontakty pozostaną połączone. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Twoje rozmowy należą do Ciebie, tak jak zawsze było przed pojawieniem się Internetu. Sieć nie jest miejscem, które odwiedzasz. Jest miejscem, które tworzysz i które należy do Ciebie. Nikt nie może Ci tego odebrać, niezależnie od tego, czy jest to miejsce prywatne, czy publiczne. + No comment provided by engineer. + Your credentials may be sent unencrypted. Twoje poświadczenia mogą zostać wysłane niezaszyfrowane. @@ -10342,6 +10398,10 @@ Powtórzyć prośbę połączenia? Twoja grupa No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Twoje preferencje diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index a9f194b609..ae1e9f411a 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -1117,11 +1117,6 @@ swipe action Принять звонок No comment provided by engineer. - - Anybody can host servers. - Кто угодно может запустить сервер. - No comment provided by engineer. - App build: %@ Сборка приложения: %@ @@ -1326,6 +1321,21 @@ swipe action Ошибка хэш сообщения No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + Будь свободен в своей сети. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + Потому что мы разрушили саму возможность узнать, кто вы. Чтобы вашу свободу невозможно было отнять. + No comment provided by engineer. + Better calls Улучшенные звонки @@ -1509,15 +1519,6 @@ swipe action По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Используя SimpleX Chat, Вы согласны: -- отправлять только законные сообщения в публичных группах. -- уважать других пользователей – не отправлять спам. - No comment provided by engineer. - Call already ended! Звонок уже завершен! @@ -2048,11 +2049,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Настроить операторов серверов - No comment provided by engineer. - Confirm Подтвердить @@ -2670,11 +2666,6 @@ This is your own one-time link! Отладка доставки No comment provided by engineer. - - Decentralized - Децентрализованный - No comment provided by engineer. - Decode link relay test step @@ -3523,6 +3514,10 @@ chat item action Введите пароль выше, чтобы раскрыть! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4428,6 +4423,10 @@ Error: %2$@ Уведомления, когда Вас упомянули. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Добрый день! @@ -4700,11 +4699,6 @@ Error: %2$@ Сразу No comment provided by engineer. - - Immune to spam - Защищен от спама - No comment provided by engineer. - Import Импортировать @@ -5591,16 +5585,15 @@ This is your link for group %@! Сообщения, файлы и звонки защищены **квантово-устойчивым end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Мигрировать устройство No comment provided by engineer. - - Migrate from another device - Миграция с другого устройства - No comment provided by engineer. - Migrate here Мигрировать сюда @@ -5716,6 +5709,11 @@ This is your link for group %@! Имя swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Сеть и серверы @@ -5750,6 +5748,11 @@ This is your link for group %@! Оператор сети No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Настройки сети @@ -5868,6 +5871,11 @@ This is your link for group %@! Нет No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6030,15 +6038,20 @@ This is your link for group %@! Нет непрочитанных чатов No comment provided by engineer. - - No user identifiers. - Без идентификаторов пользователей. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + Никто не отслеживал ваши разговоры. Никто не составлял карту ваших перемещений. Конфиденциальность не была функцией - это был образ жизни. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + Не более надёжный замок на чужой двери. Не более вежливый хозяин, который уважает вашу частную жизнь, но всё равно ведёт учёт всех посетителей. Вы не гость. Вы у себя дома. Ни один король не войдёт в ваш дом - вы суверенны. + No comment provided by engineer. + Not all relays connected alert title @@ -6119,6 +6132,10 @@ new chat action Предыдущая версия данных чата No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Одноразовая ссылка @@ -6364,6 +6381,13 @@ alert button Сервер оператора alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Или импортировать файл архива @@ -6687,18 +6711,12 @@ Error: %@ Политика конфиденциальности и условия использования. No comment provided by engineer. - - Privacy redefined - Более конфиденциальный - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. + + Private and secure messaging. No comment provided by engineer. @@ -8091,6 +8109,14 @@ chat item action Настройки были изменены. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Форма картинок профилей @@ -8858,9 +8884,9 @@ It can happen because of some bug or when the connection is compromised.Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения! No comment provided by engineer. - - The future of messaging - Будущее коммуникаций + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8898,6 +8924,11 @@ It can happen because of some bug or when the connection is compromised.Предыдущая версия данных чата не удалена при перемещении, её можно удалить. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + Древнейшая человеческая свобода - говорить с другим человеком без слежки - построенная на инфраструктуре, которая не может её предать. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Те же самые условия будут приняты для оператора **%@**. @@ -8943,6 +8974,16 @@ It can happen because of some bug or when the connection is compromised.Темы No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + Потом мы вышли в интернет, и каждая платформа попросила частичку вас - ваше имя, ваш номер, ваших друзей. Мы смирились с тем, что за возможность общаться приходится отдавать информацию о том, с кем мы общаемся. Каждое поколение людей и технологий жило так - телефон, электронная почта, мессенджеры, социальные сети. Казалось, что другого пути нет. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Другой путь есть. Сеть без номеров телефонов. Без имён пользователей. Без аккаунтов. Без каких-либо идентификаторов пользователей. Сеть, которая соединяет людей и передаёт зашифрованные сообщения, не зная, кто с кем связан. + No comment provided by engineer. + These conditions will also apply for: **%@**. Эти условия также будут применены к: **%@**. @@ -9823,6 +9864,10 @@ To connect, please ask your contact to create another connection link and check Когда Вы соединены с контактом инкогнито, тот же самый инкогнито профиль будет использоваться для групп с этим контактом. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10084,6 +10129,12 @@ Repeat join request? Вы не можете отправлять сообщения! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -10093,11 +10144,6 @@ Repeat join request? Верификация не удалась; пожалуйста, попробуйте ещё раз. No comment provided by engineer. - - You decide who can connect. - Вы определяете, кто может соединиться. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10165,6 +10211,11 @@ Repeat connection request? Вы должны получать уведомления. token info + + You were born without an account + Вы родились без аккаунта. + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Вы сможете отправлять сообщения **только после того как Ваш запрос будет принят**. @@ -10303,6 +10354,11 @@ Repeat connection request? Ваши контакты сохранятся. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + Ваши разговоры принадлежат вам, как это всегда было до интернета. Сеть - это не место, куда вы приходите. Это место, которое вы создаёте и которым владеете. И никто не может это у вас отнять, делаете ли вы его конфиденциальным или публичным. + No comment provided by engineer. + Your credentials may be sent unencrypted. Ваши учетные данные могут быть отправлены в незашифрованном виде. @@ -10323,6 +10379,10 @@ Repeat connection request? Ваша группа No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Ваши предпочтения diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 8c45cb3e95..68dfc223c0 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -1025,11 +1025,6 @@ swipe action รับสาย No comment provided by engineer. - - Anybody can host servers. - โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้ - No comment provided by engineer. - App build: %@ รุ่นแอป: %@ @@ -1216,6 +1211,19 @@ swipe action แฮชข้อความไม่ดี No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls No comment provided by engineer. @@ -1371,12 +1379,6 @@ swipe action ตามโปรไฟล์แชท (ค่าเริ่มต้น) หรือ [โดยการเชื่อมต่อ](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (เบต้า) No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - No comment provided by engineer. - Call already ended! สิ้นสุดการโทรแล้ว! @@ -1859,10 +1861,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - No comment provided by engineer. - Confirm ยืนยัน @@ -2418,11 +2416,6 @@ This is your own one-time link! Debug delivery No comment provided by engineer. - - Decentralized - กระจายอำนาจแล้ว - No comment provided by engineer. - Decode link relay test step @@ -3204,6 +3197,10 @@ chat item action ใส่รหัสผ่านด้านบนเพื่อแสดง! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4016,6 +4013,10 @@ Error: %2$@ Get notified when mentioned. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! message preview @@ -4275,11 +4276,6 @@ Error: %2$@ โดยทันที No comment provided by engineer. - - Immune to spam - มีภูมิคุ้มกันต่อสแปมและการละเมิด - No comment provided by engineer. - Import นำเข้า @@ -5087,12 +5083,12 @@ This is your link for group %@! Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. No comment provided by engineer. - - Migrate device + + Migrate No comment provided by engineer. - - Migrate from another device + + Migrate device No comment provided by engineer. @@ -5200,6 +5196,11 @@ This is your link for group %@! ชื่อ swipe action + + Network +commitments + No comment provided by engineer. + Network & servers เครือข่ายและเซิร์ฟเวอร์ @@ -5229,6 +5230,11 @@ This is your link for group %@! Network operator No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings การตั้งค่าเครือข่าย @@ -5336,6 +5342,11 @@ This is your link for group %@! เลขที่ No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -5478,15 +5489,18 @@ This is your link for group %@! No unread chats No comment provided by engineer. - - No user identifiers. - แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -5559,6 +5573,10 @@ new chat action ฐานข้อมูลเก่า No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link ลิงก์คำเชิญแบบใช้ครั้งเดียว @@ -5779,6 +5797,13 @@ alert button Operator server alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file No comment provided by engineer. @@ -6070,17 +6095,12 @@ Error: %@ Privacy policy and conditions of use. No comment provided by engineer. - - Privacy redefined - นิยามความเป็นส่วนตัวใหม่ - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. + + Private and secure messaging. No comment provided by engineer. @@ -7344,6 +7364,14 @@ chat item action Settings were changed. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images No comment provided by engineer. @@ -8043,9 +8071,9 @@ It can happen because of some bug or when the connection is compromised.encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้! No comment provided by engineer. - - The future of messaging - การส่งข้อความส่วนตัวรุ่นต่อไป + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8080,6 +8108,10 @@ It can happen because of some bug or when the connection is compromised.ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้ No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8119,6 +8151,14 @@ It can happen because of some bug or when the connection is compromised.Themes No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. No comment provided by engineer. @@ -8906,6 +8946,10 @@ To connect, please ask your contact to create another connection link and check เมื่อคุณแชร์โปรไฟล์ที่ไม่ระบุตัวตนกับใครสักคน โปรไฟล์นี้จะใช้สำหรับกลุ่มที่พวกเขาเชิญคุณ No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi No comment provided by engineer. @@ -9136,6 +9180,12 @@ Repeat join request? คุณไม่สามารถส่งข้อความได้! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -9145,11 +9195,6 @@ Repeat join request? เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง. No comment provided by engineer. - - You decide who can connect. - ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -9210,6 +9255,10 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. No comment provided by engineer. @@ -9340,6 +9389,10 @@ Repeat connection request? ผู้ติดต่อของคุณจะยังคงเชื่อมต่ออยู่ No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. No comment provided by engineer. @@ -9358,6 +9411,10 @@ Repeat connection request? Your group No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences การตั้งค่าของคุณ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index ea9291a43a..1724c13c18 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1117,11 +1117,6 @@ swipe action Aramayı cevapla No comment provided by engineer. - - Anybody can host servers. - Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir. - No comment provided by engineer. - App build: %@ Uygulama sürümü: %@ @@ -1326,6 +1321,19 @@ swipe action Kötü mesaj karması No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Daha iyi aramalar @@ -1509,15 +1517,6 @@ swipe action Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - SimpleX Chat'i kullanarak şunları kabul etmiş olursunuz: -- herkese açık gruplarda yalnızca yasal içerik göndermek. -- diğer kullanıcılara saygı göstermek – spam yapmamak. - No comment provided by engineer. - Call already ended! Arama çoktan bitti! @@ -2048,11 +2047,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Sunucu operatörlerini yapılandır - No comment provided by engineer. - Confirm Onayla @@ -2670,11 +2664,6 @@ Bu senin kendi tek kullanımlık bağlantın! Hata ayıklama teslimatı No comment provided by engineer. - - Decentralized - Merkezi Olmayan - No comment provided by engineer. - Decode link relay test step @@ -3523,6 +3512,10 @@ chat item action Göstermek için yukarıdaki şifreyi gir! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4423,6 +4416,10 @@ Hata: %2$@ Bahsedildiğinde bildirim alın. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! İyi öğlenler! @@ -4695,11 +4692,6 @@ Hata: %2$@ Hemen No comment provided by engineer. - - Immune to spam - Spam ve kötüye kullanıma karşı bağışıklı - No comment provided by engineer. - Import İçe aktar @@ -5587,16 +5579,15 @@ Bu senin grup için bağlantın %@! Mesajlar, dosyalar ve aramalar **kuantum dirençli e2e şifreleme** ile mükemmel ileri gizlilik, inkar ve zorla girme kurtarma ile korunur. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Cihazı taşıma No comment provided by engineer. - - Migrate from another device - Başka bir cihazdan geçiş yapın - No comment provided by engineer. - Migrate here Buraya göç edin @@ -5712,6 +5703,11 @@ Bu senin grup için bağlantın %@! İsim swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Ağ & sunucular @@ -5746,6 +5742,11 @@ Bu senin grup için bağlantın %@! Ağ operatörü No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Ağ ayarları @@ -5864,6 +5865,11 @@ Bu senin grup için bağlantın %@! Hayır No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6026,15 +6032,18 @@ Bu senin grup için bağlantın %@! Okunmamış sohbet yok No comment provided by engineer. - - No user identifiers. - Herhangi bir kullanıcı tanımlayıcısı yok. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -6115,6 +6124,10 @@ new chat action Eski veritabanı No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Tek zamanlı bağlantı daveti @@ -6360,6 +6373,13 @@ alert button Operatör sunucusu alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Veya arşiv dosyasını içe aktar @@ -6683,18 +6703,12 @@ Hata: %@ Gizlilik politikası ve kullanım koşulları. No comment provided by engineer. - - Privacy redefined - Gizlilik yeniden tanımlandı - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez. + + Private and secure messaging. No comment provided by engineer. @@ -8087,6 +8101,14 @@ chat item action Ayarlar değiştirildi. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Profil resimlerini şekillendir @@ -8854,9 +8876,9 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Şifreleme çalışıyor ve yeni şifreleme anlaşması gerekli değil. Bağlantı hatalarına neden olabilir! No comment provided by engineer. - - The future of messaging - Gizli mesajlaşmanın yeni nesli + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8894,6 +8916,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Aynı koşullar operatör **%@** için de geçerli olacaktır. @@ -8939,6 +8965,14 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Temalar No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Bu koşullar ayrıca şunlar için de geçerli olacaktır: **%@**. @@ -9818,6 +9852,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Biriyle gizli bir profil paylaştığınızda, bu profil sizi davet ettikleri gruplar için kullanılacaktır. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10077,6 +10115,12 @@ Katılma isteği tekrarlansın mı? Mesajlar gönderemezsiniz! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -10086,11 +10130,6 @@ Katılma isteği tekrarlansın mı? Doğrulanamadınız; lütfen tekrar deneyin. No comment provided by engineer. - - You decide who can connect. - Kimin bağlanabileceğine siz karar verirsiniz. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10158,6 +10197,10 @@ Bağlantı isteği tekrarlansın mı? Bildirim almanız gerekiyor. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Mesaj gönderebilmek için **isteğinizin kabul edilmesini beklemelisiniz**. @@ -10296,6 +10339,10 @@ Bağlantı isteği tekrarlansın mı? Kişileriniz bağlı kalacaktır. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Kimlik bilgileriniz şifrelenmeden gönderilebilir. @@ -10316,6 +10363,10 @@ Bağlantı isteği tekrarlansın mı? Grubunuz No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Tercihleriniz diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 1b71eecb96..122884486d 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1115,11 +1115,6 @@ swipe action Відповісти на дзвінок No comment provided by engineer. - - Anybody can host servers. - Кожен може хостити сервери. - No comment provided by engineer. - App build: %@ Збірка програми: %@ @@ -1324,6 +1319,19 @@ swipe action Поганий хеш повідомлення No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + No comment provided by engineer. + Better calls Кращі дзвінки @@ -1505,15 +1513,6 @@ swipe action Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - Використовуючи SimpleX Chat, ви погоджуєтеся: -- надсилати лише легальний контент у публічних групах. -- поважати інших користувачів - без спаму. - No comment provided by engineer. - Call already ended! Дзвінок вже закінчився! @@ -2044,11 +2043,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - Налаштувати операторів сервера - No comment provided by engineer. - Confirm Підтвердити @@ -2665,11 +2659,6 @@ This is your own one-time link! Доставка налагодження No comment provided by engineer. - - Decentralized - Децентралізований - No comment provided by engineer. - Decode link relay test step @@ -3517,6 +3506,10 @@ chat item action Введіть пароль вище, щоб показати! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4415,6 +4408,10 @@ Error: %2$@ Отримуйте сповіщення, коли вас згадують. No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! Доброго дня! @@ -4687,11 +4684,6 @@ Error: %2$@ Негайно No comment provided by engineer. - - Immune to spam - Імунітет до спаму та зловживань - No comment provided by engineer. - Import Імпорт @@ -5577,16 +5569,15 @@ This is your link for group %@! Повідомлення, файли та дзвінки захищені **квантово-стійким шифруванням e2e** з ідеальною секретністю переадресації, відмовою та відновленням після злому. No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device Перенести пристрій No comment provided by engineer. - - Migrate from another device - Перехід з іншого пристрою - No comment provided by engineer. - Migrate here Мігруйте сюди @@ -5702,6 +5693,11 @@ This is your link for group %@! Ім'я swipe action + + Network +commitments + No comment provided by engineer. + Network & servers Мережа та сервери @@ -5736,6 +5732,11 @@ This is your link for group %@! Мережевий оператор No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings Налаштування мережі @@ -5854,6 +5855,11 @@ This is your link for group %@! Ні No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6016,15 +6022,18 @@ This is your link for group %@! Немає непрочитаних чатів No comment provided by engineer. - - No user identifiers. - Ніяких ідентифікаторів користувачів. + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + No comment provided by engineer. + Not all relays connected alert title @@ -6105,6 +6114,10 @@ new chat action Стара база даних No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link Посилання на одноразове запрошення @@ -6345,6 +6358,13 @@ alert button Сервер оператора alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file Або імпортуйте архівний файл @@ -6668,18 +6688,12 @@ Error: %@ Політика конфіденційності та умови використання. No comment provided by engineer. - - Privacy redefined - Конфіденційність переглянута - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - Приватні чати, групи та ваші контакти недоступні для операторів сервера. + + Private and secure messaging. No comment provided by engineer. @@ -8071,6 +8085,14 @@ chat item action Налаштування були змінені. alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images Сформуйте зображення профілю @@ -8837,9 +8859,9 @@ It can happen because of some bug or when the connection is compromised.Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з'єднання! No comment provided by engineer. - - The future of messaging - Наступне покоління приватних повідомлень + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8877,6 +8899,10 @@ It can happen because of some bug or when the connection is compromised.Стара база даних не була видалена під час міграції, її можна видалити. No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + No comment provided by engineer. + The same conditions will apply to operator **%@**. Такі ж умови діятимуть і для оператора **%@**. @@ -8922,6 +8948,14 @@ It can happen because of some bug or when the connection is compromised.Теми No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + No comment provided by engineer. + These conditions will also apply for: **%@**. Ці умови також поширюються на: **%@**. @@ -9799,6 +9833,10 @@ To connect, please ask your contact to create another connection link and check Коли ви ділитеся з кимось своїм профілем інкогніто, цей профіль буде використовуватися для груп, до яких вас запрошують. No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10058,6 +10096,12 @@ Repeat join request? Ви не можете надсилати повідомлення! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -10067,11 +10111,6 @@ Repeat join request? Вас не вдалося верифікувати, спробуйте ще раз. No comment provided by engineer. - - You decide who can connect. - Ви вирішуєте, хто може під'єднатися. - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10139,6 +10178,10 @@ Repeat connection request? Ви повинні отримувати сповіщення. token info + + You were born without an account + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. Ви зможете надсилати повідомлення **тільки після того, як ваш запит буде прийнято**. @@ -10277,6 +10320,10 @@ Repeat connection request? Ваші контакти залишаться на зв'язку. No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + No comment provided by engineer. + Your credentials may be sent unencrypted. Ваші облікові дані можуть бути надіслані незашифрованими. @@ -10297,6 +10344,10 @@ Repeat connection request? Ваша група No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences Ваші уподобання diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 2d3342b15b..1b4771642e 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -1118,11 +1118,6 @@ swipe action 接听来电 No comment provided by engineer. - - Anybody can host servers. - 任何人都可以托管服务器。 - No comment provided by engineer. - App build: %@ 应用程序构建:%@ @@ -1328,6 +1323,21 @@ swipe action 错误消息散列 No comment provided by engineer. + + Be free +in your network + No comment provided by engineer. + + + Be free in your network. + 在你的网络中自由畅行。 + No comment provided by engineer. + + + Because we destroyed the power to know who you are. So that your power can never be taken. + 因为我们摧毁了知道你是谁的权力,因而您的权利永远不会被夺走。 + No comment provided by engineer. + Better calls 更佳的通话 @@ -1511,15 +1521,6 @@ swipe action 通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。 No comment provided by engineer. - - By using SimpleX Chat you agree to: -- send only legal content in public groups. -- respect other users – no spam. - 使用 SimpleX Chat 代表您同意: -- 在公开群中只发送合法内容 -- 尊重其他用户 – 没有垃圾信息。 - No comment provided by engineer. - Call already ended! 通话已结束! @@ -2050,11 +2051,6 @@ chat toolbar Configure relays No comment provided by engineer. - - Configure server operators - 配置服务器运营方 - No comment provided by engineer. - Confirm 确认 @@ -2671,11 +2667,6 @@ This is your own one-time link! 调试交付 No comment provided by engineer. - - Decentralized - 分散式 - No comment provided by engineer. - Decode link relay test step @@ -3525,6 +3516,10 @@ chat item action 在上面输入密码以显示! No comment provided by engineer. + + Enter profile name... + No comment provided by engineer. + Enter relay name… No comment provided by engineer. @@ -4430,6 +4425,10 @@ Error: %2$@ 被提及时收到通知。 No comment provided by engineer. + + Get started + No comment provided by engineer. + Good afternoon! 下午好! @@ -4703,11 +4702,6 @@ Error: %2$@ 立即 No comment provided by engineer. - - Immune to spam - 不受垃圾和骚扰消息影响 - No comment provided by engineer. - Import 导入 @@ -5597,16 +5591,15 @@ This is your link for group %@! 消息、文件和通话受到 **抗量子 e2e 加密** 的保护,具有完全正向保密、否认和闯入恢复。 No comment provided by engineer. + + Migrate + No comment provided by engineer. + Migrate device 迁移设备 No comment provided by engineer. - - Migrate from another device - 从另一台设备迁移 - No comment provided by engineer. - Migrate here 迁移到此处 @@ -5722,6 +5715,11 @@ This is your link for group %@! 名称 swipe action + + Network +commitments + No comment provided by engineer. + Network & servers 网络和服务器 @@ -5756,6 +5754,11 @@ This is your link for group %@! 网络运营方 No comment provided by engineer. + + Network routers cannot know +who talks to whom + No comment provided by engineer. + Network settings 网络设置 @@ -5874,6 +5877,11 @@ This is your link for group %@! No comment provided by engineer. + + No account. No phone. No email. No ID. +The most secure encryption. + No comment provided by engineer. + No active relays No comment provided by engineer. @@ -6036,15 +6044,20 @@ This is your link for group %@! 没有未读聊天 No comment provided by engineer. - - No user identifiers. - 没有用户标识符。 + + Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life. + 没有人追踪你的谈话内容。没有人绘制你去过的地方的地图。隐私从来都不是一项功能--而是一种生活方式。 No comment provided by engineer. Non-profit governance No comment provided by engineer. + + Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign. + 别人家的门锁再好也比不上这里。房东再好也比不上这里,他既尊重你的隐私,又保留着所有访客的记录。你不是客人,你是家。没有国王能闯入--你是主人。 + No comment provided by engineer. + Not all relays connected alert title @@ -6125,6 +6138,10 @@ new chat action 旧的数据库 No comment provided by engineer. + + On your phone, not on any server. + No comment provided by engineer. + One-time invitation link 一次性邀请链接 @@ -6370,6 +6387,13 @@ alert button 运营方服务器 alert title + + Operators commit to: +- Be independent +- Minimize metadata usage +- Run verified open-source code + No comment provided by engineer. + Or import archive file 或者导入或者导入压缩文件 @@ -6693,18 +6717,12 @@ Error: %@ 隐私政策和使用条款。 No comment provided by engineer. - - Privacy redefined - 重新定义隐私 - No comment provided by engineer. - Privacy: for owners and subscribers. No comment provided by engineer. - - Private chats, groups and your contacts are not accessible to server operators. - 服务器运营方无法访问私密聊天、群组和你的联系人。 + + Private and secure messaging. No comment provided by engineer. @@ -8101,6 +8119,14 @@ chat item action 设置已修改。 alert message + + Setup notifications + No comment provided by engineer. + + + Setup routers + No comment provided by engineer. + Shape profile images 改变个人资料图形状 @@ -8867,9 +8893,9 @@ It can happen because of some bug or when the connection is compromised.加密正在运行,不需要新的加密协议。这可能会导致连接错误! No comment provided by engineer. - - The future of messaging - 下一代私密通讯软件 + + The first network where you own +your contacts and groups. No comment provided by engineer. @@ -8907,6 +8933,11 @@ It can happen because of some bug or when the connection is compromised.旧数据库在迁移过程中没有被移除,可以删除。 No comment provided by engineer. + + The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it. + 人类最古老的自由--与他人交谈而不被监视--建立在不会背叛它的基础设施之上。 + No comment provided by engineer. + The same conditions will apply to operator **%@**. No comment provided by engineer. @@ -8950,6 +8981,16 @@ It can happen because of some bug or when the connection is compromised.主题 No comment provided by engineer. + + Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible. + 然后我们转向线上,每个平台都要求你提供一些信息--你的姓名、电话号码、好友列表。我们接受了这样一个事实:与人交流的代价就是让别人知道我们在和谁交流。每一代人,每一代科技,都遵循着这样的模式--电话、电子邮件、即时通讯、社交媒体。这似乎是唯一可行的方式。 + No comment provided by engineer. + + + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + 还有另一种方法。一个没有电话号码、没有用户名、没有账户、没有任何用户身份的网络。一个连接人们并传输加密信息的网络,而无需知道谁连接了。 + No comment provided by engineer. + These conditions will also apply for: **%@**. 这些条件将同样适用于: **%@**。 @@ -9830,6 +9871,10 @@ To connect, please ask your contact to create another connection link and check 当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。 No comment provided by engineer. + + Why SimpleX is built. + No comment provided by engineer. + WiFi WiFi @@ -10091,6 +10136,12 @@ Repeat join request? 您无法发送消息! alert title + + You commit to: +- Only legal content in public groups +- Respect other users - no spam + No comment provided by engineer. + You connected to the channel via this relay link. No comment provided by engineer. @@ -10100,11 +10151,6 @@ Repeat join request? 您的身份无法验证,请再试一次。 No comment provided by engineer. - - You decide who can connect. - 你决定谁可以连接。 - No comment provided by engineer. - You have already requested connection! Repeat connection request? @@ -10171,6 +10217,11 @@ Repeat connection request? You should receive notifications. token info + + You were born without an account + 你生来就没有账户。 + No comment provided by engineer. + You will be able to send messages **only after your request is accepted**. **只有在你的请求被接受后**你才能发送消息。 @@ -10307,6 +10358,11 @@ Repeat connection request? 与您的联系人保持连接。 No comment provided by engineer. + + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + 你的对话内容始终属于你,就像互联网出现之前一样。网络不是一个你访问的地方,而是一个你创建并拥有的地方。无论你将其设为私密还是公开,任何人都无法将其夺走。 + No comment provided by engineer. + Your credentials may be sent unencrypted. 你的凭据可能以未经加密的方式被发送。 @@ -10327,6 +10383,10 @@ Repeat connection request? 你的群 No comment provided by engineer. + + Your network + No comment provided by engineer. + Your preferences 您的偏好设置 diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 0de696d430..3c62d4e3c4 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -249,6 +249,7 @@ D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; }; E559A0A12E3F77EE00B26F74 /* CommandsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E559A0A02E3F77EE00B26F74 /* CommandsMenuView.swift */; }; + E5A0B0012F960000AAAA0001 /* YourNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A0B0022F960000AAAA0001 /* YourNetwork.swift */; }; E5AEC0AB2F91A6EB00270665 /* CIChatLinkHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */; }; E5AEC0AF2F91A73500270665 /* ComposeChatLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */; }; E5C0BBE82F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */; }; @@ -621,6 +622,7 @@ D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; }; E559A0A02E3F77EE00B26F74 /* CommandsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandsMenuView.swift; sourceTree = ""; }; + E5A0B0022F960000AAAA0001 /* YourNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YourNetwork.swift; sourceTree = ""; }; E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIChatLinkHeader.swift; sourceTree = ""; }; E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeChatLinkView.swift; sourceTree = ""; }; E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SimpleXAssets.xcassets; sourceTree = ""; }; @@ -957,6 +959,7 @@ 5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */, 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */, 640743602CD360E600158442 /* ChooseServerOperators.swift */, + E5A0B0022F960000AAAA0001 /* YourNetwork.swift */, E5DBF1922F88169800E1D7FD /* ConnectBannerCard.swift */, ); path = Onboarding; @@ -1509,6 +1512,7 @@ 640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */, 6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */, 640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */, + E5A0B0012F960000AAAA0001 /* YourNetwork.swift in Sources */, 64A779FE2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift in Sources */, 5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */, E5E418012F83D2CA00252B9E /* OnboardingCards.swift in Sources */, diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index f0838bf9df..0391a3ebca 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -626,9 +626,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Отговор на повикване"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри."; - /* No comment provided by engineer. */ "App build: %@" = "Компилация на приложението: %@"; @@ -879,9 +876,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "С използването на SimpleX Chat вие се съгласявате със:\n- изпращане само на легално съдържание в публични групи.\n- уважение към другите потребители – без спам."; - /* No comment provided by engineer. */ "Call already ended!" = "Разговорът вече приключи!"; @@ -1059,7 +1053,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Чатът ще бъде изтрит за вас - това не може да бъде отменено!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Чат с администраторите"; /* No comment provided by engineer. */ @@ -1173,9 +1168,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Конфигурирай ICE сървъри"; -/* No comment provided by engineer. */ -"Configure server operators" = "Конфигуриране на сървърни оператори"; - /* No comment provided by engineer. */ "Confirm" = "Потвърди"; @@ -1513,9 +1505,6 @@ server test step */ /* time unit */ "days" = "дни"; -/* No comment provided by engineer. */ -"Decentralized" = "Децентрализиран"; - /* message decrypt error item */ "Decryption error" = "Грешка при декриптиране"; @@ -2405,9 +2394,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "Веднага"; -/* No comment provided by engineer. */ -"Immune to spam" = "Защитен от спам и злоупотреби"; - /* No comment provided by engineer. */ "Import" = "Импортиране"; @@ -2816,9 +2802,6 @@ server test error */ /* No comment provided by engineer. */ "Migrate device" = "Мигрирай устройството"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Мигриране от друго устройство"; - /* No comment provided by engineer. */ "Migrate here" = "Мигрирай тук"; @@ -2993,9 +2976,6 @@ server test error */ /* copied message info in history */ "no text" = "няма текст"; -/* No comment provided by engineer. */ -"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн."; - /* No comment provided by engineer. */ "Not compatible!" = "Несъвместим!"; @@ -3242,9 +3222,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy & security" = "Поверителност и сигурност"; -/* No comment provided by engineer. */ -"Privacy redefined" = "Поверителността преосмислена"; - /* No comment provided by engineer. */ "Private filenames" = "Поверителни имена на файлове"; @@ -4000,9 +3977,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Ново поколение поверителни съобщения"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хешът на предишното съобщение е различен."; @@ -4537,9 +4511,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Вече сте направили заявката за връзка!\nИзпрати отново заявката за свързване?"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index e63d3c0cc9..7c970a63d0 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -572,9 +572,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Přijmout hovor"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Servery může provozovat kdokoli."; - /* No comment provided by engineer. */ "App build: %@" = "Sestavení aplikace: %@"; @@ -674,6 +671,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Špatné ID zprávy"; +/* No comment provided by engineer. */ +"Be free in your network." = "Buďte svobodní ve své síti."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Protože jsme zničili sílu vědět, kdo jste. Aby vám vaši moc nikdo nemohl vzít."; + /* No comment provided by engineer. */ "Better calls" = "Lepší volání"; @@ -1170,9 +1173,6 @@ server test step */ /* time unit */ "days" = "dní"; -/* No comment provided by engineer. */ -"Decentralized" = "Decentralizované"; - /* message decrypt error item */ "Decryption error" = "Chyba dešifrování"; @@ -1917,9 +1917,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "Ihned"; -/* No comment provided by engineer. */ -"Immune to spam" = "Odolná vůči spamu a zneužití"; - /* No comment provided by engineer. */ "Import" = "Import"; @@ -2386,7 +2383,10 @@ server test error */ "no text" = "žádný text"; /* No comment provided by engineer. */ -"No user identifiers." = "Bez uživatelských identifikátorů."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nikdo nesledoval vaše konverzace. Nikdo nevytvořil mapu, kde jste byli. Soukromí nikdy nebylo funkcí - byl to způsob života."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nejde o to mít lepší zámek na dveřích někoho jiného. Ani o to mít nájemce, který respektuje vaše soukromí, ale vede evidenci všech vašich návštěvníků. Nejste host. Jste doma. Ani král k vám nemůže vstoupit - jste suverén."; /* No comment provided by engineer. */ "Notifications" = "Oznámení"; @@ -2583,9 +2583,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy & security" = "Ochrana osobních údajů a zabezpečení"; -/* No comment provided by engineer. */ -"Privacy redefined" = "Nové vymezení soukromí"; - /* No comment provided by engineer. */ "Private filenames" = "Soukromé názvy souborů"; @@ -3212,9 +3209,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Nová generace soukromých zpráv"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Hash předchozí zprávy se liší."; @@ -3230,6 +3224,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Nejstarší lidská svoboda - mluvit s druhým člověkem, aniž by byl sledován - postavena na infrastruktuře, která ji nemůže zradit."; + /* No comment provided by engineer. */ "The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅"; @@ -3239,6 +3236,12 @@ server test failure */ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Servery pro nová připojení vašeho aktuálního profilu chatu **%@**."; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Pak jsme se přesunuli na internet a každá platforma chtěla o vás něco vědět - vaše jméno, vaše číslo, vaše přátele. Smířili jsme se s tím, že cenou za komunikaci s ostatními je dát někomu vědět, s kým mluvíme. Každá generace, lidská i technická, to tak měla - telefon, e-mail, komunikátory, sociální sítě. Zdálo se, že je to jediný možný způsob."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Existuje i jiný způsob. Síť bez telefonních čísel. Bez uživatelských jmen. Bez účtů. Bez jakékoli uživatelské identity. Síť, která spojuje lidi a přenáší šifrované zprávy, aniž by bylo známo, kdo je připojen."; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Toto nastavení je pro váš aktuální profil **%@**."; @@ -3590,9 +3593,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte."; - /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení."; @@ -3629,6 +3629,9 @@ server test failure */ /* chat list item description */ "you shared one-time link incognito" = "sdíleli jste jednorázový odkaz inkognito"; +/* No comment provided by engineer. */ +"You were born without an account" = "Narodili jste se bez účtu."; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Ke skupině budete připojeni, až bude zařízení hostitele skupiny online, vyčkejte prosím nebo se podívejte později!"; @@ -3680,6 +3683,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Vaše kontakty zůstanou připojeny."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Vaše konverzace patří vám, jako tomu bylo vždy před internetem. Síť není místo, které navštěvujete. Je to místo, které vytváříte a vlastníte. A nikdo vám ho nemůže vzít, ať už je soukromé, nebo veřejné."; + /* No comment provided by engineer. */ "Your current chat database will be DELETED and REPLACED with the imported one." = "Vaše aktuální chat databáze bude ODSTRANĚNA a NAHRAZENA importovanou."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 739f2e7721..747562f0fc 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -676,9 +676,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Anruf annehmen"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Jeder kann seine eigenen Server aufsetzen."; - /* No comment provided by engineer. */ "App build: %@" = "App Build: %@"; @@ -820,6 +817,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Falsche Nachrichten-ID"; +/* No comment provided by engineer. */ +"Be free in your network." = "Genießen Sie die Freiheit in Ihrem Netzwerk."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Weil wir die Macht zerstört haben, zu wissen, wer Sie sind. Damit Ihnen Ihre Macht niemals genommen werden kann."; + /* No comment provided by engineer. */ "Better calls" = "Verbesserte Anrufe"; @@ -944,9 +947,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Per Chat-Profil (Voreinstellung) oder [per Verbindung](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam."; - /* No comment provided by engineer. */ "call" = "Anrufen"; @@ -1181,7 +1181,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chat mit Administratoren"; /* No comment provided by engineer. */ @@ -1304,9 +1305,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure relays" = "Relais konfigurieren"; -/* No comment provided by engineer. */ -"Configure server operators" = "Server-Betreiber konfigurieren"; - /* No comment provided by engineer. */ "Confirm" = "Bestätigen"; @@ -1728,9 +1726,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Debugging-Zustellung"; -/* No comment provided by engineer. */ -"Decentralized" = "Dezentral"; - /* relay test step */ "Decode link" = "Link dekodieren"; @@ -3051,9 +3046,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Sofort"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immun gegen Spam und Missbrauch"; - /* No comment provided by engineer. */ "Import" = "Importieren"; @@ -3654,9 +3646,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Gerät migrieren"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Von einem anderen Gerät migrieren"; - /* No comment provided by engineer. */ "Migrate here" = "Hierher migrieren"; @@ -3949,7 +3938,10 @@ servers warning */ "No unread chats" = "Keine ungelesenen Chats"; /* No comment provided by engineer. */ -"No user identifiers." = "Keine Benutzerkennungen."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Niemand verfolgte Ihre Gespräche. Niemand erstellte eine Karte, wo Sie sich aufgehalten haben. Privatsphäre war nie ein Feature - sie war selbstverständlich."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nicht ein besseres Schloss an der Tür eines Anderen. Kein freundlicher Vermieter, der Ihre Privatsphäre respektiert, aber dennoch jeden Besucher registriert. Sie sind kein Gast. Sie sind zu Hause. Kein Vermieter, kein Fremder kann es betreten - Sie sind souverän."; /* alert title */ "Not all relays connected" = "Es sind nicht alle Relais verbunden"; @@ -4365,12 +4357,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Datenschutz- und Nutzungsbedingungen."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Datenschutz neu definiert"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich."; - /* No comment provided by engineer. */ "Private filenames" = "Neutrale Dateinamen"; @@ -5739,9 +5725,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Die Verschlüsselung funktioniert und ein neues Verschlüsselungsabkommen ist nicht erforderlich. Es kann zu Verbindungsfehlern kommen!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Die nächste Generation von privatem Messaging"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Der Hash der vorherigen Nachricht unterscheidet sich."; @@ -5766,6 +5749,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Die älteste Freiheit des Menschen - mit einem anderen Menschen sprechen zu können, ohne beobachtet zu werden - gestützt auf einer Infrastruktur, die Sie nicht verraten kann."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**."; @@ -5793,6 +5779,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Design"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Dann sind wir online gegangen, und jede Plattform wollte Etwas von Ihnen - Ihren Namen, Ihre Nummer, Ihre Freunde. Wir akzeptierten, dass es der Preis mit Anderen zu kommunizieren ist, Jemandem preiszugeben, mit wem und wie wir miteinander kommunizieren. Jede Generation, Menschen und Technologien, kannten es nur so - Telefon, E-Mail, Messenger, soziale Medien. Es schien der einzig mögliche Weg zu sein."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Es gibt einen anderen Weg. Ein Netzwerk ohne Telefonnummern, ohne Benutzernamen, ohne Benutzerkennungen und ohne jegliche Benutzeridentität. Ein Netzwerk, welches Menschen verbindet und verschlüsselte Nachrichten überträgt, ohne zu wissen, wer mit wem verbunden ist."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Diese Nutzungsbedingungen gelten auch für: **%@**."; @@ -6576,9 +6568,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Sie konnten nicht überprüft werden; bitte versuchen Sie es erneut."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Sie entscheiden, wer sich mit Ihnen verbinden kann."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Sie haben bereits ein Verbindungsanfrage beantragt!\nVerbindungsanfrage wiederholen?"; @@ -6633,6 +6622,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "Sie haben %@ freigegeben"; +/* No comment provided by engineer. */ +"You were born without an account" = "Sie wurden ohne eine Benutzerkennung geboren."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Sie können erst dann Nachrichten versenden, **sobald Ihre Anfrage angenommen wurde**."; @@ -6714,6 +6706,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Ihre Kontakte bleiben weiterhin verbunden."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Ihre Kommunikation gehört Ihnen, so wie es immer war, bevor es das Internet gab. Das Netzwerk ist kein Ort, den Sie besuchen. Es ist ein Ort, den Sie erschaffen und besitzen und Niemand kann es Ihnen nehmen, egal ob Sie es privat oder öffentlich machen."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Ihre Anmeldeinformationen können unverschlüsselt versendet werden."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 865a9472ea..0c2353def6 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -676,9 +676,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Responder llamada"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Cualquiera puede alojar servidores."; - /* No comment provided by engineer. */ "App build: %@" = "Compilación app: %@"; @@ -820,6 +817,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "ID de mensaje incorrecto"; +/* No comment provided by engineer. */ +"Be free in your network." = "Se libre en tu red."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Porque hemos destruido el poder de saber quien eres. De manera que tu poder nunca se pueda arrebatar."; + /* No comment provided by engineer. */ "Better calls" = "Llamadas mejoradas"; @@ -944,9 +947,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Mediante perfil (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Al usar SimpleX Chat, aceptas:\n- enviar únicamente contenido legal en los grupos públicos.\n- respetar a los demás usuarios – spam prohibido."; - /* No comment provided by engineer. */ "call" = "llamada"; @@ -1181,7 +1181,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "El chat será eliminado para tí. ¡No puede deshacerse!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chatea con administradores"; /* No comment provided by engineer. */ @@ -1304,9 +1305,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure relays" = "Configurar servidores"; -/* No comment provided by engineer. */ -"Configure server operators" = "Configurar operadores de servidores"; - /* No comment provided by engineer. */ "Confirm" = "Confirmar"; @@ -1728,9 +1726,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Informe debug"; -/* No comment provided by engineer. */ -"Decentralized" = "Descentralizada"; - /* relay test step */ "Decode link" = "Decodificar enlace"; @@ -3051,9 +3046,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Inmediatamente"; -/* No comment provided by engineer. */ -"Immune to spam" = "Inmune a spam y abuso"; - /* No comment provided by engineer. */ "Import" = "Importar"; @@ -3654,9 +3646,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Migrar dispositivo"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Migrar desde otro dispositivo"; - /* No comment provided by engineer. */ "Migrate here" = "Migrar aquí"; @@ -3949,7 +3938,10 @@ servers warning */ "No unread chats" = "Ningún chat sin leer"; /* No comment provided by engineer. */ -"No user identifiers." = "Sin identificadores de usuario."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nadie monitorizaba tus conversaciones. Nadie registraba tus ubicaciones. La privacidad nunca fue un lujo, era la manera de vivir."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "No un candado mejorado en la puerta de otro. No un terrateniente que respeta tu privacidad pero sigue guardando un registro de tus visitantes. Tu no eres el invitado. Estás en tu casa y ningún rey podrá entrar. Tu eres el soberano."; /* alert title */ "Not all relays connected" = "Hay servidores no conectados"; @@ -4365,12 +4357,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Política de privacidad y condiciones de uso."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Privacidad redefinida"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores."; - /* No comment provided by engineer. */ "Private filenames" = "Nombres de archivos privados"; @@ -5739,9 +5725,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "El cifrado funciona y un cifrado nuevo no es necesario. ¡Podría dar lugar a errores de conexión!"; -/* No comment provided by engineer. */ -"The future of messaging" = "La nueva generación de mensajería privada"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "El hash del mensaje anterior es diferente."; @@ -5766,6 +5749,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "La libertad más antigua del ser humano, la de hablar con otra persona sin ser observado, materializada sobre una infraestructura que no puede traicionarla."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**."; @@ -5793,6 +5779,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Temas"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Después pasamos a internet y cada plataforma pedía una parte de tí: tu nombre, tu número, tus amistades. Aceptamos que el precio de hablar con los demás es informar a alguien de quién es interlocutor. Cada generación, personas y tecnología, ha funcionado así: teléfono, email, mensajería, redes sociales. Parecía el único camino."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Existe otro camino. Una red sin números de teléfono. Sin nombres de usuario. Sin cuentas. Sin identificadores de ningún tipo. Una red que conecta las personas y entrega mensajes cifrados sin saber quien está conectado."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Estas condiciones también se aplican para: **%@**."; @@ -6576,9 +6568,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "No has podido ser autenticado. Inténtalo de nuevo."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Tu decides quién se conecta."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ya has solicitado la conexión\n¿Repetir solicitud?"; @@ -6633,6 +6622,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "has desbloqueado a %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Naciste sin una cuenta."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Podrás enviar mensajes **después de que tu solicitud sea aceptada**."; @@ -6714,6 +6706,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Tus contactos permanecerán conectados."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Tus conversaciones te pertenecen, tal como ha sido siempre antes de la llegada de internet. Tu red no es un lugar que visitas. Es un lugar que has creado, te pertenece y nadie te la podrá quitar, ya sea pública o privada."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Tus credenciales podrían ser enviadas sin cifrar."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 4dfb01aae7..6ba1644d54 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -377,9 +377,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Vastaa puheluun"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia."; - /* No comment provided by engineer. */ "App build: %@" = "Sovellusversio: %@"; @@ -852,9 +849,6 @@ server test step */ /* time unit */ "days" = "päivää"; -/* No comment provided by engineer. */ -"Decentralized" = "Hajautettu"; - /* message decrypt error item */ "Decryption error" = "Salauksen purkuvirhe"; @@ -1590,9 +1584,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "Heti"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immuuni roskapostille ja väärinkäytöksille"; - /* No comment provided by engineer. */ "Import" = "Tuo"; @@ -2055,9 +2046,6 @@ server test error */ /* copied message info in history */ "no text" = "ei tekstiä"; -/* No comment provided by engineer. */ -"No user identifiers." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi."; - /* No comment provided by engineer. */ "Notifications" = "Ilmoitukset"; @@ -2249,9 +2237,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy & security" = "Yksityisyys ja turvallisuus"; -/* No comment provided by engineer. */ -"Privacy redefined" = "Yksityisyys uudelleen määritettynä"; - /* No comment provided by engineer. */ "Private filenames" = "Yksityiset tiedostonimet"; @@ -2872,9 +2857,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Seuraavan sukupolven yksityisviestit"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Edellisen viestin tarkiste on erilainen."; @@ -3247,9 +3229,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; - /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "Sinun on annettava tunnuslause aina, kun sovellus käynnistyy - sitä ei tallenneta laitteeseen."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 27140ac84c..4283dd349d 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -629,9 +629,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Répondre à l'appel"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "N'importe qui peut heberger un serveur."; - /* No comment provided by engineer. */ "App build: %@" = "Build de l'app : %@"; @@ -867,9 +864,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "En utilisant SimpleX Chat, vous acceptez de :\n- n'envoyer que du contenu légal dans les groupes publics.\n- respecter les autres utilisateurs - pas de spam."; - /* No comment provided by engineer. */ "call" = "appeler"; @@ -1149,9 +1143,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Configurer les serveurs ICE"; -/* No comment provided by engineer. */ -"Configure server operators" = "Configurer les opérateurs de serveur"; - /* No comment provided by engineer. */ "Confirm" = "Confirmer"; @@ -1540,9 +1531,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Livraison de débogage"; -/* No comment provided by engineer. */ -"Decentralized" = "Décentralisé"; - /* message decrypt error item */ "Decryption error" = "Erreur de déchiffrement"; @@ -2715,9 +2703,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Immédiatement"; -/* No comment provided by engineer. */ -"Immune to spam" = "Protégé du spam et des abus"; - /* No comment provided by engineer. */ "Import" = "Importer"; @@ -3216,9 +3201,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Transférer l'appareil"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Transférer depuis un autre appareil"; - /* No comment provided by engineer. */ "Migrate here" = "Transférer ici"; @@ -3453,9 +3435,6 @@ servers warning */ /* copied message info in history */ "no text" = "aucun texte"; -/* No comment provided by engineer. */ -"No user identifiers." = "Aucun identifiant d'utilisateur."; - /* No comment provided by engineer. */ "Not compatible!" = "Non compatible !"; @@ -3771,9 +3750,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy for your customers." = "Respect de la vie privée de vos clients."; -/* No comment provided by engineer. */ -"Privacy redefined" = "La vie privée redéfinie"; - /* No comment provided by engineer. */ "Private filenames" = "Noms de fichiers privés"; @@ -4835,9 +4811,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion !"; -/* No comment provided by engineer. */ -"The future of messaging" = "La nouvelle génération de messagerie privée"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Le hash du message précédent est différent."; @@ -5546,9 +5519,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Vous choisissez qui peut se connecter."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Vous avez déjà demandé une connexion !\nRépéter la demande de connexion ?"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index d8365a164f..4d3365fa68 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -676,9 +676,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Hívás fogadása"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Bárki üzemeltethet kiszolgálókat."; - /* No comment provided by engineer. */ "App build: %@" = "Alkalmazás összeállítási száma: %@"; @@ -820,6 +817,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Hibás az üzenet azonosítója"; +/* No comment provided by engineer. */ +"Be free in your network." = "Legyen szabad a saját hálózatában."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe."; + /* No comment provided by engineer. */ "Better calls" = "Továbbfejlesztett hívásélmény"; @@ -944,9 +947,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "A csevegési profillal (alapértelmezett), vagy a [kapcsolattal] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "A SimpleX Chat használatával Ön elfogadja, hogy:\n- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban.\n- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek."; - /* No comment provided by engineer. */ "call" = "hívás"; @@ -1181,7 +1181,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "A csevegés törölve lesz az Ön számára – ez a művelet nem vonható vissza!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Csevegés az adminisztrátorokkal"; /* No comment provided by engineer. */ @@ -1304,9 +1305,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure relays" = "Átjátszók konfigurálása"; -/* No comment provided by engineer. */ -"Configure server operators" = "Kiszolgálóüzemeltetők beállítása"; - /* No comment provided by engineer. */ "Confirm" = "Megerősítés"; @@ -1728,9 +1726,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Kézbesítési hibák felderítése"; -/* No comment provided by engineer. */ -"Decentralized" = "Decentralizált"; - /* relay test step */ "Decode link" = "Hivatkozás dekódolása"; @@ -3051,9 +3046,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Azonnal"; -/* No comment provided by engineer. */ -"Immune to spam" = "Védett a kéretlen tartalmakkal szemben"; - /* No comment provided by engineer. */ "Import" = "Importálás"; @@ -3654,9 +3646,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Eszköz átköltöztetése"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Átköltöztetés egy másik eszközről"; - /* No comment provided by engineer. */ "Migrate here" = "Átköltöztetés ide"; @@ -3949,7 +3938,10 @@ servers warning */ "No unread chats" = "Nincsenek olvasatlan csevegések"; /* No comment provided by engineer. */ -"No user identifiers." = "Nincsenek felhasználói azonosítók."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide - Ön itt szuverén."; /* alert title */ "Not all relays connected" = "Nem minden átjátszó kapcsolódott"; @@ -4365,12 +4357,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Adatvédelmi szabályzat és felhasználási feltételek."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Újraértelmezett adatvédelem"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára."; - /* No comment provided by engineer. */ "Private filenames" = "Privát fájlnevek"; @@ -5739,9 +5725,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Az üzenetváltás jövője"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Az előző üzenet kivonata különbözik."; @@ -5766,6 +5749,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "A legrégebbi emberi szabadság - beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének - olyan infrastruktúrán alapul, amely nem tudja elárulni."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**."; @@ -5793,6 +5779,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Témák"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot - nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött - telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Ezek a feltételek lesznek elfogadva a következő számára is: **%@**."; @@ -6576,9 +6568,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nem sikerült ellenőrizni; próbálja meg újra."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ön dönti el, hogy kivel beszélget."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ön már küldött egy kapcsolódási kérést!\nMegismétli a kapcsolódási kérést?"; @@ -6633,6 +6622,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "Ön feloldotta %@ letiltását"; +/* No comment provided by engineer. */ +"You were born without an account" = "Fiók nélkül születtünk."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Csak azután tud üzeneteket küldeni, **miután a kérését elfogadták**."; @@ -6714,6 +6706,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "A partnereivel továbbra is kapcsolatban marad."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "A hitelesítési adatai titkosítatlanul is elküldhetők."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 2d1161de80..d42a70a114 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -676,9 +676,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Rispondi alla chiamata"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Chiunque può installare i server."; - /* No comment provided by engineer. */ "App build: %@" = "Build dell'app: %@"; @@ -820,6 +817,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "ID del messaggio errato"; +/* No comment provided by engineer. */ +"Be free in your network." = "Vivi libero nella tua rete."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Perché abbiamo distrutto il potere di sapere chi sei. In modo che il tuo potere non possa mai esserti sottratto."; + /* No comment provided by engineer. */ "Better calls" = "Chiamate migliorate"; @@ -944,9 +947,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Per profilo di chat (predefinito) o [per connessione](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Usando SimpleX Chat accetti di:\n- inviare solo contenuto legale nei gruppi pubblici.\n- rispettare gli altri utenti - niente spam."; - /* No comment provided by engineer. */ "call" = "chiama"; @@ -1181,7 +1181,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "La chat verrà eliminata solo per te, non è reversibile!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chat con amministratori"; /* No comment provided by engineer. */ @@ -1304,9 +1305,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure relays" = "Configura i relay"; -/* No comment provided by engineer. */ -"Configure server operators" = "Configura gli operatori dei server"; - /* No comment provided by engineer. */ "Confirm" = "Conferma"; @@ -1728,9 +1726,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Debug della consegna"; -/* No comment provided by engineer. */ -"Decentralized" = "Decentralizzato"; - /* relay test step */ "Decode link" = "Decodifica il link"; @@ -3051,9 +3046,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Immediatamente"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immune a spam e abusi"; - /* No comment provided by engineer. */ "Import" = "Importa"; @@ -3654,9 +3646,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Migra dispositivo"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Migra da un altro dispositivo"; - /* No comment provided by engineer. */ "Migrate here" = "Migra qui"; @@ -3949,7 +3938,10 @@ servers warning */ "No unread chats" = "Nessuna chat non letta"; /* No comment provided by engineer. */ -"No user identifiers." = "Nessun identificatore utente."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nessuno monitorava le tue conversazioni. Nessuno disegnava una mappa delle tue posizioni. La privacy non era mai stata una caratteristica, era uno stile di vita."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Non una serratura migliore sulla porta di qualcun altro. Non un padrone di casa più gentile che rispetta la tua privacy, ma che continua a tenere traccia di tutti i visitatori. Non sei un ospite. Sei a casa tua. Nessun re può entrarvi: sei tu il sovrano."; /* alert title */ "Not all relays connected" = "Non tutti i relay sono connessi"; @@ -4365,12 +4357,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Informativa sulla privacy e condizioni d'uso."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Privacy ridefinita"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server."; - /* No comment provided by engineer. */ "Private filenames" = "Nomi di file privati"; @@ -5739,9 +5725,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "La crittografia funziona e il nuovo accordo sulla crittografia non è richiesto. Potrebbero verificarsi errori di connessione!"; -/* No comment provided by engineer. */ -"The future of messaging" = "La nuova generazione di messaggistica privata"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "L'hash del messaggio precedente è diverso."; @@ -5766,6 +5749,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "La più antica libertà umana, parlare con un'altra persona senza essere osservati, si basa su un'infrastruttura che non può tradirla."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**."; @@ -5793,6 +5779,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Temi"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Poi ci siamo trasferiti online e ogni piattaforma ha chiesto un pezzo di noi: il nome, il numero, gli amici. Abbiamo accettato che il prezzo da pagare per comunicare con gli altri fosse quello di far sapere a qualcuno con chi parliamo. Ogni generazione, sia di persone che di tecnologia, ha funzionato così: telefono, email, messenger, social media. Sembrava l'unico modo possibile."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "C'è un'altra via. Una rete senza numeri di telefono. Senza nomi utente. Senza account. Senza identificatori utente di alcun tipo. Una rete che connette le persone e trasferisce messaggi crittografati senza sapere chi è connesso."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Queste condizioni si applicheranno anche per: **%@**."; @@ -6576,9 +6568,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Non è stato possibile verificarti, riprova."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Sei tu a decidere chi può connettersi."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Hai già richiesto la connessione!\nRipetere la richiesta di connessione?"; @@ -6633,6 +6622,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "hai sbloccato %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Sei nato senza un account."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Potrai inviare messaggi **solo dopo che la tua richiesta verrà accettata**."; @@ -6714,6 +6706,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "I tuoi contatti resteranno connessi."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Le tue conversazioni appartengono a te, come è sempre stato prima dell'avvento di internet. La rete non è un luogo che visiti. È un luogo che crei e possiedi. E nessuno può portartelo via, che tu lo renda privato o pubblico."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Le credenziali potrebbero essere inviate in chiaro."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 37dd9d92ba..e095ec2aae 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -557,9 +557,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "通話に応答"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。"; - /* No comment provided by engineer. */ "App build: %@" = "アプリのビルド: %@"; @@ -1134,9 +1131,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "配信のデバッグ"; -/* No comment provided by engineer. */ -"Decentralized" = "分散型"; - /* message decrypt error item */ "Decryption error" = "復号化エラー"; @@ -1881,9 +1875,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "即座に"; -/* No comment provided by engineer. */ -"Immune to spam" = "スパムや悪質送信を防止"; - /* No comment provided by engineer. */ "Import" = "読み込む"; @@ -2208,9 +2199,6 @@ server test error */ /* No comment provided by engineer. */ "Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。"; -/* No comment provided by engineer. */ -"Migrate from another device" = "別の端末から移行"; - /* No comment provided by engineer. */ "Migrating database archive…" = "データベースのアーカイブを移行しています…"; @@ -2355,9 +2343,6 @@ server test error */ /* copied message info in history */ "no text" = "テキストなし"; -/* No comment provided by engineer. */ -"No user identifiers." = "世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。"; - /* No comment provided by engineer. */ "Notifications" = "通知"; @@ -2553,9 +2538,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy & security" = "プライバシーとセキュリティ"; -/* No comment provided by engineer. */ -"Privacy redefined" = "プライバシーの基準を新境地に"; - /* No comment provided by engineer. */ "Private filenames" = "プライベートなファイル名"; @@ -3158,9 +3140,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります!"; -/* No comment provided by engineer. */ -"The future of messaging" = "次世代のプライバシー・メッセンジャー"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "以前のメッセージとハッシュ値が異なります。"; @@ -3533,9 +3512,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "確認できませんでした。 もう一度お試しください。"; -/* No comment provided by engineer. */ -"You decide who can connect." = "あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。"; - /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "アプリ起動時にパスフレーズを入力しなければなりません。端末に保存されてません。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index be478b677d..594b0d5f47 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -626,9 +626,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Beantwoord oproep"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Iedereen kan servers hosten."; - /* No comment provided by engineer. */ "App build: %@" = "App build: %@"; @@ -870,9 +867,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Door SimpleX Chat te gebruiken, gaat u ermee akkoord:\n- alleen legale content te versturen in openbare groepen.\n- andere gebruikers te respecteren – geen spam."; - /* No comment provided by engineer. */ "call" = "bellen"; @@ -1053,7 +1047,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Chat met beheerders"; /* No comment provided by engineer. */ @@ -1164,9 +1159,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE servers configureren"; -/* No comment provided by engineer. */ -"Configure server operators" = "Serveroperators configureren"; - /* No comment provided by engineer. */ "Confirm" = "Bevestigen"; @@ -1564,9 +1556,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Foutopsporing bezorging"; -/* No comment provided by engineer. */ -"Decentralized" = "Gedecentraliseerd"; - /* message decrypt error item */ "Decryption error" = "Decodering fout"; @@ -2769,9 +2758,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Onmiddellijk"; -/* No comment provided by engineer. */ -"Immune to spam" = "Immuun voor spam en misbruik"; - /* No comment provided by engineer. */ "Import" = "Importeren"; @@ -3321,9 +3307,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Apparaat migreren"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Migreer vanaf een ander apparaat"; - /* No comment provided by engineer. */ "Migrate here" = "Migreer hierheen"; @@ -3594,9 +3577,6 @@ servers warning */ /* No comment provided by engineer. */ "No unread chats" = "Geen ongelezen chats"; -/* No comment provided by engineer. */ -"No user identifiers." = "Geen gebruikers-ID's."; - /* No comment provided by engineer. */ "Not compatible!" = "Niet compatibel!"; @@ -3960,12 +3940,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Privacybeleid en gebruiksvoorwaarden."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Privacy opnieuw gedefinieerd"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders."; - /* No comment provided by engineer. */ "Private filenames" = "Privé bestandsnamen"; @@ -5136,9 +5110,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "De versleuteling werkt en de nieuwe versleutelingsovereenkomst is niet vereist. Dit kan leiden tot verbindingsfouten!"; -/* No comment provided by engineer. */ -"The future of messaging" = "De volgende generatie privéberichten"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "De hash van het vorige bericht is anders."; @@ -5880,9 +5851,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "U kon niet worden geverifieerd; probeer het opnieuw."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Jij bepaalt wie er verbinding mag maken."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Je hebt al verbinding aangevraagd!\nVerbindingsverzoek herhalen?"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 14c57ae7e0..1790291595 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -641,9 +641,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Odbierz połączenie"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Każdy może hostować serwery."; - /* No comment provided by engineer. */ "App build: %@" = "Kompilacja aplikacji: %@"; @@ -785,6 +782,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Zły identyfikator wiadomości"; +/* No comment provided by engineer. */ +"Be free in your network." = "Ciesz się swobodą w swojej sieci."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Ponieważ zniszczyliśmy moc pozwalającą poznać, kim jesteś. Więc twoja moc nigdy nie będzie Ci odebrana."; + /* No comment provided by engineer. */ "Better calls" = "Lepsze połączenia"; @@ -903,9 +906,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Korzystając z SimpleX Chat, zgadzasz się:\n- wysyłać tylko legalne treści w grupach publicznych.\n- szanować innych użytkowników – nie spamować."; - /* No comment provided by engineer. */ "call" = "zadzwoń"; @@ -1089,7 +1089,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Czat zostanie usunięty dla Ciebie – tej operacji nie można cofnąć!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Czatuj z administratorami"; /* No comment provided by engineer. */ @@ -1203,9 +1204,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Skonfiguruj serwery ICE"; -/* No comment provided by engineer. */ -"Configure server operators" = "Skonfiguruj operatorów serwerów"; - /* No comment provided by engineer. */ "Confirm" = "Potwierdź"; @@ -1618,9 +1616,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Dostarczenie debugowania"; -/* No comment provided by engineer. */ -"Decentralized" = "Zdecentralizowane"; - /* message decrypt error item */ "Decryption error" = "Błąd odszyfrowania"; @@ -2890,9 +2885,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Natychmiast"; -/* No comment provided by engineer. */ -"Immune to spam" = "Odporność na spam i nadużycia"; - /* No comment provided by engineer. */ "Import" = "Importuj"; @@ -3472,9 +3464,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Zmigruj urządzenie"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Zmigruj z innego urządzenia"; - /* No comment provided by engineer. */ "Migrate here" = "Zmigruj tutaj"; @@ -3755,7 +3744,10 @@ servers warning */ "No unread chats" = "Brak nieprzeczytanych czatów"; /* No comment provided by engineer. */ -"No user identifiers." = "Brak identyfikatorów użytkownika."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nikt nie śledził twoich rozmów. Nikt nie rysował mapy miejsc, w których byłeś. Prywatność nigdy nie była funkcją - była sposobem na życie."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nie chodzi o lepszy zamek w drzwiach kogoś innego. Nie chodzi o milszego właściciela, który szanuje twoją prywatność, ale nadal prowadzi rejestr wszystkich odwiedzających. Nie jesteś gościem. Jesteś w domu. Żaden król nie może do niego wejść - jesteś suwerenem."; /* No comment provided by engineer. */ "Not compatible!" = "Nie kompatybilny!"; @@ -4150,12 +4142,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Polityka prywatności i warunki korzystania."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Redefinicja prywatności"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów."; - /* No comment provided by engineer. */ "Private filenames" = "Prywatne nazwy plików"; @@ -5422,9 +5408,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Następna generacja prywatnych wiadomości"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Hash poprzedniej wiadomości jest inny."; @@ -5449,6 +5432,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Najstarsza ludzka wolność - możliwość rozmowy z inną osobą bez bycia obserwowanym - opiera się na infrastrukturze, która nie może jej zdradzić."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Te same warunki będą miały zastosowanie do operatora **%@**."; @@ -5476,6 +5462,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Motywy"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Następnie przenieśliśmy się do sieci, a każda platforma prosiła o podanie danych osobowych - imienia i nazwiska, numeru telefonu, znajomych. Zaakceptowaliśmy fakt, że ceną za możliwość komunikowania się z innymi jest ujawnienie komuś, z kim rozmawiamy. Tak było w przypadku każdego pokolenia, ludzi i technologii - telefonu, poczty elektronicznej, komunikatorów, mediów społecznościowych. Wydawało się to jedyną możliwą opcją."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Jest jeszcze inny sposób. Sieć bez numerów telefonów. Bez nazw użytkowników. Bez kont. Bez jakichkolwiek tożsamości użytkowników. Sieć, która łączy ludzi i przesyła zaszyfrowane wiadomości, nie wiedząc, kto jest podłączony."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Warunki te będą miały również zastosowanie w przypadku: **%@**."; @@ -6217,9 +6209,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nie można zweryfikować użytkownika; proszę spróbować ponownie."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ty decydujesz, kto może się połączyć."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Już prosiłeś o połączenie!\nPowtórzyć prośbę połączenia?"; @@ -6274,6 +6263,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "odblokowałeś %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Urodziłeś się bez konta."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Będziesz mógł wysyłać wiadomości **dopiero po zaakceptowaniu Twojej prośby**."; @@ -6349,6 +6341,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Twoje kontakty pozostaną połączone."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Twoje rozmowy należą do Ciebie, tak jak zawsze było przed pojawieniem się Internetu. Sieć nie jest miejscem, które odwiedzasz. Jest miejscem, które tworzysz i które należy do Ciebie. Nikt nie może Ci tego odebrać, niezależnie od tego, czy jest to miejsce prywatne, czy publiczne."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Twoje poświadczenia mogą zostać wysłane niezaszyfrowane."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 24f0c358cc..7c91d84f12 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -638,9 +638,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Принять звонок"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Кто угодно может запустить сервер."; - /* No comment provided by engineer. */ "App build: %@" = "Сборка приложения: %@"; @@ -779,6 +776,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "Ошибка ID сообщения"; +/* No comment provided by engineer. */ +"Be free in your network." = "Будь свободен в своей сети."; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "Потому что мы разрушили саму возможность узнать, кто вы. Чтобы вашу свободу невозможно было отнять."; + /* No comment provided by engineer. */ "Better calls" = "Улучшенные звонки"; @@ -897,9 +900,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "По профилю чата или [по соединению](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей – не отправлять спам."; - /* No comment provided by engineer. */ "call" = "звонок"; @@ -1083,7 +1083,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Разговор будет удален для Вас - это действие нельзя отменить!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Чат с админами"; /* No comment provided by engineer. */ @@ -1197,9 +1198,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Настройка ICE серверов"; -/* No comment provided by engineer. */ -"Configure server operators" = "Настроить операторов серверов"; - /* No comment provided by engineer. */ "Confirm" = "Подтвердить"; @@ -1609,9 +1607,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Отладка доставки"; -/* No comment provided by engineer. */ -"Decentralized" = "Децентрализованный"; - /* message decrypt error item */ "Decryption error" = "Ошибка расшифровки"; @@ -2863,9 +2858,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Сразу"; -/* No comment provided by engineer. */ -"Immune to spam" = "Защищен от спама"; - /* No comment provided by engineer. */ "Import" = "Импортировать"; @@ -3436,9 +3428,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Мигрировать устройство"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Миграция с другого устройства"; - /* No comment provided by engineer. */ "Migrate here" = "Мигрировать сюда"; @@ -3719,7 +3708,10 @@ servers warning */ "No unread chats" = "Нет непрочитанных чатов"; /* No comment provided by engineer. */ -"No user identifiers." = "Без идентификаторов пользователей."; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Никто не отслеживал ваши разговоры. Никто не составлял карту ваших перемещений. Конфиденциальность не была функцией - это был образ жизни."; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Не более надёжный замок на чужой двери. Не более вежливый хозяин, который уважает вашу частную жизнь, но всё равно ведёт учёт всех посетителей. Вы не гость. Вы у себя дома. Ни один король не войдёт в ваш дом - вы суверенны."; /* No comment provided by engineer. */ "Not compatible!" = "Несовместимая версия!"; @@ -4114,12 +4106,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Политика конфиденциальности и условия использования."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Более конфиденциальный"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Частные разговоры, группы и Ваши контакты недоступны для операторов серверов."; - /* No comment provided by engineer. */ "Private filenames" = "Защищенные имена файлов"; @@ -5368,9 +5354,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Шифрование работает, и новое соглашение не требуется. Это может привести к ошибкам соединения!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Будущее коммуникаций"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хэш предыдущего сообщения отличается."; @@ -5395,6 +5378,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить."; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Древнейшая человеческая свобода - говорить с другим человеком без слежки - построенная на инфраструктуре, которая не может её предать."; + /* No comment provided by engineer. */ "The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**."; @@ -5422,6 +5408,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "Темы"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Потом мы вышли в интернет, и каждая платформа попросила частичку вас - ваше имя, ваш номер, ваших друзей. Мы смирились с тем, что за возможность общаться приходится отдавать информацию о том, с кем мы общаемся. Каждое поколение людей и технологий жило так - телефон, электронная почта, мессенджеры, социальные сети. Казалось, что другого пути нет."; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Другой путь есть. Сеть без номеров телефонов. Без имён пользователей. Без аккаунтов. Без каких-либо идентификаторов пользователей. Сеть, которая соединяет людей и передаёт зашифрованные сообщения, не зная, кто с кем связан."; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "Эти условия также будут применены к: **%@**."; @@ -6160,9 +6152,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Верификация не удалась; пожалуйста, попробуйте ещё раз."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Вы определяете, кто может соединиться."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Вы уже запросили соединение!\nПовторить запрос?"; @@ -6217,6 +6206,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "Вы разблокировали %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "Вы родились без аккаунта."; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "Вы сможете отправлять сообщения **только после того как Ваш запрос будет принят**."; @@ -6292,6 +6284,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "Ваши контакты сохранятся."; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Ваши разговоры принадлежат вам, как это всегда было до интернета. Сеть - это не место, куда вы приходите. Это место, которое вы создаёте и которым владеете. И никто не может это у вас отнять, делаете ли вы его конфиденциальным или публичным."; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "Ваши учетные данные могут быть отправлены в незашифрованном виде."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 827dedd00b..141c14cd00 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -353,9 +353,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "รับสาย"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้"; - /* No comment provided by engineer. */ "App build: %@" = "รุ่นแอป: %@"; @@ -819,9 +816,6 @@ server test step */ /* time unit */ "days" = "วัน"; -/* No comment provided by engineer. */ -"Decentralized" = "กระจายอำนาจแล้ว"; - /* message decrypt error item */ "Decryption error" = "ข้อผิดพลาดในการ decrypt"; @@ -1542,9 +1536,6 @@ server test error */ /* No comment provided by engineer. */ "Immediately" = "โดยทันที"; -/* No comment provided by engineer. */ -"Immune to spam" = "มีภูมิคุ้มกันต่อสแปมและการละเมิด"; - /* No comment provided by engineer. */ "Import" = "นำเข้า"; @@ -1995,9 +1986,6 @@ server test error */ /* copied message info in history */ "no text" = "ไม่มีข้อความ"; -/* No comment provided by engineer. */ -"No user identifiers." = "แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว"; - /* No comment provided by engineer. */ "Notifications" = "การแจ้งเตือน"; @@ -2189,9 +2177,6 @@ new chat action */ /* No comment provided by engineer. */ "Privacy & security" = "ความเป็นส่วนตัวและความปลอดภัย"; -/* No comment provided by engineer. */ -"Privacy redefined" = "นิยามความเป็นส่วนตัวใหม่"; - /* No comment provided by engineer. */ "Private filenames" = "ชื่อไฟล์ส่วนตัว"; @@ -2794,9 +2779,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้!"; -/* No comment provided by engineer. */ -"The future of messaging" = "การส่งข้อความส่วนตัวรุ่นต่อไป"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "แฮชของข้อความก่อนหน้านี้แตกต่างกัน"; @@ -3157,9 +3139,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง."; -/* No comment provided by engineer. */ -"You decide who can connect." = "ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น"; - /* No comment provided by engineer. */ "You have to enter passphrase every time the app starts - it is not stored on the device." = "คุณต้องใส่รหัสผ่านทุกครั้งที่เริ่มแอป - รหัสผ่านไม่ได้จัดเก็บไว้ในอุปกรณ์"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 8681e938bf..0fc11f3478 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -638,9 +638,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Aramayı cevapla"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir."; - /* No comment provided by engineer. */ "App build: %@" = "Uygulama sürümü: %@"; @@ -897,9 +894,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "SimpleX Chat'i kullanarak şunları kabul etmiş olursunuz:\n- herkese açık gruplarda yalnızca yasal içerik göndermek.\n- diğer kullanıcılara saygı göstermek – spam yapmamak."; - /* No comment provided by engineer. */ "call" = "Ara"; @@ -1083,7 +1077,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Sohbet senden silinecek - bu geri alınamaz!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Yöneticilerle sohbet et"; /* No comment provided by engineer. */ @@ -1197,9 +1192,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "ICE sunucularını ayarla"; -/* No comment provided by engineer. */ -"Configure server operators" = "Sunucu operatörlerini yapılandır"; - /* No comment provided by engineer. */ "Confirm" = "Onayla"; @@ -1609,9 +1601,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Hata ayıklama teslimatı"; -/* No comment provided by engineer. */ -"Decentralized" = "Merkezi Olmayan"; - /* message decrypt error item */ "Decryption error" = "Şifre çözme hatası"; @@ -2847,9 +2836,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Hemen"; -/* No comment provided by engineer. */ -"Immune to spam" = "Spam ve kötüye kullanıma karşı bağışıklı"; - /* No comment provided by engineer. */ "Import" = "İçe aktar"; @@ -3420,9 +3406,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Cihazı taşıma"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Başka bir cihazdan geçiş yapın"; - /* No comment provided by engineer. */ "Migrate here" = "Buraya göç edin"; @@ -3699,9 +3682,6 @@ servers warning */ /* No comment provided by engineer. */ "No unread chats" = "Okunmamış sohbet yok"; -/* No comment provided by engineer. */ -"No user identifiers." = "Herhangi bir kullanıcı tanımlayıcısı yok."; - /* No comment provided by engineer. */ "Not compatible!" = "Uyumlu değil!"; @@ -4095,12 +4075,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Gizlilik politikası ve kullanım koşulları."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Gizlilik yeniden tanımlandı"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez."; - /* No comment provided by engineer. */ "Private filenames" = "Gizli dosya adları"; @@ -5349,9 +5323,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Şifreleme çalışıyor ve yeni şifreleme anlaşması gerekli değil. Bağlantı hatalarına neden olabilir!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Gizli mesajlaşmanın yeni nesli"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Önceki mesajın hash'i farklı."; @@ -6132,9 +6103,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Doğrulanamadınız; lütfen tekrar deneyin."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Zaten bağlantı isteğinde bulundunuz!\nBağlantı isteği tekrarlansın mı?"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 06e7b26dae..d65ba029c7 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -632,9 +632,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "Відповісти на дзвінок"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "Кожен може хостити сервери."; - /* No comment provided by engineer. */ "App build: %@" = "Збірка програми: %@"; @@ -885,9 +882,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Використовуючи SimpleX Chat, ви погоджуєтеся:\n- надсилати лише легальний контент у публічних групах.\n- поважати інших користувачів - без спаму."; - /* No comment provided by engineer. */ "call" = "дзвонити"; @@ -1071,7 +1065,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "Чат буде видалено для вас - цю дію неможливо скасувати!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "Чат з адміністраторами"; /* No comment provided by engineer. */ @@ -1185,9 +1180,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "Налаштування серверів ICE"; -/* No comment provided by engineer. */ -"Configure server operators" = "Налаштувати операторів сервера"; - /* No comment provided by engineer. */ "Confirm" = "Підтвердити"; @@ -1594,9 +1586,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "Доставка налагодження"; -/* No comment provided by engineer. */ -"Decentralized" = "Децентралізований"; - /* message decrypt error item */ "Decryption error" = "Помилка розшифровки"; @@ -2823,9 +2812,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "Негайно"; -/* No comment provided by engineer. */ -"Immune to spam" = "Імунітет до спаму та зловживань"; - /* No comment provided by engineer. */ "Import" = "Імпорт"; @@ -3390,9 +3376,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "Перенести пристрій"; -/* No comment provided by engineer. */ -"Migrate from another device" = "Перехід з іншого пристрою"; - /* No comment provided by engineer. */ "Migrate here" = "Мігруйте сюди"; @@ -3669,9 +3652,6 @@ servers warning */ /* No comment provided by engineer. */ "No unread chats" = "Немає непрочитаних чатів"; -/* No comment provided by engineer. */ -"No user identifiers." = "Ніяких ідентифікаторів користувачів."; - /* No comment provided by engineer. */ "Not compatible!" = "Не сумісні!"; @@ -4050,12 +4030,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "Політика конфіденційності та умови використання."; -/* No comment provided by engineer. */ -"Privacy redefined" = "Конфіденційність переглянута"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "Приватні чати, групи та ваші контакти недоступні для операторів сервера."; - /* No comment provided by engineer. */ "Private filenames" = "Приватні імена файлів"; @@ -5292,9 +5266,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з'єднання!"; -/* No comment provided by engineer. */ -"The future of messaging" = "Наступне покоління приватних повідомлень"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "Хеш попереднього повідомлення відрізняється."; @@ -6069,9 +6040,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "Вас не вдалося верифікувати, спробуйте ще раз."; -/* No comment provided by engineer. */ -"You decide who can connect." = "Ви вирішуєте, хто може під'єднатися."; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "Ви вже надіслали запит на підключення!\nПовторити запит на підключення?"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 3297b7dce0..21d51bbcc4 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -638,9 +638,6 @@ swipe action */ /* No comment provided by engineer. */ "Answer call" = "接听来电"; -/* No comment provided by engineer. */ -"Anybody can host servers." = "任何人都可以托管服务器。"; - /* No comment provided by engineer. */ "App build: %@" = "应用程序构建:%@"; @@ -782,6 +779,12 @@ swipe action */ /* No comment provided by engineer. */ "Bad message ID" = "错误消息 ID"; +/* No comment provided by engineer. */ +"Be free in your network." = "在你的网络中自由畅行。"; + +/* No comment provided by engineer. */ +"Because we destroyed the power to know who you are. So that your power can never be taken." = "因为我们摧毁了知道你是谁的权力,因而您的权利永远不会被夺走。"; + /* No comment provided by engineer. */ "Better calls" = "更佳的通话"; @@ -900,9 +903,6 @@ marked deleted chat item preview text */ /* No comment provided by engineer. */ "By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。"; -/* No comment provided by engineer. */ -"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。"; - /* No comment provided by engineer. */ "call" = "呼叫"; @@ -1086,7 +1086,8 @@ set passcode view */ /* No comment provided by engineer. */ "Chat will be deleted for you - this cannot be undone!" = "将为你删除聊天 - 此操作无法撤销!"; -/* chat toolbar */ +/* chat feature +chat toolbar */ "Chat with admins" = "和管理员聊天"; /* No comment provided by engineer. */ @@ -1200,9 +1201,6 @@ set passcode view */ /* No comment provided by engineer. */ "Configure ICE servers" = "配置 ICE 服务器"; -/* No comment provided by engineer. */ -"Configure server operators" = "配置服务器运营方"; - /* No comment provided by engineer. */ "Confirm" = "确认"; @@ -1612,9 +1610,6 @@ server test step */ /* No comment provided by engineer. */ "Debug delivery" = "调试交付"; -/* No comment provided by engineer. */ -"Decentralized" = "分散式"; - /* message decrypt error item */ "Decryption error" = "解密错误"; @@ -2872,9 +2867,6 @@ servers warning */ /* No comment provided by engineer. */ "Immediately" = "立即"; -/* No comment provided by engineer. */ -"Immune to spam" = "不受垃圾和骚扰消息影响"; - /* No comment provided by engineer. */ "Import" = "导入"; @@ -3451,9 +3443,6 @@ servers warning */ /* No comment provided by engineer. */ "Migrate device" = "迁移设备"; -/* No comment provided by engineer. */ -"Migrate from another device" = "从另一台设备迁移"; - /* No comment provided by engineer. */ "Migrate here" = "迁移到此处"; @@ -3734,7 +3723,10 @@ servers warning */ "No unread chats" = "没有未读聊天"; /* No comment provided by engineer. */ -"No user identifiers." = "没有用户标识符。"; +"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "没有人追踪你的谈话内容。没有人绘制你去过的地方的地图。隐私从来都不是一项功能--而是一种生活方式。"; + +/* No comment provided by engineer. */ +"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "别人家的门锁再好也比不上这里。房东再好也比不上这里,他既尊重你的隐私,又保留着所有访客的记录。你不是客人,你是家。没有国王能闯入--你是主人。"; /* No comment provided by engineer. */ "Not compatible!" = "不兼容!"; @@ -4126,12 +4118,6 @@ alert button */ /* No comment provided by engineer. */ "Privacy policy and conditions of use." = "隐私政策和使用条款。"; -/* No comment provided by engineer. */ -"Privacy redefined" = "重新定义隐私"; - -/* No comment provided by engineer. */ -"Private chats, groups and your contacts are not accessible to server operators." = "服务器运营方无法访问私密聊天、群组和你的联系人。"; - /* No comment provided by engineer. */ "Private filenames" = "私密文件名"; @@ -5389,9 +5375,6 @@ server test failure */ /* No comment provided by engineer. */ "The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "加密正在运行,不需要新的加密协议。这可能会导致连接错误!"; -/* No comment provided by engineer. */ -"The future of messaging" = "下一代私密通讯软件"; - /* No comment provided by engineer. */ "The hash of the previous message is different." = "上一条消息的散列不同。"; @@ -5416,6 +5399,9 @@ server test failure */ /* No comment provided by engineer. */ "The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。"; +/* No comment provided by engineer. */ +"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "人类最古老的自由--与他人交谈而不被监视--建立在不会背叛它的基础设施之上。"; + /* No comment provided by engineer. */ "The second preset operator in the app!" = "应用中的第二个预设运营方!"; @@ -5437,6 +5423,12 @@ server test failure */ /* No comment provided by engineer. */ "Themes" = "主题"; +/* No comment provided by engineer. */ +"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "然后我们转向线上,每个平台都要求你提供一些信息--你的姓名、电话号码、好友列表。我们接受了这样一个事实:与人交流的代价就是让别人知道我们在和谁交流。每一代人,每一代科技,都遵循着这样的模式--电话、电子邮件、即时通讯、社交媒体。这似乎是唯一可行的方式。"; + +/* No comment provided by engineer. */ +"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "还有另一种方法。一个没有电话号码、没有用户名、没有账户、没有任何用户身份的网络。一个连接人们并传输加密信息的网络,而无需知道谁连接了。"; + /* No comment provided by engineer. */ "These conditions will also apply for: **%@**." = "这些条件将同样适用于: **%@**。"; @@ -6175,9 +6167,6 @@ server test failure */ /* No comment provided by engineer. */ "You could not be verified; please try again." = "您的身份无法验证,请再试一次。"; -/* No comment provided by engineer. */ -"You decide who can connect." = "你决定谁可以连接。"; - /* new chat sheet title */ "You have already requested connection!\nRepeat connection request?" = "您已经请求连接了!\n重复连接请求?"; @@ -6229,6 +6218,9 @@ server test failure */ /* snd group event chat item */ "you unblocked %@" = "您解封了 %@"; +/* No comment provided by engineer. */ +"You were born without an account" = "你生来就没有账户。"; + /* No comment provided by engineer. */ "You will be able to send messages **only after your request is accepted**." = "**只有在你的请求被接受后**你才能发送消息。"; @@ -6298,6 +6290,9 @@ server test failure */ /* No comment provided by engineer. */ "Your contacts will remain connected." = "与您的联系人保持连接。"; +/* No comment provided by engineer. */ +"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "你的对话内容始终属于你,就像互联网出现之前一样。网络不是一个你访问的地方,而是一个你创建并拥有的地方。无论你将其设为私密还是公开,任何人都无法将其夺走。"; + /* No comment provided by engineer. */ "Your credentials may be sent unencrypted." = "你的凭据可能以未经加密的方式被发送。"; diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt index d9d3af7bb7..a4fc74f6d4 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.android.kt @@ -10,7 +10,7 @@ import chat.simplex.res.MR @Composable actual fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference, onclick: (() -> Unit)?) { if (user == null) { - OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onclick = onclick) + OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.get_started, onboarding = OnboardingStage.Step2_CreateProfile, onclick = onclick) } else { OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onclick = onclick) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index e1696fe37b..7542a0b8c6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -142,10 +142,8 @@ fun MainScreen() { when { onboarding == OnboardingStage.Step1_SimpleXInfo && chatModel.migrationState.value != null -> { // In migration process. Nothing should interrupt it, that's why it's the first branch in when() - SimpleXInfo(chatModel, onboarding = true) - if (appPlatform.isDesktop) { - ModalManager.fullscreen.showInView() - } + if (appPlatform.isDesktop) DesktopOnboarding(onboarding, chatModel) + else SimpleXInfo(chatModel, onboarding = true) } chatModel.dbMigrationInProgress.value -> DefaultProgressView(stringResource(MR.strings.database_migration_in_progress)) chatModel.chatDbStatus.value == null && showInitializationView -> DefaultProgressView(stringResource(MR.strings.opening_database)) @@ -175,36 +173,31 @@ fun MainScreen() { } } } - else -> AnimatedContent(targetState = onboarding, - transitionSpec = { - if (targetState > initialState) { - fromEndToStartTransition() - } else { - fromStartToEndTransition() - }.using(SizeTransform(clip = false)) - } - ) { state -> - when (state) { - OnboardingStage.OnboardingComplete -> { /* handled out of AnimatedContent block */} - OnboardingStage.Step1_SimpleXInfo -> { - SimpleXInfo(chatModel, onboarding = true) - if (appPlatform.isDesktop) { - ModalManager.fullscreen.showInView() + else -> { + if (appPlatform.isDesktop) { + DesktopOnboarding(onboarding, chatModel) + } else { + AnimatedContent(targetState = onboarding, + transitionSpec = { + if (targetState > initialState) { + fromEndToStartTransition() + } else { + fromStartToEndTransition() + }.using(SizeTransform(clip = false)) + } + ) { state -> + when (state) { + OnboardingStage.OnboardingComplete -> {} + OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) + OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} + OnboardingStage.LinkAMobile -> LinkAMobile() + OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) + OnboardingStage.Step3_ChooseServerOperators, + OnboardingStage.Step3_CreateSimpleXAddress, + OnboardingStage.Step4_SetNotificationsMode -> YourNetworkView(chatModel) + OnboardingStage.Step4_NetworkCommitments -> OnboardingConditionsView(chatModel) } } - OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} - OnboardingStage.LinkAMobile -> LinkAMobile() - OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) - OnboardingStage.Step3_ChooseServerOperators -> { - val modalData = remember { ModalData() } - modalData.OnboardingConditionsView() - if (appPlatform.isDesktop) { - ModalManager.fullscreen.showInView() - } - } - // Ensure backwards compatibility with old onboarding stage for address creation, otherwise notification setup would be skipped - OnboardingStage.Step3_CreateSimpleXAddress -> SetNotificationsMode(chatModel) - OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel) } } } @@ -276,6 +269,27 @@ fun MainScreen() { } } +@Composable +private fun DesktopOnboarding(onboarding: OnboardingStage, chatModel: ChatModel) { + if (onboarding == OnboardingStage.LinkAMobile) { + LinkAMobile() + ModalManager.fullscreen.showInView() + } else { + DesktopOnboardingShell(onboarding) { + when (onboarding) { + OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true) + OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {} + OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel) + OnboardingStage.Step3_ChooseServerOperators, + OnboardingStage.Step3_CreateSimpleXAddress, + OnboardingStage.Step4_SetNotificationsMode -> YourNetworkView(chatModel) + OnboardingStage.Step4_NetworkCommitments -> OnboardingConditionsView(chatModel) + else -> {} + } + } + } +} + val ANDROID_CALL_TOP_PADDING = 40.dp @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 0363c4fc05..4d396c117e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -213,7 +213,7 @@ class AppPreferences { val shouldImportAppSettings = mkBoolPreference(SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS, false) val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME) - val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName) + val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.DARK.themeName) val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = { json.encodeToString(MapSerializer(String.serializer(), String.serializer()), it) }, decode = { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 36a7ae1a80..3805a8e8b7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -162,11 +162,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } else if (startChat().await()) { val savedOnboardingStage = appPreferences.onboardingStage.get() val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) { - if (appPlatform.isAndroid) { - OnboardingStage.Step4_SetNotificationsMode - } else { - OnboardingStage.OnboardingComplete - } + OnboardingStage.Step4_NetworkCommitments } else { savedOnboardingStage } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt index 9acfffb3ac..9b0f89c36d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Type.kt @@ -10,7 +10,7 @@ val Typography = Typography( h1 = TextStyle( fontFamily = Inter, fontWeight = FontWeight.Bold, - fontSize = 32.sp, + fontSize = 33.5.sp, ), h2 = TextStyle( fontFamily = Inter, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index 6ec124048c..8b3a755d39 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -4,6 +4,7 @@ import SectionTextFooter import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors @@ -11,22 +12,35 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.* +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.layout.ContentScale import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.* import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.migration.MigrateToDeviceView +import chat.simplex.common.views.migration.MigrationToState +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.onboarding.* import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR @@ -127,45 +141,114 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { @Composable fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { - val scope = rememberCoroutineScope() - val scrollState = rememberScrollState() - val keyboardState by getKeyboardState() - var savedKeyboardState by remember { mutableStateOf(keyboardState) } - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({ - if (chatModel.users.none { !it.user.hidden }) { - appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) - } else { - close() + if (appPlatform.isDesktop) { + CreateFirstProfileDesktop(chatModel, close) + } else { + CreateFirstProfileMobile(chatModel, close) + } +} + +@Composable +private fun RowScope.MigrateButton(refocusTrigger: MutableState) { + val focusManager = LocalFocusManager.current + TextButton( + onClick = { + focusManager.clearFocus() + if (chatModel.migrationState.value == null) { + chatModel.migrationState.value = MigrationToState.PasteOrScanLink } - }) { - ColumnWithScrollBar { - val displayName = rememberSaveable { mutableStateOf("") } - val focusRequester = remember { FocusRequester() } - Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(start = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, end = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, bottom = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.create_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false) - } - ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - Spacer(Modifier.height(DEFAULT_PADDING)) - ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Center, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary)) - Spacer(Modifier.height(DEFAULT_PADDING)) - ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester) + ModalManager.fullscreen.showCustomModal(animated = false, forceAnimated = appPlatform.isDesktop) { close -> + MigrateToDeviceView { + close() + refocusTrigger.value++ } - Spacer(Modifier.fillMaxHeight().weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + } + }, + modifier = Modifier.padding(end = DEFAULT_PADDING_HALF) + ) { + Icon(painterResource(MR.images.ic_download), null, Modifier.size(22.dp), tint = MaterialTheme.colors.primary) + Spacer(Modifier.width(4.dp)) + Text( + stringResource(if (appPlatform.isDesktop) MR.strings.migrate_from_another_device else MR.strings.migrate), + color = MaterialTheme.colors.primary, fontWeight = FontWeight.Medium + ) + } +} + +private fun onboardingBackAction(chatModel: ChatModel, close: () -> Unit) { + if (chatModel.users.none { !it.user.hidden }) { + appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } else { + close() + } +} + +@Composable +private fun CreateFirstProfileMobile(chatModel: ChatModel, close: () -> Unit) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + val focusRequester = remember { FocusRequester() } + val refocusTrigger = remember { mutableStateOf(0) } + ModalView( + close = { onboardingBackAction(chatModel, close) }, + endButtons = { MigrateButton(refocusTrigger) } + ) { + val displayName = rememberSaveable { mutableStateOf("") } + val keyboardState by getKeyboardState() + val imageHeightModifier = if (keyboardState == KeyboardState.Opened) { + Modifier.heightIn(max = 100.dp) + } else { + Modifier + } + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally, maxIntrinsicSize = true) { + Spacer(Modifier.weight(1f)) + + OnboardingImage( + MR.images.your_profile, MR.images.your_profile_light, MR.images.ic_person, + modifier = Modifier + .then(if (keyboardState != KeyboardState.Opened) Modifier.fillMaxWidth() else Modifier) + .then(imageHeightModifier) + ) + + Text( + stringResource(MR.strings.onboarding_your_profile), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Text( + stringResource(MR.strings.onboarding_on_your_phone), + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.secondary, + lineHeight = 25.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 14.dp) + ) + Text( + stringResource(MR.strings.onboarding_no_account), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.secondary, + textAlign = TextAlign.Center, + lineHeight = 20.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + ProfileNameField(displayName, stringResource(MR.strings.enter_profile_name), { it.trim() == mkValidName(it) }, focusRequester) + + Spacer(Modifier.weight(1f)) + + Column(Modifier.widthIn(max = 450.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton( - if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), - labelId = MR.strings.create_profile_button, + Modifier.fillMaxWidth(), + labelId = MR.strings.create_profile, onboarding = null, enabled = canCreateProfile(displayName.value), - onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) } + onclick = { createProfileOnboarding(chatModel, displayName.value, close) } ) - // Reserve space - TextButtonBelowOnboardingButton("", null) } - LaunchedEffect(Unit) { + LaunchedEffect(refocusTrigger.value) { delay(300) focusRequester.requestFocus() } @@ -173,16 +256,52 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { LaunchedEffect(Unit) { setLastVersionDefault(chatModel) } - if (savedKeyboardState != keyboardState) { - LaunchedEffect(keyboardState) { - scope.launch { - savedKeyboardState = keyboardState - scrollState.animateScrollTo(scrollState.maxValue) + } + } +} + +@Composable +private fun CreateFirstProfileDesktop(chatModel: ChatModel, close: () -> Unit) { + val focusRequester = remember { FocusRequester() } + val refocusTrigger = remember { mutableStateOf(0) } + val displayName = rememberSaveable { mutableStateOf("") } + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView( + close = { onboardingBackAction(chatModel, close) }, + endButtons = { MigrateButton(refocusTrigger) } + ) { + ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) } + Text(stringResource(MR.strings.onboarding_on_your_phone), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, lineHeight = 25.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING)) + ReadableText(MR.strings.onboarding_no_account, TextAlign.Center, style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.secondary)) + Spacer(Modifier.height(DEFAULT_PADDING)) + ProfileNameField(displayName, stringResource(MR.strings.enter_profile_name), { it.trim() == mkValidName(it) }, focusRequester) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + Modifier.widthIn(min = 300.dp), + labelId = MR.strings.create_profile, + onboarding = null, + enabled = canCreateProfile(displayName.value), + onclick = { createProfileOnboarding(chatModel, displayName.value, close) } + ) + TextButtonBelowOnboardingButton("", null) } } + LaunchedEffect(Unit) { + setLastVersionDefault(chatModel) + } } } + LaunchedEffect(refocusTrigger.value) { + delay(300) + focusRequester.requestFocus() + } } fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { @@ -207,7 +326,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDesc chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) - chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) + chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_NetworkCommitments) } else { val users = chatModel.controller.listUsers(rhId) chatModel.users.clear() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt index afb557cc78..ee63846657 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AppBarTitle.kt @@ -22,7 +22,10 @@ fun AppBarTitle( hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp, - enableAlphaChanges: Boolean = true + enableAlphaChanges: Boolean = true, + overrideTitleColor: Color? = null, + textAlign: TextAlign = TextAlign.Start, + lineHeight: TextUnit = TextUnit.Unspecified ) { val handler = LocalAppBarHandler.current val connection = if (enableAlphaChanges) handler?.connection else null @@ -34,10 +37,12 @@ fun AppBarTitle( } } val theme = CurrentColors.collectAsState() - val titleColor = MaterialTheme.appColors.title - val brush = if (theme.value.base == DefaultTheme.SIMPLEX) + val titleColor = overrideTitleColor ?: MaterialTheme.appColors.title + val brush = if (overrideTitleColor != null) + Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) + else if (theme.value.base == DefaultTheme.SIMPLEX) Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) - else // color is not updated when changing themes if I pass null here + else Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) Column { Text( @@ -48,9 +53,9 @@ fun AppBarTitle( alpha = bottomTitleAlpha(connection) }, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.h1.copy(brush = brush), + style = MaterialTheme.typography.h1.copy(brush = brush, lineHeight = lineHeight), color = MaterialTheme.colors.primaryVariant, - textAlign = TextAlign.Start + textAlign = textAlign ) if (hostDevice != null) { Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 21520f5424..28c81fbf56 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -111,8 +111,8 @@ class ModalManager(private val placement: ModalPlacement? = null) { fun isLastModalOpen(id: ModalViewId): Boolean = modalViews.lastOrNull()?.id == id - fun showModal(settings: Boolean = false, showClose: Boolean = true, id: ModalViewId? = null, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { - showCustomModal(id = id) { close -> + fun showModal(settings: Boolean = false, showClose: Boolean = true, id: ModalViewId? = null, forceAnimated: Boolean = false, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) { + showCustomModal(id = id, forceAnimated = forceAnimated) { close -> ModalView(close, showClose = showClose, endButtons = endButtons, content = { content() }) } } @@ -123,7 +123,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { } } - fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, id: ModalViewId? = null, modal: @Composable ModalData.(close: () -> Unit) -> Unit) { + fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, id: ModalViewId? = null, forceAnimated: Boolean = false, modal: @Composable ModalData.(close: () -> Unit) -> Unit) { Log.d(TAG, "ModalManager.showCustomModal") val data = ModalData(keyboardCoversBar = keyboardCoversBar) // Means, animation is in progress or not started yet. Do not wait until animation finishes, just remove all from screen. @@ -133,7 +133,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { } // Make animated appearance only on Android (everytime) and on Desktop (when it's on the start part of the screen or modals > 0) // to prevent unneeded animation on different situations - val anim = if (appPlatform.isAndroid) animated else animated && (modalCount.value > 0 || placement == ModalPlacement.START) + val anim = if (appPlatform.isAndroid) animated else (animated && (modalCount.value > 0 || placement == ModalPlacement.START)) || forceAnimated modalViews.add(ModalViewHolder(id, anim, data, modal)) _modalCount.value = modalViews.size - toRemove.size diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt index 9c6c0fa635..2f8fc013fc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/ChooseServerOperators.kt @@ -14,80 +14,160 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.TextStyle - import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.usersettings.networkAndServers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @Composable -fun ModalData.OnboardingConditionsView() { +fun OnboardingConditionsView(chatModel: ChatModel) { LaunchedEffect(Unit) { prepareChatBeforeFinishingOnboarding() } - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false) { - val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } - val selectedOperatorIds = remember { stateGetOrPut("selectedOperatorIds") { serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() } } - ColumnWithScrollBar( - Modifier - .themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer), - maxIntrinsicSize = true - ) { - Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), bottomPadding = DEFAULT_PADDING) - } + val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } + val selectedOperatorIds = remember { + mutableStateOf(OnboardingSharedState.selectedOperatorIds.ifEmpty { + serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet() + }) + } - Spacer(Modifier.weight(1f)) - Column( - (if (appPlatform.isDesktop) Modifier.width(450.dp).align(Alignment.CenterHorizontally) else Modifier) - .fillMaxWidth() - .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), - horizontalAlignment = Alignment.Start - ) { - Text( - stringResource(MR.strings.onboarding_conditions_private_chats_not_accessible), - style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp) - ) - Spacer(Modifier.height(DEFAULT_PADDING)) - Text( - stringResource(MR.strings.onboarding_conditions_by_using_you_agree), - style = TextStyle(fontSize = 17.sp, lineHeight = 23.sp) - ) - Spacer(Modifier.height(DEFAULT_PADDING)) - Text( - stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use), - style = TextStyle(fontSize = 17.sp), - color = MaterialTheme.colors.primary, - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null + if (appPlatform.isDesktop) { + OnboardingConditionsDesktop(selectedOperatorIds) + } else { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, showAppBar = false) { + OnboardingShrinkingLayout( + modifier = Modifier.fillMaxSize().themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer) + .systemBarsPadding() + .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), + topPadding = DEFAULT_PADDING, + image = { + Column(Modifier.padding(vertical = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingImage( + MR.images.network_commitments, MR.images.network_commitments_light, MR.images.ic_shield, + modifier = Modifier.fillMaxWidth(), + aspectRatio = 1.5f + ) + } + }, + content = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + stringResource(MR.strings.onboarding_network_commitments), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + lineHeight = 42.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Column( + Modifier.fillMaxWidth() + .padding(horizontal = DEFAULT_PADDING_HALF) + .padding(top = DEFAULT_PADDING), + horizontalAlignment = Alignment.Start ) { - ModalManager.fullscreen.showModal(endButtons = { ConditionsLinkButton() }) { - SimpleConditionsView(rhId = null) - } + Text( + stringResource(MR.strings.onboarding_conditions_private_chats_not_accessible), + style = MaterialTheme.typography.body1, + lineHeight = 22.sp + ) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_by_using_you_agree), + style = MaterialTheme.typography.body1, + lineHeight = 22.sp + ) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use), + style = MaterialTheme.typography.body2, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + ModalManager.fullscreen.showModal(endButtons = { ConditionsLinkButton() }) { + SimpleConditionsView(rhId = null) { + ModalManager.fullscreen.closeModal() + acceptConditions(selectedOperatorIds.value) + } + } + } + ) } - ) - } - Spacer(Modifier.weight(1f)) - - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { - AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperatorIds) - TextButtonBelowOnboardingButton(stringResource(MR.strings.onboarding_conditions_configure_server_operators)) { - ModalManager.fullscreen.showModalCloseable { close -> - ChooseServerOperators(serverOperators, selectedOperatorIds, close) + } + }, + button = { + Column(Modifier.widthIn(max = 450.dp).padding(bottom = DEFAULT_PADDING * 2), horizontalAlignment = Alignment.CenterHorizontally) { + AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperatorIds) } } + ) + } + } + } +} + +@Composable +private fun OnboardingConditionsDesktop(selectedOperatorIds: MutableState>) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false) { + ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_network_commitments), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) + } + Column(Modifier.width(450.dp), horizontalAlignment = Alignment.Start) { + ReadableText(MR.strings.onboarding_conditions_private_chats_not_accessible, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1) + Spacer(Modifier.height(DEFAULT_PADDING)) + ReadableText(MR.strings.onboarding_conditions_by_using_you_agree, TextAlign.Start, padding = PaddingValues(), style = MaterialTheme.typography.body1) + Spacer(Modifier.height(DEFAULT_PADDING)) + Text( + stringResource(MR.strings.onboarding_conditions_privacy_policy_and_conditions_of_use), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary, + modifier = Modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + ModalManager.fullscreen.showModal(forceAnimated = true, endButtons = { ConditionsLinkButton() }) { + SimpleConditionsView(rhId = null) { + ModalManager.fullscreen.closeModal() + acceptConditions(selectedOperatorIds.value) + } + } + } + ) + } + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + AcceptConditionsButton(enabled = selectedOperatorIds.value.isNotEmpty(), selectedOperatorIds) + TextButtonBelowOnboardingButton("", null) } } } @@ -104,7 +184,7 @@ fun ModalData.ChooseServerOperators( prepareChatBeforeFinishingOnboarding() } CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false) { + ModalView(close, enableClose = selectedOperatorIds.value.isNotEmpty()) { ColumnWithScrollBar( Modifier .themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer), @@ -141,11 +221,9 @@ fun ModalData.ChooseServerOperators( } Spacer(Modifier.weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { val enabled = selectedOperatorIds.value.isNotEmpty() SetOperatorsButton(enabled, close) - // Reserve space - TextButtonBelowOnboardingButton("", null) } } } @@ -212,52 +290,42 @@ private fun SetOperatorsButton(enabled: Boolean, close: () -> Unit) { ) } +private fun acceptConditions(selectedOperatorIds: Set) { + withBGApi { + val conditionsId = chatModel.conditions.value.currentConditions.conditionsId + val r = chatController.acceptConditions(chatModel.remoteHostId(), conditionsId = conditionsId, operatorIds = selectedOperatorIds.toList()) + if (r != null) { + chatModel.conditions.value = r + val enabledOps = enabledOperators(r.serverOperators, selectedOperatorIds) + if (enabledOps != null) { + val r2 = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOps) + if (r2 != null) { + chatModel.conditions.value = r2 + completeOnboarding() + } + } else { + completeOnboarding() + } + } + } +} + @Composable private fun AcceptConditionsButton( enabled: Boolean, selectedOperatorIds: State> ) { - fun continueOnAccept() { - if (appPlatform.isDesktop) { - continueToNextStep() - } else { - continueToSetNotificationsAfterAccept() - } - } OnboardingActionButton( modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), labelId = MR.strings.onboarding_conditions_accept, onboarding = null, enabled = enabled, - onclick = { - withBGApi { - val conditionsId = chatModel.conditions.value.currentConditions.conditionsId - val r = chatController.acceptConditions(chatModel.remoteHostId(), conditionsId = conditionsId, operatorIds = selectedOperatorIds.value.toList()) - if (r != null) { - chatModel.conditions.value = r - val enabledOperators = enabledOperators(r.serverOperators, selectedOperatorIds.value) - if (enabledOperators != null) { - val r2 = chatController.setServerOperators(rh = chatModel.remoteHostId(), operators = enabledOperators) - if (r2 != null) { - chatModel.conditions.value = r2 - continueOnAccept() - } - } else { - continueOnAccept() - } - } - } - } + onclick = { acceptConditions(selectedOperatorIds.value) } ) } -private fun continueToNextStep() { - appPrefs.onboardingStage.set(if (appPlatform.isAndroid) OnboardingStage.Step4_SetNotificationsMode else OnboardingStage.OnboardingComplete) -} - -private fun continueToSetNotificationsAfterAccept() { - appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) - ModalManager.fullscreen.showModalCloseable(showClose = false) { SetNotificationsMode(chatModel) } +private fun completeOnboarding() { + appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete) } private fun enabledOperators(operators: List, selectedOperatorIds: Set): List? { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 2b92d35e72..703d295523 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -1,8 +1,7 @@ package chat.simplex.common.views.onboarding import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -15,7 +14,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* @@ -24,21 +23,26 @@ import dev.icerock.moko.resources.StringResource @Composable fun HowItWorks(user: User?, onboardingStage: SharedPreference? = null) { - ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) { - AppBarTitle(stringResource(MR.strings.how_simplex_works), withPadding = false) - ReadableText(MR.strings.to_protect_privacy_simplex_has_ids_for_queues) - ReadableText(MR.strings.only_client_devices_store_contacts_groups_e2e_encrypted_messages) - ReadableText(MR.strings.all_message_and_files_e2e_encrypted) - if (onboardingStage == null) { - ReadableTextWithLink(MR.strings.read_more_in_github_with_link, "https://github.com/simplex-chat/simplex-chat#readme") + Column(Modifier.fillMaxSize().padding(horizontal = if (appPlatform.isDesktop) DEFAULT_PADDING * 2 else DEFAULT_PADDING)) { + Spacer(Modifier.statusBarsPadding().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) + val paraPadding = PaddingValues(bottom = if (appPlatform.isDesktop) 10.dp else 12.dp) + Column(Modifier.weight(1f).padding(bottom = DEFAULT_PADDING).verticalScroll(rememberScrollState())) { + Text(stringResource(MR.strings.why_built_heading), style = MaterialTheme.typography.h1, modifier = Modifier.padding(bottom = DEFAULT_PADDING)) + ReadableText(MR.strings.why_built_p1, padding = paraPadding) + ReadableText(MR.strings.why_built_p2, padding = paraPadding) + ReadableText(MR.strings.why_built_p3, padding = paraPadding) + ReadableText(MR.strings.why_built_p4, padding = paraPadding) + ReadableText(MR.strings.why_built_p5, padding = paraPadding) + ReadableText(MR.strings.why_built_p6, padding = paraPadding) + ReadableText(MR.strings.why_built_p7, padding = paraPadding) + ReadableText(MR.strings.why_built_tagline, padding = paraPadding) } - - Spacer(Modifier.fillMaxHeight().weight(1f)) - if (onboardingStage != null) { - Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + Column( + Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), + horizontalAlignment = Alignment.CenterHorizontally + ) { OnboardingActionButton(user, onboardingStage, onclick = { ModalManager.fullscreen.closeModal() }) - // Reserve space TextButtonBelowOnboardingButton("", null) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt index 9e48f4b2bd..e902b7947e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt @@ -3,6 +3,7 @@ package chat.simplex.common.views.onboarding import SectionTextFooter import SectionView import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment @@ -57,7 +58,7 @@ private fun LinkAMobileLayout( ModalView({ appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }) { Column(Modifier.fillMaxSize().padding(top = AppBarHeight * fontSizeSqrtMultiplier)) { Box(Modifier.align(Alignment.CenterHorizontally)) { - AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles)) + AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), overrideTitleColor = MaterialTheme.colors.onBackground) } Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) { Column( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingLayout.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingLayout.kt new file mode 100644 index 0000000000..684bfb0053 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingLayout.kt @@ -0,0 +1,161 @@ +package chat.simplex.common.views.onboarding + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.layout.* +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import chat.simplex.common.BuildConfigCommon +import chat.simplex.common.ui.theme.DEFAULT_PADDING +import chat.simplex.common.ui.theme.isInDarkTheme +import chat.simplex.common.views.helpers.ModalManager +import chat.simplex.common.views.helpers.mixWith +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops +import chat.simplex.res.MR +import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.compose.painterResource + +/** + * A layout for onboarding screens: image + content + spacer + button. + * The spacer shrinks first (down to [minSpacerHeight]), then the image shrinks. + * Button is always at the bottom. + */ +@Composable +fun OnboardingShrinkingLayout( + modifier: Modifier = Modifier, + topPadding: Dp = 0.dp, + minSpacerHeight: Dp = 20.dp, + image: @Composable () -> Unit, + content: @Composable () -> Unit, + button: @Composable () -> Unit +) { + Layout( + contents = listOf(image, content, button), + modifier = modifier + ) { (imageMeasurables, contentMeasurables, buttonMeasurables), constraints -> + val width = constraints.maxWidth + val height = constraints.maxHeight + val childConstraints = constraints.copy(minWidth = 0, minHeight = 0) + + // 1. Measure fixed content (texts) and button first + val contentPlaceable = contentMeasurables.first().measure(childConstraints) + val buttonPlaceable = buttonMeasurables.first().measure(childConstraints) + val minSpacer = minSpacerHeight.roundToPx() + + // 2. Image gets remaining after top padding + content + button + minimum spacer + val topPad = topPadding.roundToPx() + val reservedHeight = topPad + contentPlaceable.height + buttonPlaceable.height + minSpacer + val imageMaxHeight = (height - reservedHeight).coerceAtLeast(0) + val imagePlaceable = imageMeasurables.first().measure( + childConstraints.copy(maxWidth = width, maxHeight = imageMaxHeight) + ) + + // 3. Spacer fills whatever is left between content and button + val usedHeight = topPad + imagePlaceable.height + contentPlaceable.height + buttonPlaceable.height + val spacerHeight = (height - usedHeight).coerceAtLeast(minSpacer) + + // 4. Place: image centered horizontally, rest below + layout(width, height) { + var y = topPad + imagePlaceable.placeRelative((width - imagePlaceable.width) / 2, y) + y += imagePlaceable.height + contentPlaceable.placeRelative((width - contentPlaceable.width) / 2, y) + y += contentPlaceable.height + y += spacerHeight + buttonPlaceable.placeRelative((width - buttonPlaceable.width) / 2, y) + } + } +} + +@Composable +fun OnboardingImage( + lightImage: ImageResource, + darkImage: ImageResource, + fallbackIcon: ImageResource, + modifier: Modifier = Modifier, + aspectRatio: Float = 1f +) { + if (BuildConfigCommon.SIMPLEX_ASSETS) { + Image( + painterResource(if (isInDarkTheme()) darkImage else lightImage), + contentDescription = null, + contentScale = ContentScale.Fit, + modifier = Modifier.fillMaxWidth().then(modifier) + ) + } else { + val isDark = isInDarkTheme() + val stops = if (isDark) darkStops else lightStops + val scale = if (isDark) 1.5f else 1.2f + Box( + modifier + .aspectRatio(aspectRatio) + .clip(RoundedCornerShape(24.dp)) + .drawBehind { + val gp = gradientPoints(size.height / size.width, scale) + drawRect( + Brush.linearGradient( + colorStops = stops, + start = Offset(gp.startX * size.width, gp.startY * size.height), + end = Offset(gp.endX * size.width, gp.endY * size.height) + ) + ) + }, + contentAlignment = Alignment.Center + ) { + Icon( + painterResource(fallbackIcon), + contentDescription = null, + modifier = Modifier.size(80.dp), + tint = MaterialTheme.colors.primary + ) + } + } +} + +@Composable +fun DesktopOnboardingShell(stage: OnboardingStage, content: @Composable () -> Unit) { + Row(Modifier.fillMaxSize()) { + Box( + Modifier.weight(0.382f).fillMaxHeight() + .background(MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.985f)) + .padding(horizontal = DEFAULT_PADDING), + contentAlignment = Alignment.Center + ) { + when (stage) { + OnboardingStage.Step1_SimpleXInfo -> + OnboardingImage(MR.images.intro, MR.images.intro_light, MR.images.ic_forum, Modifier.fillMaxWidth()) + OnboardingStage.Step2_CreateProfile, + OnboardingStage.Step2_5_SetupDatabasePassphrase, + OnboardingStage.LinkAMobile -> + OnboardingImage(MR.images.your_profile, MR.images.your_profile_light, MR.images.ic_person, Modifier.fillMaxWidth()) + OnboardingStage.Step3_ChooseServerOperators, + OnboardingStage.Step3_CreateSimpleXAddress, + OnboardingStage.Step4_SetNotificationsMode -> + OnboardingImage(MR.images.your_network, MR.images.your_network_light, MR.images.ic_dns, Modifier.fillMaxWidth()) + OnboardingStage.Step4_NetworkCommitments -> + OnboardingImage(MR.images.network_commitments, MR.images.network_commitments_light, MR.images.ic_shield, Modifier.fillMaxWidth(), aspectRatio = 1.5f) + else -> {} + } + } + Divider(Modifier.fillMaxHeight().width(1.dp)) + Box(Modifier.weight(0.618f).fillMaxHeight().clipToBounds()) { + content() + ModalManager.fullscreen.showInView() + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt index 510df13c3d..7af364b855 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/OnboardingView.kt @@ -6,7 +6,8 @@ enum class OnboardingStage { LinkAMobile, Step2_5_SetupDatabasePassphrase, Step3_ChooseServerOperators, - Step3_CreateSimpleXAddress, - Step4_SetNotificationsMode, + Step3_CreateSimpleXAddress, // deprecated + Step4_SetNotificationsMode, // deprecated + Step4_NetworkCommitments, OnboardingComplete } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index 84f473067f..adcfb8b194 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -5,8 +5,8 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.Modifier import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.stringResource @@ -14,27 +14,21 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.common.model.ChatModel import chat.simplex.common.model.NotificationsMode import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.changeNotificationsMode import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource @Composable -fun SetNotificationsMode(m: ChatModel) { - LaunchedEffect(Unit) { - prepareChatBeforeFinishingOnboarding() - } - +fun SetNotificationsMode(currentMode: MutableState, onDone: () -> Unit) { CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { ModalView({}, showClose = false) { ColumnWithScrollBar(Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer)) { Box(Modifier.align(Alignment.CenterHorizontally)) { AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_title), bottomPadding = DEFAULT_PADDING) } - val currentMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } Column(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingInformationButton( stringResource(MR.strings.onboarding_notifications_mode_subtitle), @@ -43,34 +37,28 @@ fun SetNotificationsMode(m: ChatModel) { } Spacer(Modifier.weight(1f)) Column(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING)) { - SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc_short)) { + SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc_short), icon = painterResource(MR.images.ic_bolt)) { currentMode.value = NotificationsMode.SERVICE } - SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc_short)) { + SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc_short), icon = painterResource(MR.images.ic_timer)) { currentMode.value = NotificationsMode.PERIODIC } - SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc_short)) { + SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc_short), icon = painterResource(MR.images.ic_bolt_off)) { currentMode.value = NotificationsMode.OFF } } Spacer(Modifier.weight(1f)) - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { OnboardingActionButton( modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier, - labelId = MR.strings.use_chat, - onboarding = OnboardingStage.OnboardingComplete, - onclick = { - changeNotificationsMode(currentMode.value, m) - ModalManager.fullscreen.closeModals() - } + labelId = MR.strings.ok, + onboarding = null, + onclick = onDone ) - // Reserve space - TextButtonBelowOnboardingButton("", null) } } } } - SetNotificationsModeAdditions() } @Composable @@ -78,20 +66,31 @@ expect fun SetNotificationsModeAdditions() @Composable fun SelectableCard(currentValue: State, newValue: T, title: String, description: AnnotatedString, onSelected: (T) -> Unit) { + SelectableCard(currentValue, newValue, title, description, icon = null, onSelected) +} + +@Composable +fun SelectableCard(currentValue: State, newValue: T, title: String, description: AnnotatedString, icon: Painter?, onSelected: (T) -> Unit) { + val titleColor = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary TextButton( onClick = { onSelected(newValue) }, border = BorderStroke(1.dp, color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), shape = RoundedCornerShape(35.dp), ) { Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp).fillMaxWidth()) { - Text( - title, - style = MaterialTheme.typography.h3, - fontWeight = FontWeight.Medium, - color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, - modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally), - textAlign = TextAlign.Center - ) + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally)) { + if (icon != null) { + Icon(icon, null, Modifier.size(18.dp), tint = titleColor) + Spacer(Modifier.width(8.dp)) + } + Text( + title, + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium, + color = titleColor, + textAlign = TextAlign.Center + ) + } Text(description, Modifier.align(Alignment.CenterHorizontally), fontSize = 15.sp, @@ -105,7 +104,7 @@ fun SelectableCard(currentValue: State, newValue: T, title: String, descr } @Composable -private fun NotificationBatteryUsageInfo() { +fun NotificationBatteryUsageInfo() { ColumnWithScrollBar(Modifier.padding(DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.onboarding_notifications_mode_battery), withPadding = false) Text(stringResource(MR.strings.onboarding_notifications_mode_service), style = MaterialTheme.typography.h3, color = MaterialTheme.colors.secondary) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index c6eceb0ce2..9ef72a7f12 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -107,7 +107,7 @@ private fun SetupDatabasePassphraseLayout( Modifier.themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer).padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally, ) { - AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) + AppBarTitle(stringResource(MR.strings.setup_database_passphrase), overrideTitleColor = MaterialTheme.colors.onBackground) val onClickUpdate = { // Don't do things concurrently. Shouldn't be here concurrently, just in case diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index e5d00fddd1..5b186875fa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -11,6 +11,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale @@ -21,11 +24,15 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* +import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops import chat.simplex.common.views.migration.MigrateToDeviceView import chat.simplex.common.views.migration.MigrationToState import chat.simplex.res.MR @@ -36,12 +43,16 @@ import kotlin.math.floor @Composable fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { if (onboarding) { - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { - ModalView({}, showClose = false, showAppBar = false) { - SimpleXInfoLayout( - user = chatModel.currentUser.value, - onboardingStage = chatModel.controller.appPrefs.onboardingStage - ) + if (appPlatform.isDesktop) { + SimpleXInfoDesktop(chatModel) + } else { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, showAppBar = false) { + SimpleXInfoLayout( + user = chatModel.currentUser.value, + onboardingStage = chatModel.controller.appPrefs.onboardingStage + ) + } } } } else { @@ -52,40 +63,102 @@ fun SimpleXInfo(chatModel: ChatModel, onboarding: Boolean = true) { } } +@Composable +private fun SimpleXInfoDesktop(chatModel: ChatModel) { + val user = chatModel.currentUser.value + val onboardingStage = chatModel.controller.appPrefs.onboardingStage + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false) { + ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { + Spacer(Modifier.height(DEFAULT_PADDING)) + Box(Modifier.widthIn(max = 600.dp).fillMaxWidth(0.45f).align(Alignment.CenterHorizontally)) { + SimpleXLogo() + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 600.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_be_free), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) + } + Text(stringResource(MR.strings.onboarding_private_and_secure), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, lineHeight = 25.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + ReadableText(MR.strings.onboarding_first_network, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.secondary)) + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton(user, onboardingStage) + TextButtonBelowOnboardingButton(stringResource(MR.strings.why_simplex_is_built), icon = painterResource(MR.images.ic_info), onClick = { + ModalManager.fullscreen.showModal(forceAnimated = true) { HowItWorks(user, onboardingStage) } + }) + } + } + } + } + LaunchedEffect(Unit) { + if (chatModel.migrationState.value != null && !ModalManager.fullscreen.hasModalsOpen()) { + ModalManager.fullscreen.showCustomModal(animated = false) { close -> MigrateToDeviceView(close) } + } + } +} + @Composable fun SimpleXInfoLayout( user: User?, onboardingStage: SharedPreference? ) { - ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { - Box(Modifier.widthIn(max = if (appPlatform.isAndroid) 250.dp else 500.dp).padding(top = DEFAULT_PADDING + 8.dp), contentAlignment = Alignment.Center) { + Column(Modifier.fillMaxSize().systemBarsPadding().padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.padding(top = DEFAULT_PADDING * 2).widthIn(max = if (appPlatform.isAndroid) 185.dp else 160.dp), contentAlignment = Alignment.Center) { SimpleXLogo() } - - OnboardingInformationButton( - stringResource(MR.strings.next_generation_of_private_messaging), - onClick = { ModalManager.fullscreen.showModal { HowItWorks(user, onboardingStage) } }, - ) - - Spacer(Modifier.weight(1f)) - - Column { - InfoRow(painterResource(MR.images.privacy), MR.strings.privacy_redefined, MR.strings.first_platform_without_user_ids, width = 60.dp) - InfoRow(painterResource(MR.images.shield), MR.strings.immune_to_spam_and_abuse, MR.strings.people_can_connect_only_via_links_you_share, width = 46.dp) - InfoRow(painterResource(if (isInDarkTheme()) MR.images.decentralized_light else MR.images.decentralized), MR.strings.decentralized, MR.strings.opensource_protocol_and_code_anybody_can_run_servers) - } - - Column(Modifier.fillMaxHeight().weight(1f)) { } - - if (onboardingStage != null) { - Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally,) { - OnboardingActionButton(user, onboardingStage) - TextButtonBelowOnboardingButton(stringResource(MR.strings.migrate_from_another_device)) { - chatModel.migrationState.value = MigrationToState.PasteOrScanLink - ModalManager.fullscreen.showCustomModal { close -> MigrateToDeviceView(close) } + OnboardingShrinkingLayout( + modifier = Modifier.fillMaxSize(), + image = { + Column(Modifier.padding(vertical = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingImage( + MR.images.intro, MR.images.intro_light, MR.images.ic_forum, + modifier = if (appPlatform.isAndroid) Modifier.fillMaxWidth() else Modifier.heightIn(max = 280.dp) + ) + } + }, + content = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + stringResource(MR.strings.onboarding_be_free), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + lineHeight = 42.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Text( + stringResource(MR.strings.onboarding_private_and_secure), + style = MaterialTheme.typography.h3, + color = MaterialTheme.colors.secondary, + fontWeight = FontWeight.Medium, + lineHeight = 25.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 14.dp) + ) + Text( + stringResource(MR.strings.onboarding_first_network), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.secondary, + textAlign = TextAlign.Center, + lineHeight = 20.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + } + }, + button = { + if (onboardingStage != null) { + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton(user, onboardingStage) + TextButtonBelowOnboardingButton(stringResource(MR.strings.why_simplex_is_built), icon = painterResource(MR.images.ic_info), onClick = { + ModalManager.fullscreen.showModal { HowItWorks(user, onboardingStage) } + }) } } } + ) } LaunchedEffect(Unit) { if (chatModel.migrationState.value != null && !ModalManager.fullscreen.hasModalsOpen()) { @@ -101,25 +174,11 @@ fun SimpleXLogo() { contentDescription = stringResource(MR.strings.image_descr_simplex_logo), contentScale = ContentScale.FillWidth, modifier = Modifier - .padding(vertical = DEFAULT_PADDING) + .padding(bottom = 10.dp) .fillMaxWidth() ) } -@Composable -private fun InfoRow(icon: Painter, titleId: StringResource, textId: StringResource, width: Dp = 58.dp) { - Row(Modifier.padding(bottom = 27.dp), verticalAlignment = Alignment.Top) { - Spacer(Modifier.width((4.dp + 58.dp - width) / 2)) - Image(icon, contentDescription = null, modifier = Modifier - .width(width)) - Spacer(Modifier.width((4.dp + 58.dp - width) / 2 + DEFAULT_PADDING_HALF + 7.dp)) - Column(Modifier.padding(top = 4.dp), verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF)) { - Text(stringResource(titleId), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.h3, lineHeight = 24.sp) - Text(stringResource(textId), lineHeight = 24.sp, style = MaterialTheme.typography.body1, color = MaterialTheme.colors.secondary) - } - } -} - @Composable expect fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference, onclick: (() -> Unit)? = null) @@ -155,16 +214,20 @@ fun OnboardingActionButton( } @Composable -fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?) { +fun TextButtonBelowOnboardingButton(text: String, onClick: (() -> Unit)?, icon: Painter? = null) { val state = getKeyboardState() val enabled = onClick != null val topPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else 7.5.dp) val bottomPadding by animateDpAsState(if (appPlatform.isAndroid && state.value == KeyboardState.Opened) 0.dp else 7.5.dp) if ((appPlatform.isAndroid && state.value == KeyboardState.Closed) || topPadding > 0.dp) { TextButton({ onClick?.invoke() }, Modifier.padding(top = topPadding, bottom = bottomPadding).clip(CircleShape), enabled = enabled) { + if (icon != null) { + Icon(icon, null, tint = MaterialTheme.colors.primary) + Spacer(Modifier.width(4.dp)) + } Text( text, - Modifier.padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING_HALF, bottom = 5.dp), + Modifier.padding(vertical = 5.dp), color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, fontWeight = FontWeight.Medium, textAlign = TextAlign.Center @@ -219,6 +282,7 @@ fun OnboardingInformationButton( textLayoutResult = it }, style = MaterialTheme.typography.button, + fontWeight = FontWeight.Medium, color = MaterialTheme.colors.primary ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/YourNetwork.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/YourNetwork.kt new file mode 100644 index 0000000000..b20cfe3096 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/YourNetwork.kt @@ -0,0 +1,226 @@ +package chat.simplex.common.views.onboarding + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.common.BuildConfigCommon +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.darkStops +import chat.simplex.common.views.newchat.gradientPoints +import chat.simplex.common.views.newchat.lightStops +import chat.simplex.common.views.usersettings.changeNotificationsMode +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource + +internal object OnboardingSharedState { + var selectedOperatorIds: Set = emptySet() +} + +@Composable +fun YourNetworkView(chatModel: ChatModel) { + LaunchedEffect(Unit) { + prepareChatBeforeFinishingOnboarding() + } + + val serverOperators = remember { derivedStateOf { chatModel.conditions.value.serverOperators } } + val selectedOperatorIds = remember { + mutableStateOf(serverOperators.value.filter { it.enabled }.map { it.operatorId }.toSet()) + } + + LaunchedEffect(selectedOperatorIds.value) { + OnboardingSharedState.selectedOperatorIds = selectedOperatorIds.value + } + + val notificationMode = rememberSaveable { mutableStateOf(NotificationsMode.default) } + + if (appPlatform.isDesktop) { + YourNetworkDesktop(serverOperators, selectedOperatorIds) + } else { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false, showAppBar = false) { + OnboardingShrinkingLayout( + modifier = Modifier.fillMaxSize().themedBackground(bgLayerSize = LocalAppBarHandler.current?.backgroundGraphicsLayerSize, bgLayer = LocalAppBarHandler.current?.backgroundGraphicsLayer) + .systemBarsPadding() + .padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), + topPadding = DEFAULT_PADDING, + image = { + Column(Modifier.padding(vertical = DEFAULT_PADDING_HALF), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingImage( + MR.images.your_network, MR.images.your_network_light, MR.images.ic_dns, + modifier = Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() + ) + } + }, + content = { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + stringResource(MR.strings.onboarding_your_network), + style = MaterialTheme.typography.h1, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + lineHeight = 42.sp, + modifier = Modifier.padding(top = DEFAULT_PADDING_HALF) + ) + Text( + stringResource(MR.strings.onboarding_network_routers_cannot_know), + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.secondary, + lineHeight = 25.sp, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 14.dp) + ) + Column( + Modifier.padding(top = DEFAULT_PADDING_HALF), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + ConfigureRoutersButton(serverOperators, selectedOperatorIds) { + ModalManager.fullscreen.showCustomModal { close -> + ChooseServerOperators(serverOperators, selectedOperatorIds, close) + } + } + ConfigureNotificationsButton(notificationMode) { + ModalManager.fullscreen.showModalCloseable { close -> + SetNotificationsMode(notificationMode, close) + } + } + } + } + }, + button = { + Column( + Modifier.widthIn(max = 450.dp).padding(bottom = DEFAULT_PADDING * 2), + horizontalAlignment = Alignment.CenterHorizontally + ) { + OnboardingActionButton( + modifier = Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth(), + labelId = MR.strings.onboarding_network_operators_continue, + onboarding = null, + onclick = { + changeNotificationsMode(notificationMode.value, chatModel) + appPrefs.onboardingStage.set(OnboardingStage.Step4_NetworkCommitments) + } + ) + } + } + ) + } + } + } +} + +@Composable +private fun YourNetworkDesktop( + serverOperators: State>, + selectedOperatorIds: MutableState> +) { + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) { + ModalView({}, showClose = false) { + ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) { + Column(Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + Box(Modifier.align(Alignment.CenterHorizontally)) { + AppBarTitle(stringResource(MR.strings.onboarding_your_network), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp) + } + Text(stringResource(MR.strings.onboarding_network_routers_cannot_know), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, lineHeight = 25.sp, textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING)) + ConfigureRoutersButton(serverOperators, selectedOperatorIds) { + ModalManager.fullscreen.showCustomModal(forceAnimated = true) { close -> + ChooseServerOperators(serverOperators, selectedOperatorIds, close) + } + } + } + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + Modifier.widthIn(min = 300.dp), + labelId = MR.strings.onboarding_network_operators_continue, + onboarding = null, + onclick = { + appPrefs.onboardingStage.set(OnboardingStage.Step4_NetworkCommitments) + } + ) + TextButtonBelowOnboardingButton("", null) + } + } + } + } +} + +@Composable +private fun ConfigureRoutersButton(serverOperators: State>, selectedOperatorIds: State>, onClick: () -> Unit) { + Box( + modifier = Modifier + .clip(CircleShape) + .clickable { onClick() } + ) { + Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) { + Text( + stringResource(MR.strings.onboarding_configure_routers), + style = MaterialTheme.typography.button, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary + ) + serverOperators.value.forEach { op -> + Image( + painterResource(op.logo), + contentDescription = null, + modifier = Modifier.size(22.dp), + colorFilter = if (selectedOperatorIds.value.contains(op.operatorId)) null else ColorFilter.colorMatrix(ColorMatrix().apply { + setToSaturation(0f) + }) + ) + } + } + } +} + +@Composable +private fun ConfigureNotificationsButton(notificationMode: State, onClick: () -> Unit) { + val icon = when (notificationMode.value) { + NotificationsMode.SERVICE -> MR.images.ic_bolt + NotificationsMode.PERIODIC -> MR.images.ic_timer + NotificationsMode.OFF -> MR.images.ic_bolt_off + } + Box( + modifier = Modifier + .clip(CircleShape) + .clickable { onClick() } + ) { + Row(Modifier.padding(8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), verticalAlignment = Alignment.CenterVertically) { + Text( + stringResource(MR.strings.onboarding_configure_notifications), + style = MaterialTheme.typography.button, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colors.primary + ) + Icon( + painterResource(icon), + contentDescription = null, + tint = MaterialTheme.colors.primary + ) + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt index c70243f584..a62a58cb10 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/networkAndServers/NetworkAndServers.kt @@ -826,13 +826,22 @@ fun UsageConditionsView( @Composable fun SimpleConditionsView( - rhId: Long? + rhId: Long?, + onAccept: () -> Unit ) { ColumnWithScrollBar(modifier = Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.operator_conditions_of_use), enableAlphaChanges = false, withPadding = false, bottomPadding = DEFAULT_PADDING) Column(modifier = Modifier.weight(1f).padding(bottom = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF)) { ConditionsTextView(rhId) } + Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) { + OnboardingActionButton( + modifier = if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp), + labelId = MR.strings.onboarding_conditions_accept, + onboarding = null, + onclick = onAccept + ) + } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index cb92b386a0..081d837a70 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -473,7 +473,6 @@ سريع ولا تنتظر حتى يصبح المرسل متصلاً بالإنترنت! أخفِ كيفية الاستخدام - كيف يعمل SimpleX التخفي عبر رابط عنوان جهة الاتصال رمز الأمان غير صحيحة! الإشعارات الفورية مُعطَّلة @@ -2351,7 +2350,6 @@ المشرفين لا يمكن قراءة عبارة المرور في Keystore. قد يكون هذا قد حدث بعد تحديث النظام غير متوافق مع التطبيق. إذا لم يكن الأمر كذلك، فيُرجى التواصل مع المطوِّرين. موافقة الانتظار - ضبّط مُشغلي الخادم سياسة الخصوصية وشروط الاستخدام. لا يمكن الوصول إلى الدردشات الخاصة والمجموعات وجهات اتصالك لمشغلي الخادم. باستخدام SimpleX Chat، توافق على:\n- إرسال المحتوى القانوني فقط في المجموعات العامة.\n- احترام المستخدمين الآخرين – لا سبام. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index ade88f7fc8..8fc2acf786 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1292,9 +1292,27 @@ Make a private connection Migrate from another device How it works + Be free\nin your network + Private and secure messaging. + The first network where you own\nyour contacts and groups. + Get started + Why SimpleX is built. + Your profile + On your phone, not on any server. + No account. No phone. No email. No ID.\nThe most secure encryption. + Enter profile name… + Migrate - - How SimpleX works + + You were born without an account. + Nobody tracked your conversations. No one drew a map of where you\'d been. Privacy was never a feature — it was the way of life. + Then we moved online, and every platform asked for a piece of you — your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way — telephone, email, messengers, social media. It seemed the only way possible. + There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected. + Not a better lock on someone else\'s door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it — you are sovereign. + Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public. + The oldest human freedom — to speak to another person without being watched — built on infrastructure that cannot betray it. + Because we destroyed the power to know who you are. So that your power can never be taken. + Be free in your network. To protect your privacy, SimpleX uses separate IDs for each of your contacts. Only client devices store user profiles, contacts, groups, and messages. end-to-end encrypted, with post-quantum security in direct messages.]]> @@ -1321,11 +1339,10 @@ Use random passphrase - Private chats, groups and your contacts are not accessible to server operators. - By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam. + Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code + You commit to:\n- Only legal content in public groups\n- Respect other users — no spam Privacy policy and conditions of use. Accept - Configure server operators Server operators Network operators SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app. @@ -1342,6 +1359,15 @@ Update Continue + + Your network + Network routers cannot know\nwho talks to whom + Setup routers + Setup notifications + + + Network commitments + Incoming video call Incoming audio call diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index d708736821..da3c17c184 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -764,7 +764,6 @@ Информация Инсталирай SimpleX Chat за терминал Как работи - Как работи SimpleX Защитен от спам Игнорирай Покани членове @@ -2328,7 +2327,6 @@ Чат с член Разговаряйте с членовете, преди да се присъединят. Нарушение на правилата на общността - Конфигуриране на сървърни оператори Свързване Свържете се по-бързо! 🚀 Връзката е блокирана diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml index a34a4ca7fc..4ce42e2df8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ca/strings.xml @@ -1216,7 +1216,6 @@ Sense identificadors d\'usuari. Per protegir la vostra privadesa SimpleX utilitza identificadors separats per a cadascun dels vostres contactes.. Obrir SimpleX - Com funciona SimpleX Només els dispositius client emmagatzemen perfils d\'usuari, contactes, grups i missatges. Notificacions privades Com afecta la bateria @@ -2342,7 +2341,6 @@ Els xats privats, els grups i els vostres contactes no són accessibles per als operadors de servidor. Acceptar En utilitzar SimpleX Chat accepteu:\n- enviar només contingut legal en grups públics.\n- Respectar els altres usuaris, sense correu brossa. - Configurar els operadors de servidor Enllaç al canal SimpleX Aquest enllaç requereix una versió de l\'aplicació més recent. Actualitzeu l\'aplicació o demaneu al vostre contacte que enviï un enllaç compatible. Enllaç de connexió no compatible diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index c7697c35ba..5f86035db7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -242,7 +242,6 @@ probíhající hovor Decentralizovaná Jak to funguje - Jak funguje SimpleX Pouze klientská zařízení ukládají uživatelské profily, kontakty, skupiny a zprávy. Soukromé oznámení Pravidelné @@ -2366,7 +2365,6 @@ Členové budou odstraněny z chatu - toto nelze zvrátit! Použitím SimpleX chatu souhlasíte že:\n- ve veřejných skupinách budete zasílat pouze legální obsah.\n- budete respektovat ostatní uživatele – žádný spam. Přijmout - Nastavit operátora serveru Zásady ochrany soukromí a podmínky používání. Soukromé konverzace, skupiny a kontakty nejsou přístupné provozovatelům serverů. Nepodporovaný odkaz k připojení @@ -2551,4 +2549,13 @@ Pokud jste se připojili k nějakým kanálům nebo je vytvořili, přestanou trvale fungovat. aktivní Zrušit + Narodili jste se bez účtu. + Nikdo nesledoval vaše konverzace. Nikdo nevytvořil mapu, kde jste byli. Soukromí nikdy nebylo funkcí - byl to způsob života. + Pak jsme se přesunuli na internet a každá platforma chtěla o vás něco vědět - vaše jméno, vaše číslo, vaše přátele. Smířili jsme se s tím, že cenou za komunikaci s ostatními je dát někomu vědět, s kým mluvíme. Každá generace, lidská i technická, to tak měla - telefon, e-mail, komunikátory, sociální sítě. Zdálo se, že je to jediný možný způsob. + Existuje i jiný způsob. Síť bez telefonních čísel. Bez uživatelských jmen. Bez účtů. Bez jakékoli uživatelské identity. Síť, která spojuje lidi a přenáší šifrované zprávy, aniž by bylo známo, kdo je připojen. + Nejde o to mít lepší zámek na dveřích někoho jiného. Ani o to mít nájemce, který respektuje vaše soukromí, ale vede evidenci všech vašich návštěvníků. Nejste host. Jste doma. Ani král k vám nemůže vstoupit - jste suverén. + Vaše konverzace patří vám, jako tomu bylo vždy před internetem. Síť není místo, které navštěvujete. Je to místo, které vytváříte a vlastníte. A nikdo vám ho nemůže vzít, ať už je soukromé, nebo veřejné. + Nejstarší lidská svoboda - mluvit s druhým člověkem, aniž by byl sledován - postavena na infrastruktuře, která ji nemůže zradit. + Protože jsme zničili sílu vědět, kdo jste. Aby vám vaši moc nikdo nemohl vzít. + Buďte svobodní ve své síti. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 251987f658..90f40e3713 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -473,7 +473,6 @@ Stellen Sie eine private Verbindung her Wie es funktioniert - Wie SimpleX funktioniert SimpleX nutzt individuelle Kennungen für jeden Ihrer Kontakte, um Ihre Privatsphäre zu schützen. Nur die Endgeräte speichern Benutzerprofile, Kontakte, Gruppen und Nachrichten. GitHub-Repository mehr dazu.]]> @@ -2450,7 +2449,6 @@ Durch die Nutzung von SimpleX Chat erklären Sie sich damit einverstanden:\n- nur legale Inhalte in öffentlichen Gruppen zu versenden.\n- andere Nutzer zu respektieren - kein Spam. Datenschutz- und Nutzungsbedingungen. Annehmen - Server-Betreiber konfigurieren Private Chats, Gruppen und Ihre Kontakte sind für Server-Betreiber nicht zugänglich. Verbindungs-Link wird nicht unterstützt Verkürzter Link @@ -2821,6 +2819,15 @@ Diese Adresse in Ihrem Social‑Media‑Profil, auf Ihrer Webseite oder in Ihrer E‑Mail‑Signatur verwenden. Wir haben das Verbinden für neue Nutzer vereinfacht. Ihre öffentliche Adresse + Sie wurden ohne eine Benutzerkennung geboren. + Niemand verfolgte Ihre Gespräche. Niemand erstellte eine Karte, wo Sie sich aufgehalten haben. Privatsphäre war nie ein Feature - sie war selbstverständlich. + Dann sind wir online gegangen, und jede Plattform wollte Etwas von Ihnen - Ihren Namen, Ihre Nummer, Ihre Freunde. Wir akzeptierten, dass es der Preis mit Anderen zu kommunizieren ist, Jemandem preiszugeben, mit wem und wie wir miteinander kommunizieren. Jede Generation, Menschen und Technologien, kannten es nur so - Telefon, E-Mail, Messenger, soziale Medien. Es schien der einzig mögliche Weg zu sein. + Es gibt einen anderen Weg. Ein Netzwerk ohne Telefonnummern, ohne Benutzernamen, ohne Benutzerkennungen und ohne jegliche Benutzeridentität. Ein Netzwerk, welches Menschen verbindet und verschlüsselte Nachrichten überträgt, ohne zu wissen, wer mit wem verbunden ist. + Nicht ein besseres Schloss an der Tür eines Anderen. Kein freundlicher Vermieter, der Ihre Privatsphäre respektiert, aber dennoch jeden Besucher registriert. Sie sind kein Gast. Sie sind zu Hause. Kein Vermieter, kein Fremder kann es betreten - Sie sind souverän. + Ihre Kommunikation gehört Ihnen, so wie es immer war, bevor es das Internet gab. Das Netzwerk ist kein Ort, den Sie besuchen. Es ist ein Ort, den Sie erschaffen und besitzen und Niemand kann es Ihnen nehmen, egal ob Sie es privat oder öffentlich machen. + Die älteste Freiheit des Menschen - mit einem anderen Menschen sprechen zu können, ohne beobachtet zu werden - gestützt auf einer Infrastruktur, die Sie nicht verraten kann. + Weil wir die Macht zerstört haben, zu wissen, wer Sie sind. Damit Ihnen Ihre Macht niemals genommen werden kann. + Genießen Sie die Freiheit in Ihrem Netzwerk. Abonnenten-Meldungen diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index 20a271f58f..250e587761 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -549,7 +549,6 @@ Διαμορφωμένοι SMP διακομιστές Διαμορφωμένοι XFTP διακομιστές Διαμορφωμένοι ICE διακομιστές - Διαμόρφωση χειριστών διακομιστή Επιβεβαίωσε Επιβεβαίωση διαγραφής επαφής; Επιβεβαίωση αναβαθμίσεων βάσης δεδομένων @@ -1007,7 +1006,6 @@ Πως επηρεάζει τη μπαταρία Πως βοηθάει την ιδιωτικότητα Πως δουλεύει - Πως δουλεύει το SimpleX Πως να Πως να το χρησιμοποιήσεις Πως να χρησιμοποιήσεις markdown σύνταξη diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 4fc83c8a84..4fe56f1f98 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -381,7 +381,6 @@ Servidores ICE (uno por línea) Nombre completo: Tu decides quién se conecta. - Cómo funciona SimpleX Colgar Archivos y multimedia ¡Grupo no encontrado! @@ -2376,7 +2375,6 @@ Política de privacidad y condiciones de uso. Los chats privados, los grupos y tus contactos no son accesibles para los operadores de servidores. Al usar SimpleX Chat, aceptas:\n- enviar únicamente contenido legal en los grupos públicos.\n- respetar a los demás usuarios - spam prohibido. - Configurar operadores de servidores Enlace de canal SimpleX Enlace completo Enlace corto @@ -2681,6 +2679,16 @@ Dejarás de recibir mensajes de este canal. El historial del chat se conservará. fallo + Naciste sin una cuenta. + Nadie monitorizaba tus conversaciones. Nadie registraba tus ubicaciones. La privacidad nunca fue un lujo, era la manera de vivir. + Después pasamos a internet y cada plataforma pedía una parte de tí: tu nombre, tu número, tus amistades. Aceptamos que el precio de hablar con los demás es informar a alguien de quién es interlocutor. Cada generación, personas y tecnología, ha funcionado así: teléfono, email, mensajería, redes sociales. Parecía el único camino. + Existe otro camino. Una red sin números de teléfono. Sin nombres de usuario. Sin cuentas. Sin identificadores de ningún tipo. Una red que conecta las personas y entrega mensajes cifrados sin saber quien está conectado. + No un candado mejorado en la puerta de otro. No un terrateniente que respeta tu privacidad pero sigue guardando un registro de tus visitantes. Tu no eres el invitado. Estás en tu casa y ningún rey podrá entrar. Tu eres el soberano. + Tus conversaciones te pertenecen, tal como ha sido siempre antes de la llegada de internet. Tu red no es un lugar que visitas. Es un lugar que has creado, te pertenece y nadie te la podrá quitar, ya sea pública o privada. + La libertad más antigua del ser humano, la de hablar con otra persona sin ser observado, materializada sobre una infraestructura que no puede traicionarla. + Porque hemos destruido el poder de saber quien eres. De manera que tu poder nunca se pueda arrebatar. + Se libre en tu red. + Informes de suscriptores Se permiten mensajes directos entre suscriptores. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 5483becb91..da5dac16a3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -730,7 +730,6 @@ تماس پذیرفته نامتمرکز پروفایل خود را ایجاد کنید - SimpleX چگونه کار می‌کند مخزن GitHub ما.]]> استفاده از چت بهترین گزینه برای باتری. شما اعلان‌ها را فقط وقتی دریافت می‌کنید که برنامه در حال اجراست (بدون سرویس پس‌زمینه).]]> @@ -2183,7 +2182,6 @@ شرایط به‌طور خودکار برای اپراتورهای فعال در: %s پذیرفته خواهد شد. سرورهای SMP پیکربندی‌شده سرورهای XFTP پیکربندی‌شده - پیکربندی اپراتورهای سرور سرورهای متصل شده اتصال نیاز به تجدید مذاکره رمزنگاری دارد. اتصالات diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 5ea012dcf9..24634192ec 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -388,7 +388,6 @@ Miten Koko nimi: Miten markdownia käytetään - Miten SimpleX toimii Saapuva äänipuhelu Saapuva videopuhelu Sivuuta diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index 677bdeb9b5..383e4c49f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -447,7 +447,6 @@ Créez votre profil Établir une connexion privée Comment ça fonctionne - Comment SimpleX fonctionne Seuls les appareils clients stockent les profils des utilisateurs, les contacts, les groupes et les messages. GitHub repository.]]> Batterie peu utilisée. L\'app vérifie les messages toutes les 10 minutes. Vous risquez de manquer des appels ou des messages urgents.]]> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml index 3084b8569b..84e806dda0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hr/strings.xml @@ -90,7 +90,6 @@ Domaćin Kako utiče na bateriju Kako pomaže privatnosti - Kako SimpleX radi Grupni profil je uskladnjen na uredjajima korisnika, ne na serverima. O operatorima Skrivena šifra profila diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index c706dc8e8a..3b77d42277 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -624,7 +624,6 @@ Hiba történt a téma importálásakor Partner nevének és az üzenet tartalmának elrejtése Nem kompatibilis adatbázis-verzió - Hogyan működik a SimpleX Nem kompatibilis verzió Elrejtés Bejövő videóhívás @@ -2343,7 +2342,6 @@ A SimpleX Chat használatával Ön elfogadja, hogy:\n- csak elfogadott tartalmakat tesz közzé a nyilvános csoportokban.\n- tiszteletben tartja a többi felhasználót, és nem küld kéretlen tartalmat senkinek. Adatvédelmi szabályzat és felhasználási feltételek. A privát csevegések, a csoportok és a partnerek nem érhetők el a kiszolgálók üzemeltetői számára. - Kiszolgálóüzemeltetők beállítása Nem támogatott kapcsolattartási hivatkozás Rövid hivatkozás Teljes hivatkozás @@ -2715,6 +2713,16 @@ A SimpleX hálózat hosszú távú működésének biztosítása érdekében. Az új felhasználók számára egyszerűbbé tettük a kapcsolatok létrehozását. + Fiók nélkül születtünk. + Senki sem követte nyomon a beszélgetéseinket. Senki sem készített térképet arról, hogy merre jártunk. A magánéletünk nem csak egy funkció volt, hanem az életmódunk. + Aztán felléptünk az internetre, és minden platform kért belőlünk egy darabot - nevet, telefonszámot, baráti kapcsolatokat. Elfogadtuk, hogy a kommunikáció ára az, hogy mások megtudják, hogy kivel beszélünk. Minden generáció, az emberek és a technológia is eddig így működött - telefon, e-mail, üzenetküldő programok, közösségi média. Úgy tűnt, ez az egyetlen lehetséges mód. + De van egy másik lehetőség is. Egy hálózat, amelyben nincsenek telefonszámok. Nincsenek felhasználónevek. Nincsenek fiókok. Nincsenek semmiféle felhasználói azonosítók. Egy hálózat, amely összeköti az embereket és titkosított üzeneteket továbbít, anélkül, hogy tudná, ki csatlakozik hozzá. + Nem egy jobb zár mások ajtaján. Nem egy kedvesebb házmester, aki tiszteletben tartja az Ön magánéletét, de mégis nyilvántartást vezet minden látogatójáról. Ön itt nem csak egy vendég. Ön itt otthon van. Nincs az a hatalom, amely beléphetne ide - Ön itt szuverén. + A beszélgetései Önhöz tartoznak, ahogy az internet megjelenése előtt is mindig így volt. A hálózat nem egy hely, amelyet meglátogat. Ez egy olyan hely, amelyet Ön hoz létre saját magának. És senki sem veheti el Öntől, függetlenül attól, hogy privát vagy nyilvános. + A legrégebbi emberi szabadság - beszélgetni az emberekkel, anélkül, hogy mások megfigyelnének - olyan infrastruktúrán alapul, amely nem tudja elárulni. + Mert felszámoltuk a lehetőségét is annak, hogy megtudjuk, Ön kicsoda. Így az önrendelkezése soha nem kerülhet idegen kezekbe. + Legyen szabad a saját hálózatában. + Feliratkozók jelentései A közvetlen üzenetek küldése a feliratkozók között engedélyezve van. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 2257d93efa..6b3449f7b1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -619,7 +619,6 @@ Kesalahan saat menginisialisasi WebView. Pastikan Anda telah menginstal WebView dan arsitektur yang didukung adalah arm64.\nKesalahan: %s Gunakan obrolan Bagaimana caranya - Cara kerja SimpleX Berkala Panggilan suara masuk panggilan suara terenkripsi e2e @@ -2345,7 +2344,6 @@ Frasa sandi di Keystore tidak dapat dibaca. Hal ini mungkin terjadi setelah pembaruan sistem yang tidak kompatibel dengan aplikasi. Jika tidak demikian, silakan hubungi pengembang. Terima Dengan menggunakan SimpleX Chat, Anda setuju untuk:\n- hanya mengirim konten legal di grup publik.\n- hormati pengguna lain – tidak ada spam. - Konfigurasikan operator server Kebijakan privasi dan ketentuan penggunaan. Obrolan pribadi, grup, dan kontak Anda tidak dapat diakses oleh operator server. Frasa sandi di Keystore tidak dapat dibaca, silakan masukkan secara manual. Hal ini mungkin terjadi setelah pembaruan sistem yang tidak kompatibel dengan aplikasi. Jika tidak demikian, silakan hubungi pengembang. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index d65dc3b128..c296fd538b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -358,7 +358,6 @@ Videochiamata crittografata e2e terminata Come funziona - Come funziona SimpleX Rispondi alla chiamata Audio spento Audio acceso @@ -2379,7 +2378,6 @@ Usando SimpleX Chat accetti di:\n- inviare solo contenuto legale nei gruppi pubblici.\n- rispettare gli altri utenti - niente spam. Le chat private, i gruppi e i tuoi contatti non sono accessibili agli operatori dei server. Accetta - Configura gli operatori dei server Informativa sulla privacy e condizioni d\'uso. Questo link richiede una versione più recente dell\'app. Aggiornala o chiedi al tuo contatto di inviare un link compatibile. Link completo @@ -2751,6 +2749,16 @@ Sicurezza: solo i proprietari hanno le chiavi del canale. Abbiamo semplificato la connessione per i nuovi utenti. + Sei nato senza un account. + Nessuno monitorava le tue conversazioni. Nessuno disegnava una mappa delle tue posizioni. La privacy non era mai stata una caratteristica, era uno stile di vita. + Poi ci siamo trasferiti online e ogni piattaforma ha chiesto un pezzo di noi: il nome, il numero, gli amici. Abbiamo accettato che il prezzo da pagare per comunicare con gli altri fosse quello di far sapere a qualcuno con chi parliamo. Ogni generazione, sia di persone che di tecnologia, ha funzionato così: telefono, email, messenger, social media. Sembrava l\'unico modo possibile. + C\'è un\'altra via. Una rete senza numeri di telefono. Senza nomi utente. Senza account. Senza identificatori utente di alcun tipo. Una rete che connette le persone e trasferisce messaggi crittografati senza sapere chi è connesso. + Non una serratura migliore sulla porta di qualcun altro. Non un padrone di casa più gentile che rispetta la tua privacy, ma che continua a tenere traccia di tutti i visitatori. Non sei un ospite. Sei a casa tua. Nessun re può entrarvi: sei tu il sovrano. + Le tue conversazioni appartengono a te, come è sempre stato prima dell\'avvento di internet. La rete non è un luogo che visiti. È un luogo che crei e possiedi. E nessuno può portartelo via, che tu lo renda privato o pubblico. + La più antica libertà umana, parlare con un\'altra persona senza essere osservati, si basa su un\'infrastruttura che non può tradirla. + Perché abbiamo distrutto il potere di sapere chi sei. In modo che il tuo potere non possa mai esserti sottratto. + Vivi libero nella tua rete. + Segnalazioni degli iscritti Permetti l\'invio di messaggi diretti agli iscritti. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 1ef02fd128..faf69dfd03 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -482,7 +482,6 @@ הסתר פרופיל איך להשתמש במרקדאון איך זה עובד - איך SimpleX עובדת נתק עזרה הקבוצה תימחק עבור כל חברי הקבוצה – לא ניתן לבטל זאת! @@ -2095,7 +2094,6 @@ לשליחה צ\'אט אחד עם חבר הודעה חדשה - הגדרת מפעילי שרת ניתן להגדיר שרתים דרך הגדרות. אישר אותך ממתין לסקירה diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index a5c9f98421..db563e9ece 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -153,7 +153,6 @@ 通知の常時受信 SMPサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 WebRTC ICEサーバのアドレスを正しく1行ずつに分けて、重複しないように、形式もご確認ください。 - SimpleX の仕様 通話中 電池消費がより高い!非アクティブ時でもバックグラウンドのサービスが常に稼働します(着信してすぐに通知が出ます)。]]> 発信中 @@ -2014,7 +2013,6 @@ プライバシーとセキュリティの向上 承諾 プライバシーポリシーと利用条件 - サーバオペレータの設定 承諾 サーバオペレータは、プライベートチャット・グループ・連絡先にはアクセスできません。 SimpleX Chat を利用することで、以下の事項に同意したものと見なされます:\n- パブリックグループでは合法なコンテンツのみを送信すること。\n- 他のユーザを尊重すること、またスパムメッセージを送信しないこと。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 651d32518f..83f937db32 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -500,7 +500,6 @@ 설명서 내 서버 사용법 마크다운 사용법 - SimpleX 작동 방식 그룹 초대가 만료되었어요. 그룹 멤버는 보낸 메시지를 영구 삭제할 수 있습니다. (24 시간) 그룹 멤버는 음성 메시지를 보낼 수 있어요. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml index 0ea9328085..92985b15be 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ku/strings.xml @@ -215,7 +215,6 @@ Bluetooth Profîla xwe çêke Çawa dişuxule - SimpleX çawa dişuxule Çawa tesîrê li pîlê dike Her serê pêlekê Di cih de diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index aa2e2e46c9..bccd49eed9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -214,7 +214,6 @@ Ištrinti nuorodą Ištrinti nuorodą\? Klaida kuriant grupės nuorodą - Kaip SimpleX veikia Duomenų bazė šifruota! kūrėjas Ištrinti grupę\? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml index 0f192b284c..d673e80394 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lv/strings.xml @@ -1566,7 +1566,6 @@ Izveidot privātu savienojumu Migrēt no citas ierīces Kā tas darbojas - Kā darbojas SimpleX Lai aizsargātu privātumu, SimpleX izmanto ID rindām Tikai klientu ierīces glabā kontaktu grupas un e2e šifrētas ziņas @@ -1591,7 +1590,6 @@ Ievada nosacījumi, izmantojot jūs piekrītat Ievada nosacījumi privātuma politika un lietošanas noteikumi Ievada nosacījumi pieņemt - Ievada nosacījumi konfigurēt servera operatorus Ievada izvēlēties servera operatorus Ievada tīkla operatori Ievada tīkla operatori simplex flux vienošanās diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml index d21b8b8f83..19aa92a4a0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml @@ -353,7 +353,6 @@ സ്വാഗത സന്ദേശം നൽകുക... (ഇച്ഛാനുസൃതമായ) സംരക്ഷിക്കാതെ പുറത്ത് പോവുക ഇത് എങ്ങനെ പ്രവർത്തിക്കുന്നു - SimpleX എങ്ങനെ പ്രവർത്തിക്കുന്നു കൃത്യസമയം പ്രവർത്തനരഹിതമാക്കുക എന്നതിൽ ഇല്ലാതാക്കി diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 4ab2b10904..331ef12e4d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -424,7 +424,6 @@ cursief Hoe het werkt gemiste oproep - Hoe SimpleX werkt Inkomende audio oproep Inkomend video gesprek Negeren @@ -2380,7 +2379,6 @@ Volledige link Niet-ondersteunde verbindingslink Korte link - Serveroperators configureren Privacybeleid en gebruiksvoorwaarden. Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders. contact verwijderd diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index cc8b369386..4bb9626694 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -426,7 +426,6 @@ Możesz używać markdown do formatowania wiadomości: Utwórz swój profil Jak to działa - Jak SimpleX działa Natychmiastowy Jak wpływa na baterię Nawiąż prywatne połączenie @@ -2230,7 +2229,6 @@ Czat z administratorami Czat z członkiem Czatuj z członkami, zanim dołączą. - Konfigurowanie operatorów serwerów Połącz Połącz się szybciej! 🚀 kontakt usunięty @@ -2557,4 +2555,13 @@ Połączenie nie powiodło się niepowodzenie Jeśli dołączyłeś do kanałów lub je utworzyłeś, przestaną one działać na stałe. + Urodziłeś się bez konta. + Nikt nie śledził twoich rozmów. Nikt nie rysował mapy miejsc, w których byłeś. Prywatność nigdy nie była funkcją - była sposobem na życie. + Następnie przenieśliśmy się do sieci, a każda platforma prosiła o podanie danych osobowych - imienia i nazwiska, numeru telefonu, znajomych. Zaakceptowaliśmy fakt, że ceną za możliwość komunikowania się z innymi jest ujawnienie komuś, z kim rozmawiamy. Tak było w przypadku każdego pokolenia, ludzi i technologii - telefonu, poczty elektronicznej, komunikatorów, mediów społecznościowych. Wydawało się to jedyną możliwą opcją. + Jest jeszcze inny sposób. Sieć bez numerów telefonów. Bez nazw użytkowników. Bez kont. Bez jakichkolwiek tożsamości użytkowników. Sieć, która łączy ludzi i przesyła zaszyfrowane wiadomości, nie wiedząc, kto jest podłączony. + Nie chodzi o lepszy zamek w drzwiach kogoś innego. Nie chodzi o milszego właściciela, który szanuje twoją prywatność, ale nadal prowadzi rejestr wszystkich odwiedzających. Nie jesteś gościem. Jesteś w domu. Żaden król nie może do niego wejść - jesteś suwerenem. + Twoje rozmowy należą do Ciebie, tak jak zawsze było przed pojawieniem się Internetu. Sieć nie jest miejscem, które odwiedzasz. Jest miejscem, które tworzysz i które należy do Ciebie. Nikt nie może Ci tego odebrać, niezależnie od tego, czy jest to miejsce prywatne, czy publiczne. + Najstarsza ludzka wolność - możliwość rozmowy z inną osobą bez bycia obserwowanym - opiera się na infrastrukturze, która nie może jej zdradzić. + Ponieważ zniszczyliśmy moc pozwalającą poznać, kim jesteś. Więc twoja moc nigdy nie będzie Ci odebrana. + Ciesz się swobodą w swojej sieci. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 133f65edb2..ca0ec5f2ba 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -417,7 +417,6 @@ O arquivo será recebido quando seu contato estiver online, aguarde ou verifique mais tarde! O perfil do grupo é armazenado nos dispositivos dos membros, não nos servidores. ajuda - Como o SimpleX funciona Servidores ICE (um por linha) Ignorar A imagem será recebida quando seu contato estiver online, aguarde ou verifique mais tarde! @@ -2376,7 +2375,6 @@ Os servidores para novos arquivos do seu perfil de chat atual A conexão atingiu o limite de mensagens não entregues, seu contato pode estar offline. Mensagens não entregues - Configurar operadores de servidor Chats privados, grupos e seus contatos não são acessíveis aos operadores de servidor. Aceitar Ao usar o SimpleX Chat, você concorda em:\n- enviar apenas conteúdo legal em grupos públicos.\n- respeitar outros usuários – sem spam. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index 11ca93be3b..d9af79fe96 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -1504,7 +1504,6 @@ Ieșire fără salvare Parolă profil ascuns italic - Cum funcționează SimpleX Fără identificatori de utilizator. Acest lucru se poate întâmpla atunci când:\n1. Mesajele au expirat în aplicația de trimitere după 2 zile sau pe server după 30 de zile.\n2. Decriptarea mesajului a eșuat, deoarece tu sau contactul tău ați folosit un backup vechi al bazei de date.\n3. Conexiunea a fost compromisă. Eroare la exportarea bazei de date a conversației @@ -2283,7 +2282,6 @@ Ai partajat o cale de fișier nevalidă. Raportează problema dezvoltatorilor aplicației. Deschide în aplicația mobilă, apoi atinge Conectare în aplicație.]]> Actualizează adresa - Configurați operatorii serverului Condițiile vor fi acceptate pentru operatorii activați după 30 de zile. Apasă pe butonul de informații de lângă bara de adrese pentru a permite accesul la microfon. Colţ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 067174ba0d..64ca9c0711 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -473,7 +473,6 @@ Добавьте контакт Как это работает - Как SimpleX работает Чтобы защитить Вашу конфиденциальность, SimpleX использует разные ID для всех ваших контактов. Только пользовательские устройства хранят контакты, группы и сообщения. GitHub репозитория.]]> @@ -2459,7 +2458,6 @@ Принять Используя SimpleX Chat, Вы согласны:\n- отправлять только законные сообщения в публичных группах.\n- уважать других пользователей — не отправлять спам. Частные разговоры, группы и Ваши контакты недоступны для операторов серверов. - Настроить операторов серверов Политика конфиденциальности и условия использования. всех Принять @@ -2754,4 +2752,13 @@ Вы можете поделиться ссылкой или QR-кодом — любой желающий сможет присоединиться к каналу. Изменить настройки канала могут только владельцы канала. Одноразовая ссылка + Вы родились без аккаунта. + Никто не отслеживал ваши разговоры. Никто не составлял карту ваших перемещений. Конфиденциальность не была функцией - это был образ жизни. + Потом мы вышли в интернет, и каждая платформа попросила частичку вас - ваше имя, ваш номер, ваших друзей. Мы смирились с тем, что за возможность общаться приходится отдавать информацию о том, с кем мы общаемся. Каждое поколение людей и технологий жило так - телефон, электронная почта, мессенджеры, социальные сети. Казалось, что другого пути нет. + Другой путь есть. Сеть без номеров телефонов. Без имён пользователей. Без аккаунтов. Без каких-либо идентификаторов пользователей. Сеть, которая соединяет людей и передаёт зашифрованные сообщения, не зная, кто с кем связан. + Не более надёжный замок на чужой двери. Не более вежливый хозяин, который уважает вашу частную жизнь, но всё равно ведёт учёт всех посетителей. Вы не гость. Вы у себя дома. Ни один король не войдёт в ваш дом - вы суверенны. + Ваши разговоры принадлежат вам, как это всегда было до интернета. Сеть - это не место, куда вы приходите. Это место, которое вы создаёте и которым владеете. И никто не может это у вас отнять, делаете ли вы его конфиденциальным или публичным. + Древнейшая человеческая свобода - говорить с другим человеком без слежки - построенная на инфраструктуре, которая не может её предать. + Потому что мы разрушили саму возможность узнать, кто вы. Чтобы вашу свободу невозможно было отнять. + Будь свободен в своей сети. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 2fe2000fb3..c355d8d9fb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -376,7 +376,6 @@ วิธีใช้มาร์กดาวน์ สิ้นสุดลงแล้ว มันทำงานอย่างไร - วิธีการ SimpleX ทํางานอย่างไร กระจายอำนาจแล้ว การโทรเสียงแบบ encrypted จากต้นจนจบ การโทรวิดีแบบ encrypted จากต้นจนจบ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 16d821637b..32672e365f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -611,7 +611,6 @@ Gizle Konuşulan kişileri ve mesajları gizle Uygulamayı, son kullanılanlar kısmından gizle. - SimpleX nasıl çalışıyor bir görüntülü aramada karşıdakine karekodunu gösterebilir ya da konuştuğun kişiye bir katılım bağlantısı paylaşabilirsin.]]> bir görüntülü aramada karşıdakinin karekodunu okutabilirsin ya da konuştuğun kişi seninle bir katılım bağlantısı paylaşabilir.]]> Eğer yüz yüze görüşemiyorsanız bir görüntülü aramada karşıdakine karekodunu gösterebilir ya da konuştuğun kişiye bir katılım bağlantısı paylaşabilirsin. @@ -2270,7 +2269,6 @@ Arkaplan servisi yok Kabul Et SimpleX Chat\'i kullanarak şunları kabul etmiş olursunuz:\n- genel gruplarda sadece yasal içerik göndermeyi.\n- diğer kullanıcılara saygı göstermeyi - spam yapmamayı. - Sunucu operatörlerini yapılandırma Sohbetten çıkılsın mı? yönetici Bütün sunucular diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 6d498ef4ed..61f64df2fd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -407,7 +407,6 @@ очікування підтвердження… Приватність перевизначена Ви вирішуєте, хто може під\'єднатися. - Як працює SimpleX зашифрований e2e аудіовиклик Відкрийте SimpleX Chat для прийняття виклику e2e зашифровано @@ -2376,7 +2375,6 @@ Приватні чати, групи та ваші контакти недоступні для операторів сервера. Прийняти Використовуючи SimpleX Chat, ви погоджуєтесь на:\n- надсилати тільки легальний контент у публічних групах.\n- поважати інших користувачів – без спаму. - Налаштувати операторів сервера Політика конфіденційності та умови використання Це посилання вимагає новішої версії додатку. Будь ласка, оновіть додаток або попросіть вашого контакту надіслати сумісне посилання. Повне посилання diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index ae22c40277..21e2e43a44 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -880,7 +880,6 @@ Hồ sơ trò chuyện ẩn Cách sử dụng Cách thức hoạt động - Cách thức SimpleX hoạt động Cách làm Lỗi khởi động WebView. Hãy đảm bảo bạn đã cài đặt WebView và kiến trúc hỗ trợ của nó là arm64.\nLỗi: %s giờ @@ -2351,7 +2350,6 @@ Bằng việc sử dụng SimpleX Chat, bạn đồng ý:\n- chỉ gửi nội dung hợp pháp trong các nhóm công khai.\n- tôn trọng những người dùng khác - không gửi tin rác. Các cuộc trò chuyện riêng tư, nhóm và liên hệ của bạn không thể truy cập được đối với các bên vận hành máy chủ. Chấp nhận - Định cấu hình các bên vận hành máy chủ Đường dẫn này yêu cầu một phiên bản ứng dụng mới hơn. Vui lòng nâng cấp ứng dụng hoặc yêu cầu liên hệ của một gửi cho một đường dẫn tương thích. Đường dẫn kênh SimpleX Đường dẫn kết nối không được hỗ trợ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index b189ee581d..21811df621 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -618,7 +618,6 @@ %d 小时 %d 月 %d 秒 - SimpleX 是如何工作的 确保 WebRTC ICE 服务器地址格式正确、每行分开且不重复。 确保 SMP 服务器地址格式正确、每行分开且不重复。 Markdown 帮助 @@ -2364,7 +2363,6 @@ 接受 使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。 服务器运营方无法访问私密聊天、群和你的联系人。 - 配置服务器运营方 不支持的连接链接 SimpleX 频道链接 短链接 @@ -2734,4 +2732,13 @@ 在社交媒体资料、网站或电子邮件签名中使用该地址。 我们让连接对新用户更简单。 你的公开地址 + 你生来就没有账户。 + 没有人追踪你的谈话内容。没有人绘制你去过的地方的地图。隐私从来都不是一项功能--而是一种生活方式。 + 然后我们转向线上,每个平台都要求你提供一些信息--你的姓名、电话号码、好友列表。我们接受了这样一个事实:与人交流的代价就是让别人知道我们在和谁交流。每一代人,每一代科技,都遵循着这样的模式--电话、电子邮件、即时通讯、社交媒体。这似乎是唯一可行的方式。 + 还有另一种方法。一个没有电话号码、没有用户名、没有账户、没有任何用户身份的网络。一个连接人们并传输加密信息的网络,而无需知道谁连接了。 + 别人家的门锁再好也比不上这里。房东再好也比不上这里,他既尊重你的隐私,又保留着所有访客的记录。你不是客人,你是家。没有国王能闯入--你是主人。 + 你的对话内容始终属于你,就像互联网出现之前一样。网络不是一个你访问的地方,而是一个你创建并拥有的地方。无论你将其设为私密还是公开,任何人都无法将其夺走。 + 人类最古老的自由--与他人交谈而不被监视--建立在不会背叛它的基础设施之上。 + 因为我们摧毁了知道你是谁的权力,因而您的权利永远不会被夺走。 + 在你的网络中自由畅行。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index 05997a9fec..9ec116058a 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -847,7 +847,6 @@ 感謝用戶 - 使用 Weblate 的翻譯貢獻! 正在修改聯絡地址為 %s … 受加密的資料庫密碼會再次更新和儲存於金鑰庫。 - SimpleX 是怎樣運作 當發生: \n1. 訊息將在傳送至客戶端後兩天或在伺服器內三十天時過時。 \n2. 訊息解密失敗,因為你或你的聯絡人用了舊的資料庫備份 \n3. 連接被破壞。 只有客戶端裝置儲存個人檔案、聯絡人、群組,和訊息。 請放置你的密碼於安全的地方,如果你遺失了密碼,將不可能修改你的密碼。 @@ -2061,7 +2060,6 @@ 停用自動刪除訊息? 刪除或審查最多 200 條訊息。 於網路和伺服器設定中啟用 Flux 以獲得更好的元資料隱私。 - 配置伺服器營運者 使用條款 更好的隱私和安全性 你可以再試一次。 diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/intro_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/network_commitments_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_network_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile_light.svg b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile_light.svg new file mode 100644 index 0000000000..cd6f033c62 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/assets/default/MR/images/your_profile_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/ASSETS_LICENSE.md b/assets/ASSETS_LICENSE.md new file mode 100644 index 0000000000..36ac54d04f --- /dev/null +++ b/assets/ASSETS_LICENSE.md @@ -0,0 +1,18 @@ +# Application Graphic Assets License + +Copyright (C) 2026 SimpleX Chat Ltd. All rights reserved. + +The graphic assets in this folder, subfolders and in other folders of this repository - including illustrations, images, icons, and visual designs - are proprietary and are not licensed under the AGPLv3 that covers the application source code. + +## Permitted use + +- Unmodified application distribution. You may use these assets as part of the SimpleX Chat application, provided the application is not modified in any way. +- Publications with permission. You may use screenshots containing these assets in publications with prior written permission from SimpleX Chat Ltd. + +## Not permitted + +All other use, including modification, redistribution, or incorporation into other works, is not permitted without prior written permission from SimpleX Chat Ltd. + +## Contact + +To request permission, contact chat@simplex.chat. diff --git a/assets/multiplatform/resources/MR/images/banner_create_link@2x.png b/assets/multiplatform/resources/MR/images/banner_create_link@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6f768932ad2df573fafbeef767e5cc429413e5ff GIT binary patch literal 29172 zcmV(~K+nI4P)N{`|IO?F%<2Eo=>N>>|IO(C&glQo=>O30|IhFL&Fuft^8eEF|IFwA&+h-#_y5!M z|J3&X(eeM*`2W!H|IY6J)Aj$?`TyAZ|I6wB)b;<-=>N^<|IF+E()9n%?ElW^|JnQh z|NsBc=l|RO|I+gR(dYlm=l|9A|IqLM&FcTp@c;k*|NQ>{(ewX8M#sm_y2XW{~}TUD`Wr9?f*1& z|19wTvDg1kzyCe;|I6zCLe~GR|NbLY|4_OA@%aCy=KrMf|7Ga^(Chzh*Z*Gc|2c^N zS@!>Xy8kcg|7ec>Z>9exS^w<(|2xG0MBV>9`~P;H|AeOf#O3~_?f=*7{~AXBa+m)= zzW+zC|3kR{PrCm!Yze^#4=- z|Ig|Fr2PL#p8q=C|2WY9V#@z=@c(qs{!G#SiP8Uz$o`+f{;$mZX`KHu*Z;ui{{t@i zpw#}ki$oL@PFa|o3G5w?*DLfmB;G-$n*bFb_N z%hKt3inqV*|9O6&&g%bAe*TG!rZ-#kVTjGp`2S;Jg4Ou{3;$Pu001BWNklV1c3qpLJ$L_43LN-F<=pQ7a8>u7~IwKTz_^MY|_uoUr+nfsZ(|AzGUM# z=5s!eIjlKXYvA{1$cIlk3}u<)(_Hw#H?0)jsS#5ta*R6;9Mr)ezNuSeH zthZ^s6>DKG^UFFFX5nME>Y_Glt84Z0*YYs;Vy;L1SjR<$@Y(+sdjzAXg!(gfL?{A)tOt(kV!Bc}duICf5$Q@xp4};?uOxy?k zB5(wV!7<`1%X0HTm1H}$$odeLVHnE2P6nkF5K7xRD0Jc7gJOVBd$VhD@9AdrLL^oV zWb@zKRu@~Ciy~y8JP1p@SBd+8pK~H8m?!)^zbocP)IwN9H(@vo$CHD(9| zk<2lr7?~g#sA8&im8C6RP9?wu05K4jimPG0f?{BRN=)5ajbNrw=tLK`-lcE0~IqnLN4h_qG`#8#zo^DkK+R0sgWC&82(9 zs@GG!St!WHC(}jPzz#d8<&7w0UyI##g9#C%@d9&RdNTM;#vg`kMrM?C+*P#)ikSa$~E=q^80$U>I# z*Ik#&EYe7og_Z#VYB9Q*xsdrSnEXm5EdP9V{Z~1(t(c3(K{~;!WhS>ifQ4HzBasQ; zY%LKuHI}iuC}Q5SSh-jxwP!J%Hhc@^5uSV%QsDIb)}P_krN+lIC5Fy_sbOYEg5o4<*-gu$##Wj*V{ObOfAUZ93;Ma1qG z@E7y2J=hX*{Z1+7#P6R>ru(9pA1jz=%wP-zDb& z8b;3I>e$X%O6@Ba%nhcN)+3_6SL}|#S=OjbYU?pl#ezy%L@(zBjN0NyckV9Ihglq* z;?nIsORw}QKiLs1Oyx=lW%stOXk;og0nEC5}xGDCa45b<+ zwc*<~j3yW2G5OXq(QHXWC1Z)zFeqXMN>S8jmJj{0u3gKa59=PGX);j5kV^O4KI~Ay z00?TNVKRpU z9#F>6SprZtH%ivjg~i4eY`?IS#jO6}4=WG#^*m@sWYPU*y!YJxk~B%xBuoWxG^7@{ zVs4MUvEf6eGDd2cv|^wZQpunveCsMgr0-P^*1EBU=Xy7?to&vAu+KeSGKyiT)2EaFNuMXBuIp@kYKb{q<#erA7hu1`>@dl zV&i^bL3`me%&%a!4J%>3FMB=V^KF>~9e<_h#lWD3?5u`?>DR^SfM3@SGL|~3md+(F zr;V|lC&Mf{uQSHW0O8fZrIdndF)okRLI#sjiyUmja+q4UJJxyFuzQy|kM|#{T;{v= z1yg#(%QeTw=`p3e^(qGwJi-Lza`S1dn#QJb%80KlA{X#`^n3ub#ISUa-;69ar|iBM zivjXhNYF~0RIq|o4pD|$IS8fV+1}hn9x%IrKw|SGRIi+Z7xWV;W3Bf4o!Rra0vC9F zd)wv5ULPj2*3W)A9j5J{v7BQDDD8(YAt zeE}P}L7tIL?zURyBb;);ak7+Qk`v=n4U+9K661{)3HC#=%Vb+IUBaeFP`NoK+ZKN&>54rGT?k(im&Y&jgE~yNqgP;frP#PW6vd3)l&|ea>U# zA)z#*{XVDcsT~u$cWGrvG9;TR-i1j@N$E>4$QN&cK))s`PA!W+w(#ikWs`+t${mpR zD@{QoDJkXc>qV)hf$mtkG1r4NnW|tSnDHQ*kwVG^7{FNKAXaz$%q4>ByYsT2>{oSvF-*=D4FQj6zz~hp>UhgzbUrG9YCmxVD z18FRp#)#jILvAv1sbCJR(D91eJLNtnCTWq(Cd9%2V+5IoZ`>sz?`L?c`OzYQ8pfuNeeqE4;;=U4EYKLYvRB-;feBwtEh~k` zx8mD^)J1Q#6o>wJk?7dE%W{nKe<_u&yQH*Uco{UW)F|da7Hr2ZSA*e7L}Vp`$N+g$@$yAOe*aD?luOby&h;@j zwtzBKy#h1kh|x=qim_|-%NS0OpAY7gLwH-#-)GsUlpIv5A~x+jUk3c?q=MlV%lUXu zn=Fgk0Sj$Oe#3G>8YxpzT1gFs7bxXz9==K%HLaLcC7xP%#+UL{sn|HU)skZ|8)44R zqg#`-EBV38-^3q|RC)^ZEf#g)rQfHNbgY8&7Huk1SSl{i-}X`1V*4?>|HYg6yR$Bo z=SOPTMMzw)X=n)^-|Lg3%c$6e1%p~qIpvPW=+BcBy69c}V~UI+rMb(GJ@?`jFjd6* ze%B&PmM^(We?0TTXDl#!Iqii%lr|;K(w8y%;Dr8YI_H5=KW} zC=x4Z8*Gc_l+IU{SQ};(a=+ghioC5(f=pgi&Ok2jH&?Nj2c^-HX*ha;N8%bV?2qk6 zaiu1gnH&Y4aF7xN6CNg2Cst}M{AgvXr5#)mQwF1s?Wv8H9h!Bed1ab75nBVE?@E|6 z`t3MWF1Ax996@WjAP+=xr_ZZe&g-v7hcQzmO_PR8Fb->+-*ay)k+Cgfq!f4N<>31W zCgX_(on;)uGrr68KWbNUI9LmpF(nyqS2C;>o+OJ^@~|noAxUQ|is*sxMfO@``9~=) z_xqne#KI}%%5s4_y?cG}b;sq+cAtViaC8Z^V0uH+vC;d)Zjg+fcNx*{$1D^YTT}Vu z98ju#mU8e;BFQGcHGDou*?Kd}EpkANJfCE;#>YrFd0d=zVKpAp#~ovTzP|vDe|N$n z>v~U_jIx)zXDzS2U?8M?lebtL@1wmmvd^O1F@Fa?xGhP63o8btGTKgT^m7-QCG9SY zK3?G+KvqY?T79zOFtr5Cg`;Em?}^~#vWde`c3i}dyDW6JLIa_7|AR;f%Ri}OEEMK) zcNHu!w!CD>hxEmgc!f(@lZf9Zp4;0UmzY>aI;+o-%gH}au)75QnZ=GR?cw~f-o&Tv z;`k|7+>xJ$rlHNq1;I@f9~QZ)?fnG5`Y2|3)sFH?%y=% zI}U+v*&TRED%mhrIi+93s)J}|K17K!nFIfDL;$#sN> z?qP!Io}qVkm>#%+o=)7bJ&lL$z<+`T8#z$0IG9`<92;D)iNywTAQT-Kd=8x$R)Z9tb`s%B?ji(;NHj5cco}N7- z9UEW8Fu{zHJ_bpj5T_(1mR|{$Vb=%Z&B6F49n>w1hbSm&g%PRf-=q+Ifc z;1%0X#$hfcpLK-C`FeLnzgkv!V>qLR9`$(R*Ff6uOo&`qA$O47Nq-o+I zVmiq=EKIUQc?g9U50k~lwv%?-C`S2^#7@2L{&}oGeju_B3gb~-_Y@Je`l!>2N&hWkw#mT3%jB=<*X z1^8rc9IlSLVlCHMz)h)_iY=!&vzARXiNW?NmdDS}sN0vX+5qrC3xq|a*w zO6~-P_Z1N#Sg3b#Iu-t&8M+_T!$3ebI@mOWRMthcN^5s9^Dog+t~i4QEq+7Riuu0T z?~j$nTN;8HZc#uPE9F{lwYUaXF=vU8%7*A(q48WZ%PEA&>0`5Eg~%w+>l}gvo(9JT zNHGWEhb(>q3u_UbpW@{4U22QXByJM6##!m#CnW1vL=%QZcVPEQl&EjLtIW{dwE{#0-MDrAc}#auK@u_QV$IWnOtLm zSxn`rS`=blA}dYjQWC#d$}dbF;}Wba5se}~m29ydw#MUV8iS67F|oMd3hT_y!2~3Y z5Gd^;>!5agkUpdZ?R;;y?*1yV{RUM1g}}rgOGXkH3?$E>I)(^Q(Sbi+((;If!0B_{ zuK+M{4NM>du|k8$vJ_zgpp=2V4-C`cUHZ()(h^BCD$A0B9cjB(Gl1Q?0;c|q-mAY7X?XL zr3g&w6;OL7n@41Lg8g4HjHF3 zHgg=6t__K9BGCkFV8~*#7=;SUm1`R=kzP`NJ2h9t;^VKgQRM)_ut2z^sZaJbGChWi zLMMT?1vA(%D%Uvu(mzF+&}=fnIZJU}+UnR0z=nfHHI>u0bzFNSiv{an6eBZf(x+lf z4kee7Jyv~1M5nOh)35mgntW*P4}g!#DYs(ld*LekbUADx?5)CktUJlIlfy>oF0Cf1`qY_?8AZW(?kz91s)kh5aCveV+J$zfJTmcy3DL8TEZ zV0%957Pste+3+&w`uGrQTI8hI>iKK*QrgMnMyISQo$Lq*9}~`81P-AT)?)xjuM18-ipVi<`x|&AdN{#9O<}-wvk9H=y}0@1*+q0a5jm7V$LJ1WM>~5EA|!?P{Q_m z;QYZ=Bix>FS6CPEyKWn2`b7&zd`v_|NR{rY3In8a1H^yKCbk-@gG{DzNi-;+ASeA$ z3d4vgknoejRAPnO*fXyb(&5^M_(0d9ZTeG`!)zbf`fhNS@Lemo;|Oo8Z4W=e9p>z? zx@l|yV-_fl58^z6a|6+gzcwW7TW=VyN{%LzNjjM*FaVEAKM^feKruiq)ex=F_Z~8u zu8ei8)JIfGQpHN*7^6{gH_0Gn)?g8Rdhn$PA8oEs9S@B1S7xV0F5=7G;(=ur2o*YA zp<8@AZQ08$uq|KOQ=rW0u#2|=wwC7+TyMpCJ(}!PzN#wqE1du;I}Q~<#=zr-2_cgD zypra&F3uCmV2PlXA*C{b{H161`rYdX5%dRR8(_m@Nz}on(hSb4 zMywsrR>PvcmM6hw5i0syF=#okF>DLcabT^m)$zh~`J0i#V?wXmO%yPd1_)SQ1GDp@ zBo}m_hNL3GrO&xWCKV=m{J{uJp>}bp-gRF(C0J&XS%x_*RvZJTbfCHrAgwcLS$X#n zR-_v**>|iW#75TCn))9$7UK8+ioFN~{-qr$prKL-bWN8XNBK_i;No%g6KBb#PiRGJqf| z_G_hq!4eFT*hF$l2#Jwe^!GZ_v~XpN2=%=g7uN!dGU=E1-~H#`A7A|T#V@aJZ?9&T z{e4MfLaj?ukTa~5)mUa{Ernl$QNZX9-xS8aBDO+{{&FdHC8rh`s~#U47^796%P-kR z?!64=N|aj>CK{0OB=R&MicfSd+W{~;g^KzH)Z(VFDhQV}50o%VCp}LmA!fjR#MN2KYWNPJ$;;;}_QCJrc-j1v<&sV01##dcI6?{+du zD*G$<+m>+l#n1pKI5vhPmS5ce`tX!w8e*)IaFPheT-7qMq#X8@wVy3zJ6<>C?Gm@KY}A@pb~_^B`q; z@v=jtQYgb9b?u1X6+U^`Oj>G*WG_1@rU4j^jQfi>FF#)W@lRj9I|n%4>p!=*ib~Ko zRthI2Ca|O|y(Oa(m3=D3ir7wZ3qwiJ#v~YRX9C3;=P!d@Nk=)38!7jEs0uz~#9~`~ zbBG!+8I31HNoxojh0YErrP@`MfnrxxX}r-Fxhqm5*EXSDyt(`K*DwG3)9=6Szj%vq zAS74Ii@x_7N)jm%k)$xNSaU@t`QF=T>1bOLtom&>w8q*^<*---Vk9vmQ@ReyrJKOm zN{N?T{S#FO&wH#v8BNAx!ecCcj=mIXF~d^;MCH3S-!@mTQ5*kKkgmF>7v*59CE(Rwi| zFTOY#sxKs0Lcr9zccEG2#hceZeDTfA&6~eJd-j*R&pv(mielva{MJy(KsTw%uEi)vX*-VX;y3h99l*_sW;USdkH(*m;V~Ep z5QFhZu_ZDnOn+`|5|!zW?*Ni4vfa zycgn2=}o|d#nlWXoodXoN?Z|ZjfmV$2Zb?ORsBd=7&?;|wyueHDgKW5V(Jy8fe znT(%|#|(@K8G{J_SKHA~{Gv=(#ghq=$)vJUe&rWp^qr&^1qBP0)y3;S-2L)@#;zX3 zt?UZFr}q$HBx4mqB3y!aAV?u;#_U^+P^VIMvn~c4rc)DCc2CeY=5;#U20~vm`NfWF9wxX(Q=I9LaSiJ zl14>^-`Vlnl-{aj$4ps7>CzUKO4|H}6A3p=y<*%*5V8;rMUd#{4FxccVU8~)Y#9y( zF#MC$QS6Byb$Z2~SXj1%HNFPq_{afrcsRZIn&OrxC3)?t34YCbDanImP%-`EhIb^P zxcF6YiVdutveba=jauA2kxs|oEiO2a$l(e!x#4c_QN1S z2AmQuS;XASK6^sL!!KmW^62Q?0y3L}l)OAPkeq2qzTgx%ziM9GmCg!Mm6cF?=b{wC zQc|_xwl|h74gd6Er)sBVr`>MLtCa1+u2Dhie`(tKbROZxjfk;O%0Q#)N?DLBBu?l{ zPyq}$m<!sXLVU}9Zq7UYZ#;96ajNy34^j*x%nERC1+MVJ7S2=Eh94yk` zzEXl1ie5L4H#P;M5SB_nHq|l@jWv z+{Y3oM<)`uj<3{}71)lgx)@71Da*=VIr19$Avik_#DFY(0&%A-jJXZgw!C)i9ia{j zm9`0CCS*kvu2|Cv6^27eo|sUkAarf6uIPP?dXR~MxC}9J*-WhGF~kBgpU-Dwf@J>S z`~AmDsqQ)j=H#r#_7_x^EVsInS8xW0l8_>=SnS131TTyk$sd$VB~e?@RWT5|M*bws zD2uyA-EPZNtRs?R`G{Ox5{Z_5pW1kqnu5h;$F9WRq}5y*O4-NuF-^bfVjzjc$=>|e zg{&D!$c!O59BV0g;Yo>3fqTu|+n@j~X;PQ6tcJ`vKQ=@$Zc1T7a&F45Gl^AL_6FuC z5?EZXSjCrzUNi=#HHOaP&Nbh74ERVuBrp-F+6p?7H|8dr2FQC6^9nNT20 zaPp!7A%?tSh5#Il$sAMQ*XL47Fm5?JI85(h`UP%R;nB~WmgK=YYZnbkEpHFst{a(D ztlJP5_XdS~NaR8qp_{VggsVTrM}a|}?6iYKj1WRVB8XNX?NRD80~0T$EMe*nF#2&x zaT?}eX?49~-(lleV=<&Uve&y041tjKPYhTZkjZQ^narG&JO(K_6H=0R+h5LroKp(c zbjp&K>pgXhsVC+j5ywQhcyYSo0$Hmu5*UrBVi^TC{j$Y8mw@qRF<+^|=mmIaP-2zF zfQt)TN*Q!m?^*Lxg9c)xgu1wL-ph~ELQ~(h!g|!ury!O&KxU{Q6NF^UfeWSN#nM3n z-IOPZ>Ejnxz;M)pt408fAH?#|@?v+s-=v+r001BWNklO4o&p|H!0nqzIc*p8vHz}~rv86Z_qs0i&+6_2StLUoAetc-l*94G zJxYPks8G}?@ZdJkBMHrVXnLU~);#A3mQ|LLkbtf5b=4KaVsFU8&~#gshj&Qt$%`}^ z&>6&FgEd%d=~$h2hear4(U%$NM{IGx=SXH-GW8M&kiaMt#Oi`ErC_ARyci!2ki|mB ztGPx39{PDan^-B?mr{ap>r*eBNhZGK7@Vy@6ZE&{v_Vl?wRF3-HAZ6=uN5Y)?{r(O z5*O0I-VUP_8E=WW>}ynddS}O;cDsh0i7;Z(4-!SOrrUHFaj-N|F)KX;vdAT0GE1(@ zYSPCbm9>3PNg6<25g_9UhAjL$L0F1X0%P|tblmE(zoK;E$ihvk#0#7l( z5{cxizyzC(1>iW8sE@2sH#7$@Y3}O-Y|wsRkWDb@U-4R6Nv}r zzi|{t7NwMuFb-dl07R&L1f3L<8%N5^nXykPa^t#6#q3_D#~T3T`22jS0a>p#9t6mA zLd6JSVpuXid~UlbXZrEQv&+t>z#e8sU`fGRk@?;V?7+~C1Y1p3HY?4XxF*h4wERhn zSjDe6yn{z8kR!IpAC&`=N^I!6re9P35CWL%*mKi9V!5|Tmu(v>k$qVvi7~Mm8^g2u zBmsdUeY@Q*7K`n6LKWhK5dpoYZpudu3E}}^>py=fHj<4`%u&Q_ePyB&YbT3TMR*H!2Gq{V06Igo+Y8G4LQS^xv2u0y-%1xyX&ZF_&_0 zWm9aJ%2|s>r3lF4?#0E)T0=53u?yb-CbO|lfnR>}`ke5%)QB{OJ%4?o={8#Ylvorl zb}z5iw5MKdiJw>I$Fzx0nK5ljy6HU!vxXUgl`gYj5;o!n^1v?U4T^vmSSWId9E|7! z3;~kZkQLcxu?hh3#YEJV8=HeEGc4ACXokE$UM>lctp;SfzIe2|d+VnQ4GF`+h)e*k z38ui`K%IDb`R?7j=a*019IRKq0ajvhwF~&hrJr%-QmZ&yK|5c({l%6_g2m3iyqdDX z-Q+5RE4acMcpn@(`lCQY93nA%^ZZ%rctVMoq6Sz>>2a(qG#*O)dt@t@R47ZTM9&4M z#{!Ut3tbE@PHy%0`n$XRpH9|Wf@DTH7mx%1(kbw_Kl=gkc=qi1^uv>0a>cCJf|7)u zz09RxR`YA?S{UUh5!_{*kZ{&jHAT5`oyFu zSfs>}gMgD}s!^1keYwAB35Qhb0C}hZna$R(`+K{)yL)^6TNjAP%z#3#8Wf!Zzl~nM zLp+`zEMDH<(Aot23=4A7w9X1^*`pk+kc1ILz>p@!udHOgl6RfE)!sv5H+ZXv4b8rS z?czbf^fGdUh$Ta0$O`zQzrolD4U~|;8-OpB37KfMD47_C1&bKpJ3zkEfNXWd`Z*9} zPe1y*ZW7)80+kGbz1+WtNhz%rR97_#Y{7+J*k$zStz}fjDwPUb_Z$614 z9|`5xGM|0~K#rFNkh_m`yxQ&W_5l*jk;$YClJWe_@7@3&)9KB{z1th=#h?)hSBr;N zFBT`>0$5?M*xarFNQf;tFHn>$vASja?#i^pc(~FHW?x7Xdvs3-%t%neyty8i$-GY= z1s~umum$^D^p#^J85<5nC6&7~QpljC*~EYlLv(7T0om(6`f%}shNOS%WHI&Opca=_ z_nSZdI zARr$et{+}}rQdw{a61hE!R&WDo1gyuZyFCBv!+ZDc87r#DXG`V?Sk`SvGg6-k-+Op z!7Hh{C(?9>UO-TqI5EcdOS9%3)GZ!EY* z{Yuf9q@q!#VG24m5qXgi5Dm$z@18zg>xvKoIa!>ZuJ3@Fyj@SRa5vo)r|t8TKfDJ# z4i092eu7gk=vLx}(Xb8{S9XKr%y^iTEE+XKFL=_FR}!+qw54g9`qU3=R!d8_CYSIz zey#k>*!>?tT8#n#C30RFzG7fg@m~{5SWTEYRzZZwaHxtYGiE@Jm%kN&>|QLU(^E)* z`(NGN>aa8w0c&FVTQ~px>r({e;LhV@!yV+I=B8I{O(W_Tb72yzB|aY<45nG7K;W0nqqC*@udQ6r+^7Y3~ughkj_ zT@nyGK>qmjPda9;?-)R)r|V-)k&DGC05UbO(D(}K#qr&Ly(d5x|KsdzLgGl%INq;a z^kE5*IOqVS0GZegzZwo)@DdBOCO;zuf(8$VyB;Qu z7%#f;o$r7FYt`>kij!e-2uUt}Uu>?=;A}M9ftY^b7$RTM!)Ic#78t8aS`lAwg#eH{ zsE}2vkh}_HJv-z$3t+RTuRpwVhy^a`MyNz){x(PTQ7(Vo{_hLLM*A!{smCXg(15w( zMuIwe?8>bP-=m_`>OkrA^8lOJ|f$+&==JT6HU0+Q-=#x#5^I5Dj(?_5MG9Z!> zkTCGe01Gk~EswOUhYZLa4x}W3l*{Gpf9Y!)`BJTbt55#naUR29;n>SRy`VS>g|FBC z)MV8ch#efO6zlilmt8`SyM><&Z56xT7<)mr5)=H^2IUtvuy+$mFZ@n;b2ERVo1U9y zNKEn8H%jrTw1@g+jixzWvQj%bP$lj6iqN_0J>W#0y{i7E{L}7K{1I`Cg|b zP;rTC;W0}kF@NM4epAB%WHB3dvEG~CRNm&+9cORNllK0Qw{8G)Ave}Uu0x=rTAK8jVI21nQ z3WXP$KDz3WmkS|VlWOxTt|pdZZ8FY|xW-W@t*s5cJ%1I(fC+-AUo_RnP4z@Ve2K4K zjLD+n*I9lKF)`7k#LD`X08#-UH30-dD;oYfpDi#T7;d#Bv6`|l$2D^0kL#D7{<4|P zWal@!0Ep$uLe+?W0LedrjL|W!uYzZB@oYuZ7b0H>iNxwpRq)G)xH}P7nv(IbtH=1d zS$o(@ib!bq)!3Jx+tCp11sE-2D3KRZfo8r0a&LQ^##yB{fH0FUV?F{D0LUwNom}X5 zfJk}QTrxtvDMY0H&dZ69MzGIErydYX*n()!>+fj7ARx(8J;D~ zdU=IPsK3CJOA{VLtmjtr2bbWQNYKv`LCYkh^DBT55gPNqMHEDa9zi3JO6AUF2BgY? z6aWHHa1KlO+DX37%zc2e{0C7^S-;frV>`t$Khi~k80sK060u_Xh-+{=&y~8t164&g z7cCyMnlYFz*RE5gM6Y6dhpK{NiHqNwcykg%W1OdK(tjw(MFzx_4^GM1#dCybXNGtO z68V3V7k^oRr}4xh4^QsydJ4%@+n|IsLm@z;3aY*fhnXq>IW3Kk=N~g8#?nLp@^oXK zN(6zZRZBL16w@Y6?T03Px3Z;X7iUWFw zLo)#6S)K;M`Nv6z87&0z6o72ZBM>Xl28&tBUY_AUi{OjCENeFR)%)f5U&hncF&^BD z!Wl%i?`_)ww*qO4%R68;mW{nQ>_SNL5?5+sJOblqs#-OZrN8mfZ*wrF`aLH3mF~n; z|9!p70=NzSHR{xl?(SARAeq$)efS3yNTD=y3OK--Ls$1zzDzU3{2}x6K!gLCpXUnE zhVog*(%X>q<|+b)%ZRzhmYe&2ba}b+#*O{!xmlX`+qQbM%4M}MM9?U0k;6y|0#VX$ z5~D}_+A)O4&};!a7G_Np013Sq>;VW#O~~1%%2EZ#Lol8@67ukfDdgI{O;pJ015iUX z3q-15zLMZKT2LW9J!R;Zh>9ZsX%|3iXY zw~FsStt(GY`-j7{iz2N`fK0U#2!ni&&ga0D6vPaMxBkjj#5{u~k@Jz@Ll}?>0NJk9 zs;Sl2Adyunkt_glhFJVdUCCqgB8tsYO%|5(BYcq0(5GB1WjbG70Yf-N>O8LRFW&$f z6SsSN`}!s)?>)Mfp0$pTgS8jF$_gc9zAzb8?^P%x75c$YQ54gE&Xg>OH4Ks9>61W& zl`xe*^`5%s|IKq1zIaIWQp1pZF9K=op7= z8gU12cq?JV$bCC{W0|Rg(f}S`(2pnIr$1r|*$xH)qQDQPjH$&7kNKQSU>1otCF6lX zeN0-y-pwX2Vue0Ktr`;1KH;C6bs&>7nq8=n-QCLN6;wzqm0CT4{O|O^>gt0eLvyg; zVC4idcm%?oNRJ0()-nQ{tP!^NBIaN$c^AQ3m!!_4$ec&##C>Y5dMOUz(cS$;_t8!| z7GBk7*FE0ewJeeZkfX^7AV@yw8k zT1v3wGE^jHBQiu$$jaK}v(r{FBGhb`Ix@`XTSmS z033ZQD_cj%V{B|}czAg2#dP|kRgBf?ec9qN0e z7j}zMH(|W>B4)z8!AjIh2oTc@wfjUHq2n1lk)+Is?`0&S3QLNPNz4p*5>&_@Q^+C( za*Ii%jan_Jwo`!&rLx)5GYq^AJ{+nhixO!EAafE(M6OcQ0}_#&-eflV&k5$sZ&PbU zo%zb-^_IVHy?cIi)IEkc+S{LYY!06M40cNcOh@`ephS&t- z0YqCBOcqNe5oKvW=I0m?7z~puF6uf(P$o2%NPoqh$8E4yAde?gPHyGpe$LtHUR+!p zMjnHMgB}0bo1JA@e`v|r&<`buu8WH;FmU_qE~V?mU4b9i5|64+MH(K{47dSPH(=5- zagyLmd{q`TY?I%p%g)q+Y`xgq1t1d?2qn_B#E8_oPEHpVh7PJ33giHRK&~i<%yv znyfhnL>YGFzqCN@JY_tyM3oW97;FcTawBa@YHFPo6)2^qalm_KptVF?jXX ztq0TThio<9I&1yj_03j9**#bOV_Kr}h?p<0ua`1J%t)X{;@Kng_NrbYO8nIzSvN@} zVut&CYO=N-Q6SR@1Z>v85+%Zr)Rx}XfJZj-w%>6+A&^2z02%Rs%wGi{zBMB`1+FX? z8*FGi{a0QmVb*GLvTxsZrncU_JDPH)_Fr~??@S%FGaQ3g2fDfjx=wb|mRxO77r;Vz zxY`vl8@rNGo8>K0HW02A@8P4t=t9V2O+*y0q4;eQYMfYjY@RU`X|nGDAftP`)6=_` zI{^qvL?TJGr7{^xBm*VjBrB(sUNe&g<0d^%|Mdug45G;jWH~tR!q@R4(ppjH@m}EQ zMSV<8?oT=W&JUw6`>ypTcczlpy4ScofXC8OSFLSt*22wVxjU&HdodbZcXL^@_s@b-I zout~ZH2aNul5W?|k=uqt+~&P1xvU ze)pW;IluEeM<%<-OJUkv!O!g0neibjAV{)>1KX28Yp5y6=Tk?l#w+p@698~fVxcK( zaBGqRGFqU3d_2M40f9)j_~1F~X1@ZYd+gdZ1CTR%$O3jIneW15+#RdC=CQ zv$)V%EmkYJWMVHePQv3^;KWnI8QUrl>7dVc?98a>g6M8E>8=u;Yx%VB5Di`0#c z+}z+O11oOEngDiBw-cpQU4U8ZR2voYVZN$fkCm2=~0+7i>TcJP--_{BGCV(KJ zA=2H=##Rp~A{vlu43NtJkdRS~0fG=)XyFz}r!Rx}uOY2t8X4kU$3Pi5R!+qATt_-(aX<(nsy{d6-eXUKznp?1JLU3)@@A!2tyT$>CwB|+-5>w(K-X3xJkD2ZqZd-sNGbZB z=hpp@)(QpG$+z|zdH=#yU35Yj|Zs0omH3fRNU(j{>5e zPZ%UCk3VEK6gI5-F9c-j^6%Dr&!7`LFhVC$aFEbQM4FOIcgvOPe)bM2bPMFmYw}=l zQ1U3BO(CsL>J|KPaU6*kZ{up`n_m>Qf#KSd7!4MaG=g-F!>J47pq7I5v{#ygG5BAO zbE$$)=Mz9G83o8tzQo>>E``X|XfsEonI$W#ZxKL#djkS8MF2TMb+mtWHpHKPvMI=8 zgmr=o#cVvDO-F`z(}X`VtJSr=$v#dVU4lGFw)&Wm$DOoX0Otw-UA7okg1*`@4xBqy zYu~_sjc?tE?#|YM1hqkoK{O#8pbbaZx$x;+56q>C6{%}L$TK=b-!aV&8Z#;134OC#RIC)qPj`0R*JGq1!WE(_b3;*nj)Xv=%*hLkTb|09ONswsbQg$ zu-1cStJpB<0;`3>U{h|7&B#7i#mHscBy<<2kOm_6^A0Q+Fss%lNDq z<1ylf4k5wB#WBFzR%mGKr*I@<>s%07ba-HEvA9%agk&ZY0|bylo(rw(pb>zOthE2< z{*UfISfP5#SbskSWQqcEpd-Y1rSu;{9wJ*UEby5J;qhWQ7dcz0R?CsdT6L(eRHDLF zmk5vZBwOWc+hs3By)m9%&r{39e0vY_yW@dFVjjHT%IL*&ZFgH;kh09GmQ_&*i&mWE zAH86OvE2|9q8rNff%&g59IE`C@f{RG(nm)d@A3#4crh;!X^}#!?9BiqH&<3xZcsUe zg~-8`N&paSzEc0_h>PrPB06HlN@93-tx_!R=8~E0ej@VyArX?K-tN-mQMsFTdA8!W zLdCwMJQw1HbbSwIF#wIUgn|2q;nEn^JfQVPJdOE04cQpMXfkXaz2l?MY)d3YUpv6m zA19>F0Le%|;&eE~5m9-|K!mj={oMkPj}#y_w-HH*ywv55d9g^^jzT#Zi6r-OOLy-y zMRJLFW~NV)2m4y*8F`E(2zemBNGY#VIqI?-Q)BGHzIi@0c!m$0(07d-=9n!slFbOK znK0vAq?C1RlZ0`&vPFQF;6utt9M0WgHe-NnWinfdhuiZ-i3l4;KuBQT>h6|^jI9zt z7N-_rgkYW-*%O7{OaK0Z(f8ZhsK{O{tbMafK7S$BlT7vuu4V50dZ{V*D9;ORRa`~e zX{kG%f{Xk7WQCr2A|;2>c?F>F<4 zX=Q0v;0g%=#4$V0X7w2IgAJom1|goZGMS79gblRWz)nsiK;CNBnxg=OATlKZ!6p|X zKlPKHpWYgYb;dditKX0==F6EvJYKClqsnfz(6KsHqOec!Y{kflVD~^}piD=5Ym&hnUim@#B81z~5mJuZcl1J~*2+@GRLXiIrr|0f{u=65Y?3hfl zt+=K?9ppO-g|)rN-r7(fD|NdjWOziQtz0MJeU*!ZHJq)sxN5Nl%!%Pp(y<*Akq9{8 zuAcfy{Z<2f`*o$|!>N;s4&QPKMLhSR4ia0W6O52m=jB)-2}tJQ_5eX7MnWXspBX)kpiNUE3b92cRV+x;bJVYs*J!>zudzIVg;Q@3KU$Gt2 ziP?=%M;|^^2ahGMAOjS~^ts4uRiJ&*1zCgixWcvoiF}K~vJ~SJq!tGiMkmzAzO2~% z;Ia*T5uB}lmn%%oXC2({9Y8Vjl<&X+b zgyuGTa}tqr=ORmA|CxmztE@G7P@QD%I(wRs(VK~@6+o<1H>jC$Mm+A0y5(BWf)u)Z zs6*u#ArR?}9H4fEUF+5h0`+7Qt4mdqx=dNOE|Cx!UEqj7CJD9fDoP&BRu(xRjbLz$ zb}oS9bzk>ee63^G7gdzX=A>y*c~OC0y%SO5SZ07*naQ~~+x#t*1aoc!#o=dVBid4b`i;(i>UoQUh)5xb^J%9E3^PO6O zRaYW9XsNrsfv)>LSk17`B#dLzp`A&w7bfq6ixlS?Mi@sI$%el*kWFx~fwr6ia6*1} zU4r?JO-}*yT+^j$EGsfV5)6=A0|O*!@#$*J79>F`XfS>Cb+$=isznZ;0`|8!}*E{?97CtDZvQ=|ewbs^?TJ}%27=)THxg~%AVqTE{ z9e&-#(O34wo&?0n0>~St@Qy8{97stha3DaHQy6S|p0pfFrITNAK>o!58DPp2Lj+Js zpS|9St`bt8AwO1WgjmLu^dZ6T?U7XV-4|cHGkg2+)#2gJ&VGr~xgH4~rF?Y40Wloj zA$MJKW+66=Rj(ooU{BntRNg(u!ut+>!@jI@idFMaE25WmkO&(DA_&7S1P6^O3Xnw4 z!+-zn5<_IPO$JF!21!St1QzTb)e+L@2jg0{pZ-$QXHUQV_VA;_*;xw6#e3Ve`~){J zl=im==Tg4!ILEsR^aTVInng(eYWR>V`z^8Gs&0hq=J9`|UC(P<*%iK*5j>O4$v-d; z612!AA*PE^hK4TUG6Btu7paA?PzH%WgiIQ;co2%^P} z<5{E)S;%NJt_3P)ve7Jz7YT(7HeH0?-|yV}?voscfNDJbIuj^UkEt2 zJ>+~NG&H7Vb1X3XI*^7VcadGGBn6;3hi_0}ziUADD+UlXi6A!wviTNL_fHi=9!iGf z{1Yq^;p*u1(&p!%OF$l#=hy15Uj6m!pZ`qyBJoor%2EFz+TVdIu2iEnUy_H})A z_n`)a1u308D7AUGIy`$n*<1b7>VFQG&Rt5T=I?y`>MLG(T)oc4gE_bziH!3A>feaPghk!$M~0=*FIw+9jd zH&IRtNd?Gt1;}tiBGMX+iqpl;UaNOkE%G}o!Bxp;bp^;@EFf}9Rm(YJol=?0M{+Ka z{xrY**7oFqcl}AC@+NTSDZPXj@<+`ms(bZWZDi6pFd6CB8F6f4710z4F6-n@&!L+f zf)gh|oMvun8Jn_2*C`l~{;(?$sWz?@OJXE$ex+AcDe$kn(%nGvhx)gQA-m;*cU47Q zB<1|@T)|dIxLGE~2AFeC+XX#pD_vudLHGtFG!(&3`6#H#7)_N(J!H&^Q25d~0Z9P^ zAbErugPzq5LNR7wqk`TY#ei@T5r}MDP)pF4FPE>%xOGkCTfg&2^3mThAdKPzBc+^o zxSYSa2rPGs+;=0G9WsM41L*D~^JEwb*@#w9HZl6!6&C7`NPlRbbGS26Ke8*(xrb*` zv!zIF&z_!a-kcSH+}W>H8VvzRcU`}N$aJy2DZ`eSZo3x9w|*f2+0qPg$L+a%C^=6U zTG~Tdh_j^vXc_>u8{Oe>>%M|Jss4UPRZ*Hz8+{a|7Ic(q5s0(3d&;2DBk~8RA;fHz z90T}E4k~0RKyK{U=7tRsv;-i<>9!vMs8?H7@7)NCRi?)fI9GyI1~G zl#xkO-T4?$Z*zs9PO%ga9it*0ry?sRksGj5Dj{W2+z@Yo6qt|&xM2#J z*N^~{rn%aQ(o)abOG{;a9*MiRfjY%eEGVLM!zhTBc@k#?J{z*4ta4z<0ZRNatnexj zV-F-Cvl5Vsph#B&ve&3?G*(9Zy(=54a#w-Njav$kEfuoV+6qaY$m~i~gDh2QbbTj8 z8^}(fL54CD#>&%q8-ni!njg#}aqT1mB5K7cT>xpjco+C5)BiP=`Iq^|qthG|k2a{& zG>F+71Wq&{cfJ>Z3=tsJR%3ruEVc*MOp&Vu9FmXCAOr$LU-yJ5dgyA<4I{bf1_Ya$ z0TYf=81HzYil1h!$bccHv5U}ym;h$k63dvRGvdhTy8Ixoc0J{?j#s#d;adgB?2Yed zY6_9c1qp~K+8f&dF4s9_M% z98n*OODCRzm{=mo>mTc21$m*@&rrM^3?t?OGA999;eg!hOF%X`AP24L#)>$Xw6opn zDa+ARC4wRK=cCd3{QSW|r`;aCd42ru=#bt|m_2Max@Knwy-+!D~l5+8BX%eRU+Mq?yDbR7>`#3P$5=P&C7y?)7%6A8%mSq{ju zq)21f?G%fhuAqoyNP8fPcj=~l_(HfT`g{@&i3H^BsM8sZMnArNbNJt*cSo-mE|NqK zSAugN=1*6Ko21l#wE^mmd}RR%BdcZ8Sds3=zsZu~=yVUw7?S!dGJ@$W%&Sf!$8-TQ zK8&gEie`ugWKLvS{XI^R^_!Kh0c2y))qHJnyGe)>@q>iv7j!~iB4ZF5c6DtO2zj0PeCqIF*@O{(v<%@u{}l$_?4nx=ms0lChztXnf24~acNw=NAY1jnHP_Y_%4_$}oGm1b_;YSRGOwCB=0yVi z6LK9yX!szu;17mpXwA|9q{G5Mm~|^OL#A99BnA*$&R|q*h?CVztZ*SF3z*Qu5}Zr< zo3+mxO7rriPmzt zO+j(|b{o$$J5!=x9U_zlfg{kAjs*gLrHxXCE_%j5y2)vGNY*we!NLJc(ghQ6HN+(){&A`Z88h zKpQsRk8|?)JI#>a-kw=rR)BCRnX4_11SF+md8h&DR;#Vbpme#o-jdT>SMN5TZPvG& zbpZ%N6*G12Kbr=H%XKdeqJA30vbk|4e}+KI6ewYI+&|}#Z^~R?`#YYByj(brC*{M2 z9x5UMNW)SVj>JSWZS8AJOk^_ybCIajztar)fde8rQd=JG&nzDnhF_q%X^=5?8F3kxZ88MZDB!{XWMBht z+av!%#}l56osYC*Y(CJ57z$`sK4jz*-$(2=w=5w4{NeWPnVA`Z$1`4Y?+Hlu_ZAj- zJ<=V1C{Sp$bjQ7TZ$pl6ebe0AY&Hc$l7gF_y0tY+SH#p}FwJoYDP)#)7!6-+HQvHst?jR>aVpTb!$S9n*^C-B8dm(5#DI*1i4aisDs)t5|rU=gu)Bm(5uw#duh8UfPCyzO{>Rz* zyhfF5ar|Bos_v@(gMuk~(;Ho78M0{HwBRB}!VH~-H1xwJi=aaiLRR8JXu`zJ%tKvf zAR=MN*2t(WE*=OWje~9iLi{7%@HVf`IrZbzy>&Zz@6MQX_e2Kjlk@vqRbc`VHz5{$ zT``njDu#~^%6Blks@uK9C|t`WZ+Ni$5;I?!cHm{r88zXAk92}clgrXEbhyk*2U&~u zIv1v!P!7nu_37e{1u}9#+{K>Hz7mnu&luk~j#trpjS0y8Jp%G!^Jmdmxw5JBVS}PZ zle}btxBA9AMVob+7p0X&&Uv*M(_EFRk+7~(l3B>G(lUjj)75W!u)2RMcq}1ep`T3I zSLldQj}m}vSRMofO=JW>zQ48whs}Avui%j6>!(*(C?3699V3wYKiwxFdj#ZK(KxER z$n_Ony{MQf$%pQ;Ob)Y1CLd+E@R3cRk`sz^=a8_Y*;NGA+`p_S?Uj;%5F7Aa_MKfs zlO({}LD9jM<_DE}bF3Eoi%>(7+{FTUu|XEHv}1|bz;1!`I|t`0Cw;h{ZFfRQ1~V$% zkKmG*F~;|w{slnx_G&<~joO0El2yYKDK4$-`X&n3D=VhsRYg32RdGQ|Uiy;`MOsFy z5=?}67zAMqyzzjX<)qfrir}&UQ-nBFxc&<@l(j0ng)9J&I|yV%Kt_|%o4e4uS|cK> zgV_z&P4PhPlZ6BzSyfWmDA3D{R!5awCdUf?ZW#t2{I7MxW}R+RL!jDZK`gaX4VQ(F z{bp$S@wJc+hIHY*8936OR~AMR+<7`sBtuHqtbFqFY~%I#-3E&8aPyv;i4d1B4;k+N#- zh9kvgXH)=$_6DPUd|`$nWLSRPmV4=|WoMXdxkR~8KOm5I2n2})Aic?Ccw%4LwzJF8b??$BM%gK37T56h$-`O%{SKYkLuh42S1q1Og?= zXwAMn=?%_oI9afUGWj}#M*PL`y<;HaEM)J)S_VkIx!jfpbyd68unY-+;%9awx0sfB z7gayqibXZmun=b-K6uE$#k(%!R^PtVHJ_z32$DoV>!jAyEr#TPELb3a-(0jrY#cd1 z03bU6WVi-CYcl!Vw`MW6W#Y=@^K5tadG#2d-}e??eQ-ehcuckKqsYq~R3nXb#l9CJ zma4|4aHhd8-YQ#Fs#mk?a3s!4A+RiLu}EoWf(@)HZgO@^Ep{9*?~(Xvsu=;W2oqteMp z7zg-Ie&ayUYmv`7A37k9S9*gB0CLh>L7@V!n*m6R+EpdO*zE6ZP9=kHx-JP* z$@nV~7F}JXTGmyn86fq?$eKUH{L6bXLp>J1+>%O{BoULmE2i6HJ~_1?ERcnF3mfZy zKV5P}?wrB$-#t4Yk%dfqBkRSi&+6f%#5K483bT;3vES}0@+aYyu|_L1)>78BTHSi( z@jt3mi@Hj+vGmbr$t1={9N}Y4Ax5xr1uY2P=sCfr3CYG5g+LYn$fND0CGuK}uRHx4 zo&L#mX&)?P+MDzs-2xz!*~Q_&HMovf_jV6A$wK~sGIL(PH7kcxc};k_PazhE@kMZ5 z?DPh6)fb@HWGor zzH=9Zah;>XF#>^p%FBz+>z=ibqfY1h=K(n`0eF{L9kjR~-=@=U&whkHDXK0B` z$K#a|_P1*Y(?P*m6MM(eWjyyViWwuHj}u&gS# z%Ep^X)o@R>5+(k4{ErDA@5TJMOR|2L_RTS7A{b9@QBYr4|MvXRBa4K*mL;;d`0F$5 zBp%poYkD?>l&f>m+XoB5d~0QOus4PnviTRJy&m4~ znL~o2D6Vwpc>C*!$}l`2ko+57j~l#9GMO{?BeF zSV2Np9rqZa4otM{%)5f(Did7Xp6;|@DfuoDe~i8_DdRE7s>S+X!Ewb+W&f$Iv&pW# zdu{mV)OImGFBYMTFG5hk;sx_DEhrC5M#dJ%Da=D|zyI~i10Zq}i8v!!8cwI@P`md# zoyTv6-#gbYCc`&(ZN7zyy1^y_*|b2e6=BCwoJcxdg^Hp*HK%k`^LB)(tRbby;2rdaP z!j?%TTK0>y9Hx;u5LDGhl#0(-R5;s>Zl5TVb2jZ?so;_D$3mzai^>>g2RXkL0GUjP z36IvbpQaKYacS?pTmt;@$AfRP<=x%g<>jZ=M7B~QkO5mFcY2RI)?FPeo{bM7*CL7I zVBmrLwcnM67g{*E3(g}&*u6^gd?hmFqiWo!9EEKvujX$*$ReH{R@S0LnVk6I57_lT zB^?in=7CEvn>vW9r7RJwK`dF{*u+nzHa?uhrhjMJ-H3Gr}VPVTx*nd?>v~YJB7yv;>1!e)uiZ)`uViZBsF`+t1 zvaiZq-dstICAA+k92xszLVUb+aJqp+&_o;&G7@GbHU<9v27oXmlMw>39&2Ao)&tOy zOre$6>DBkT7D3BTyH|y1QSi=HTeWxZRhr#~30Bc%v&Nt|uDq85hvgnJ5Q~BmV*k}K zLo$`j$Mj5cY;yfck)XlY4?gSgJph3y^6()elKQQsMG`>}5kvw&_N|G4g{V|#9Q1od z*v7&%Fr@xgAw5pd&x<)%nctw7X(G3t4xVl1t1>C{Ehf(((C|JCz8PmezBsQ~`s)LH zmC3*=lkG+t6UurMr^BJvhthDFp7DQ($OB5Yf{{4Cg(8R@Zza%h+;`?6876qRvN++&ed{UJ97WDIeV_|3J6B>}rqhkI@|A0P!w^S9t!jw6?9$htORsoaB`SXwaiO3 z_p)AT&p~dp&VMPpdeFv_B>W81yzbz5*=6d1f`;ATqC?@a(M5&}JUDQ$PA)SJ%*?Z|(d zM-`DVIep1kB)udFo1n8e^9bRYVyUB|YWz+!NqrjlY^A%LZ0Gk5lF5IU)ovlti=b!8fPY3ngByfp=gY4tY|d{tjI6R-4=unZtzIG69EI6 z+e-ad2)ZP(f5=7@cVqw*E5l%9oLP(Xm6Rb!dK>wfETob3k%_hj zNJCk?|DbCdmf?3HHftI-oYy2P5fYP0wk4#a#|Oi)SzcvP6#yzcLJSkr7J3tS#YfI* z!jfr^L>mx|D!Fx+XVdM($G+pq{p02*f@Bw!{PoR`@4tNd^eF(6^t~n{_*%=_44zH{ zFh5SA(@uTVTXf5|hIN!vhILf~U8?c5CO#R6$&!FK$EEOL1t7t!LbmT>FCQcj>nmA{ zfmP+2eZM{&Z?_lsR%9GsT%6xue?Y&*MU6#z8RW$~#(R|ZZnq34z1+{$CFTWymCZjk>LBfd{`_JSHs{e9v_-yRG(Jvn*=k*Bi&GwV}I&~0RaghJN#IL z0V<3WGI)!dGc^d(F(s9|a+LMuudD55`(-i-kepx5-=e6X2q6>*9`*v% zkr0FcsEP_!Ly%O;B&}4H#tJ|~(emVK`Lvm?jshg(uV>S5JE-K%$4?fJ*{26y)W1M8cUv-#6zc5(EV0LlF=3xPl6e#-)KI-AWN9$FO# zjj(5omBaQ-L+as6O+~NNkyRQ##@fUjKf=TE$S&{JV;e0j4ni34&-TH>Bm7@DQn5eW zDnu}M`ha+F<%KGe#Q9KWBzk>$c)Qp#= zSd%LZ?FPZZvL1k{;Za$WO8r4HZZS~_YmVEBr8FhY2nT~96r7!8UP(m11TP4a%N!=8 zC*-ye3I)+xQvao-)`zX7k|j~e*Q?oQ4hf?Y2gvE&>|yGWLI<-$A0i2pm@tr#u(v^qkjwDEG*gkOW~eb0t|R4 zSv467zIx{oNqnMruSlZNy)Mr)DMJZHlm0eKR<9TSrr5JJ6cB>^7DS_Auz7m%$9^i zl96>uOxKrMG(jv*eP7c=x7JX(B`j>E&4{tUV%c2es2Goh2y8F>ISL*RLm7a@2yBRK z_*SU>2f>)nq60gKTl|+oOTQEV9^+pwkS+ zxgo$PS=WrD37T=2NtkRIV${LnX7)BV<`Wvm;$cigMTMt4NhEWLmw60aFLJ3+Y)kUxV6HQb{9JM+@D`sl}xO7ECM8knTny86cc;15j967OkogWmof&`d=dbQ$;c-* zPrNxfo<}MfGc^Sv=>h32WU;vG0a0zz9fQ4ktxUvbi4A)iU+UNZBZr5I_YNB;JKO@T zhZMhy0WGVHHE6~Z+i=w_9kKhN!!H}@v_zNDkK=G!_dO?sz=l`T=ZnG_Z4#yl9DTl}d9lD7n6a`_QpP_|S>>ycyvnZ#%<6ulJa z!(UfZtEL=9HRS{ITc1yVv4AWHkXjiG>b=Nq94&_Mm|6nVxLxnvgw*D>ux!H%?F4V} zhrnEBA0Zg!Xk_n$^D<~D!e#Xs6N`l2MLpgE=CW9&KCii?6&Lqft6WXFvT91Ok`D;T zcKRx7=u5|sARo^w=EA%WL#_cDWsO5enis$ZcfIKHLQ^cL(oM@$WRFmK`ih}oYQ|%w z$nZrU0N|Z*ONf;#B(f)!^kT3xlmsSsp$~zrnsWMBQ&Zj%AUECy+DrhT!et93AZ$gNXn7P=u zpm{_rhnKum3zE961j9byY7ow*zC2H~z$07azvN$Ru)l*m#> zj&Gdf0o8}V(yJ*{Q1rt5=WmPO^%gQ)E*2+xcbz!j?81^q30c1@ux=R)y|Oh`xs^aQ zcii`e6^;>*5rcx;OC@dOaWY{N?x65{(GCnm{)e)S1)^c>7009wY6?=xZBSEsK<10< z<#;jw_Zqln35iMuv52hey<)r&0Ew4tlPq5{DJ*-^1M#x18&391Ax+gh_S~|sD;4ud z5psIWHKS4ts-D}k2Z$jSRC%X&TzyuOsVUEon{PkOujb3u?bY1IB)gw%}DuMQMzH<7oN}Nso&Nej&`D%X3W-CBtL8r4_p>Q z`R(9(q$VDoSHIU-JU<6pa1fQzu#a3W!kGm!>d6kvs$vM zxGFV-0PIR56aN*3l%oq9j$&5Z!ZH#qrITd7!D`;6O>#0CZ zAMD4K<}ay->%~?=@it+P-t=%7#_pA4EpnF3IwHU15~ZQ6(WmsvVDk}4EoP{h1%^tL zR+wL&_BL{R%;2B`3q{cm3W=TJae`GrI-(L68Dh4Q5)UMcc7PRGsL@5oL_YxGS5ZU3 z=eiyVH`XeGkytFyC?c~kOBp3xlCuwUSf&c3JNh07VBfGRvG-u1reCRT?P$wiU!L4t zz5o95rq&f61uh3j`>n??3} zo{h!hI}3^XOa8-uhi5(-l}3?*qXun;n8(yjF18$Dc@n5fEY!}PFAs^jxe?AhAa4Q> zT}aM@Nt&ZnGEtoW-`KV6HWI_IT#VQs;T#AE@Za|r+p+buHPaN>Zkl#4qm@WelsbKT zarLh4I}~RvLmI5GhwmInZWvi>rUQQhhIEYyb$<9=^QM!>@_xLa97u??WlTyK^DGLs ziXsfGEcJ3mqLie)4%LyVmGCF5O=UO!*M33l^3oDfIWdd7TWE?Eh%IgJ|#e&1LW?n^EG9BSjT{$u0 zw`~O<*td*$X|LLS7d-bXk2|cuaVSBJ#%rvyd`KVcGZlYY|HYxW0G;D9k%aSOr9-!> zW$#!r^uW8~xRGv()h=2hXk*RHFH{)qOUdT-&R$V6tih?xREH;Szqlzq7QHs7I2vBRs|Ko?@r(DKRb1 zMn;jZuwdoLeD?6(@Gt#go3LMNbwe=DB*pr3J?{{P_snn#SagFez5gA={x!B=QL;;+ zFRoM;gK=TW1QmXd#)AC8Swdd<3Yuh@5&QC^-8D0F`Xxp^D%RA`3O4O6m6ir6Vrz*4I9BezsA%u>5;E%Bb1*qO&583_pgz{aA6C7(LW;eX9GT6^iVX?6 z#G=&uJ>}XwEja;WTxl+C$l$+;WH}!rZ3e#0WQ5cXjz_^6P1_bG566wx*0#HQ!IzAi z!@9;6=K85CUixeC>T6<$V_Fi5$$Kp@3?lN0!!Z9z%7>k7oJnKth7Q9eOkqfBST~&%OYsny6_*`6Nd7@WKx+yyRl`O)r{MHqX6Dfnay4B)MVCJ-e&tE zJ4RyF51I?)N|@g;em(<~hHxw?MhAczLEKBHa1_}KE1pUQ}`EEeTM2v@Ei38~v z3ZJ`oSx2*5$K;_y@uRioJQeunpj&dPQfk|)myuz)57J;=WfV)8A?=E2D-8U5-*P_j z?yA{Cuel=HZluZ2D?xc z|FRT&N3gu}9EGD83RmAP6vN9i{W~X&XlBhx0uWKg&+JRExR#8U;cj6PzMjFefC@Kp z!8WFE$4v76cY#>EUS|YK2nRE;vc2aUlwzSOtL2xh8PX|Ax35MD7m6NweL7l}!vjWP zj^F$!U)+>ME&4XTM@cLdX#cKvOAlXNq!2_)7AzT88UiOvp+41-XR7=Ha#2d5E*Jm2 P00000NkvXXu0mjf-&0F> literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/banner_create_link@3x.png b/assets/multiplatform/resources/MR/images/banner_create_link@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..8fe3b9c035ac715d911d5b96f05df97de80220fa GIT binary patch literal 81066 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 z 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

wUU3x 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 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/banner_create_link_light@2x.png b/assets/multiplatform/resources/MR/images/banner_create_link_light@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c7bfca381ae3cc37b8368e3a1e95ed9265c53d58 GIT binary patch literal 19614 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 Z@efSFtbdwR1{nYV002ovPDHLkV1heRvbF#K literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/banner_create_link_light@3x.png b/assets/multiplatform/resources/MR/images/banner_create_link_light@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a25f96d59693e8f44c9b6f587718d6b574595520 GIT binary patch literal 50187 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 z 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 Y1zhB=lSQpm3IG5A07*qoM6N<$f(^ju1poj5 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/banner_paste_link@3x.png b/assets/multiplatform/resources/MR/images/banner_paste_link@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4d1a6e2fda8fb87bfcefc63b53b1d1dd4cfa0df6 GIT binary patch literal 71865 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

Uq(IsG0KOniML}6aYqUy{|@S)d4*7_&M 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 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@3x.png b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha_light@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..556c4d36a4ca4aad251225454386f4379a964f77 GIT binary patch literal 53471 zcmce7`9IX(7eA>K(PFEZQV~T&A|{HGP?iW2BaD3+S>`o%%2w87kCA=JHfHQYS+Z~0 zXE0=sdCgd|Z}Xk^=ll5n3*Y%^Uh{a}d(ZMb&vWj%cRuN8sd2DfW@BPv;!uD7^c54+ zagd3L<@_lY#xJ&aNk@zaCY=}hs%r0EtZp7YdyJwc=w8!Kpf9;^Ru~E1c328Eya;)@ zGhkiik4SekI2iXlqUAn+b1!YkqAJ zka?dPSy-8qvIY)^mDG~QI$kCIYj4DBsX2*hewR>*e(2oqu&3NJ4z84rDv-Z1VP zRruw5lV9^FWym97cz*pK?e2@3luR7(2^*09Q}wYiold_+r{AE{#UDk`kKPI}o=acq z;ppj%FFBf;S#bHKNfw^@OaB=xwhw+v)WF z*XqCNnw@mTc{+U_1~J}v6iKHq!34DC=>LpWN9hW|bV0Kn8814$T#NoyU8jV8+nKKX zhn{0$q@GKsV_wN@(CH1YuIexI-tp9~r>8K4{iRFCsHeO&rqkZsIi!yUJ*R(puHHkJ z-lc1`(B)R?Vj6^--i@Rf&6^?3B41iBsgs{2(CMoh6Bum)m5c$8CmC)ijGbMWw%&D( z0*wQ@h-(A=peV^m@0L+LpM31gCc3!UFM7fYAvrI4w5ISYyfB$w{+oK`MW>!c{LUd= zY>+N$+a!ftks}<@>P*M-jcy}mXR)ub896n@-zB>auN#g2Yq7o{^-*q$o*NwY5PPI% znAqOdC)-R9qtnxT-Y@Ud=|?-FD|9G{{!k<8ikN8)d7oi}+Y5ATMa!Qmvx05Hu?7-{pi)Ck14u!@3thN#S!W2C1wZ2w4*f=A;fj6{|9~HscIb^=lI%t^$;jMh*;PgAx+Y| z?xzuVj@Co%C-;x=V)dJJ3*{FN?fRVR_d6PE6unk#(+*_uf8dlO&9K&&UY+t;vspR% z-i(ltbAP3!$8^9ld*f(^nXWhqS3f#pHPTExIJ!G@ws!DnkX^j~$djPRU*uZL7kRm9 z(5ZRHYL^J+9)3#iTh5VWViIIhf2yqSF$R+B?Qg&?x4IzC9lP^=D>p8<0^(3(7)9Z7NiRoPsX|9%pB6~Zgd%p3F;-M&&@R1b9~>} zEEBm-9k2Qn25pf)8WLJ&s*65dxi(%Zy=-y`_4arS+HOJ~3Gc;~Lz(ukpnAq&f|(Km zk8*Y8&SyD7KM~e!E}?@U0lo%GdwCHGFv>5c6l?f68{v63&UJ~Q)JRN{+3RBO)W2`G zg$qzraq7?jJTeE$Gd16}dJ%}m+> z;$fkwX+kcdf-7aDwgp)9QL+^(*deIw#cHgi8ZJn+$}KRApoEWB!d zv6kj2>vphJTj$m@05NAZyV^uc_mn-=yYlPYe%71%7`~R72WJ-DE_rHpV!q{&cXqPZ z-ak@d@;(o+UbPQwoWc=X9+e=Ub_Y+3pHcn85xS-_vFngPt2SUZYrF4Pe<_0Y<6E@F zc@*vNaAIxm&`z{T>zvDCDZJK=QgNe+vw;Tv-XhodcYpie--@efEj&As6BnO=eI4zxwt*+4FO)w1u0NR+AueqKf9bc(NF>{{)i$94eH;SQKx0%3HYj_a( zZPso*h-z9@xC#^-$cRaIDJ&0>Giq(Sdc_0adPsdsB7ABp`t=Y;DQlpebn9~Yzw2_O zf{Ufj=n~I^PgoYLo0gdWM=4pE3W*=Q<|1D)XQ0V$wd0CU@7?bXxlTLafb!TcMKRuU zu#fkDOFpxpuslO~Zx2X=`epa*M9{ZKt~}|O(dx#5`b@+QEJ4RyPQQZYb?!{=+x^?r zH_4$RdwOW4=ts;%J1ZWbEUeZ;&|kPm3#mi&T=cX?BhbF{%j~rMdzvamPu*CP39Dj!Jei$6U;mm9ONXb2!M*F?Tq1XULShlA zSQ3pagN?XQ?jL@ZV#KXOPcJu=3$PBgnB`8X8^6LQj~WF`Q056mEZ7|Dyf!1xPGsC! z!plYNH1u+>;gn$31T*mso>2LfJX(^5px%eBbi|hp%e5bHXrdP`vN1Vkk(TP%2RPVj zY!kEL?{y6@wS)XT(;wJLbBV zc=iE#*=ns3a1Te?H0Ueq+PZb+4Zl)nYzyXx@-rf{$G9~ba|WkesceoZ|HpP8I!p_j z6yzUuAQ7kss!Se*);394%6BZjkeplY{rqS;d!@4U7CLIj1y;+lTG`XvSsSfRBq54^ z$QxkEiXMz%vlT0q`!w}g7Di^7EFvupE7V_tvVFncvm3>?8acxY@HQlsmC7zX^fwU0 z#^f=~Fbh#ou9dL}0ltQU`Y!i^`r?vkCkh$3B7Ji8S&zi?FA`IrmmS_g7>W!2CqXJO zs&OB!oN{{frwq#EOah;deZo@4%o9yw?qB5>b3d+n7?!G%M-xB=f}gQ0tGtXE26{Q6 zrHSL&&%a^}&GZZodrb<6b?%GADZv|SOm!OgeLXqCV|qT8GS{1y-KIT|f;cg$++SdW zsWtWwjBx(U>_N5O&a(rQYpqAAU$L4A)S^3KA9M{jvL4zI_){1!G>FL(1n&Sf)}vj0 zWh297O~yRSCrPz>=q&>EWhW%S&;j66Ct4wjUJzQWN4<_tb3441l~_maSNF17GuPSU z`Hy#S7pB1T5S0i%0=(&h??sN5M)-voOV|PS5gaFXv6LjNarM&}|NKLF&#JW{42Zt|Z){VK7R~GtX^nQ- zoDzhW;hRh7?oin(5sIP+?aCD|{KgP@xtz-d{bG#S!_LZ~M~i+r@`Jr!4{a&Lu~t)e zL!)KJ7#%)OGdM`9fW`w4gOB~m87wiV!2yV3>%`XtOA^g)Mp2HI(2Kjdgrp+X81ukL z@=yp0N6Ilzgk478aY*m+cj-6wEzT@|7ubEAEL0`gCB*(`J_W-@a34m>>?H@or!FHl z>3{u~iuYPADpAwL2cp{Rmo<{I$wL08a$Yj#WN2U|E85MS5{!XUhkiJg{GI>CimkM* zcdy<|sD$>p6uZj7fQ8;Q3da$Wsm>9vXP(Egj&Pg`laEEgm?S)h^ycB~`T={M?eM&p zbpM>>Pf!@`rgg&0=<$wnx8;|dd@LTj)`^&hmkx_^dnBf)UWakyl{chycr83e$hAQDW}AR=v@bHX=-M>OD2r^SzG*=N165Z+4BHhofmw8r?|f$=ID+l>KcX}jCWYN zdQh*b|~1=YsX>dhnTl2Dmrs)I}AlXIo44Vw1}dFI>q zIH31i5;mB|??k>FNUr;*>HDvyMP}T!K*Uec$iWM?tyyK);y;215q{|F(&mP{zjnEp zBuOCB)m*N#spOl)$Q^6UyM6zy-l>X6naJ65fY#@`vmS|mijmRe>#BaK=1wNrr#_y$ zFLCmu33A%P?Dy2W;3JmA>AhelZmpB=1%D-5CmLNB%n(h!9@$vS6%>R)u=5#C>?^I5 zF2+9tc(r#AMW07uhy>Z)Ro}G5!}BNh4ASN?mYP33wM$nbj}H?K>JyeN`u|C~9_&Gh)C405*vng0du*l7f7&=|LxcXUqnc?DF{t`z5&d{C?F%t4IDQ%z25 zmc-&5g8iG7sZ0TvV=;!UwA3=YA{^udq5kzcEUxU;mi?77-4IyTtJ~vi`f51#4|nT4 z`Gduo(s>QMfvmJ|py=r96VL(j*H;Dz8RDePzWvlcScte4-ED1VW`-ZL>|Vk?rUK0B z^orj7a)N0n4ouq)>9Q)qVvJQLItE0i3K!JPB;Y2SsX%I;LKbw1u)VBKq|H=d;dT~q zK{re!@EOANXD=y@{7x09jS?QUBDqIWw$?v=qmIe;S76q*#`lj-AMyy)v}azgtTW(RbvXRN(!!Uq*|bdj5QCBJfk` z=J<(c?(2yk0fal1iRu1LLPiJhx^mz7$8*WzsQN5c?HsDnZb zMM0TV28YJsm;xx~xn-t(5(YSY2^a3)oNUUjvg9{w>%1aOV9x?4yU#)R-Bh`HrQnn5 zqDnh06RTR}r^^Cs=Va2+#>K|aUObF^fzx~rB%cP-KN;X%Zb!~>p6e!QiK9qa7s!ZrtE{5$yNy`k8QuR2N0s!d-oSh#?vCdG1<{dyYB zT6y6%Yzqh0`JyisUoj_c!9|@*I9NvTlsM@z9^x}*ru=)Pj(=J&`Xu_f*a`@bg`g4y z&&N<3J^w0@>)sb9{1=pBD=N30C(hTLFevyDR_VYhJ!T%t08E1AXgmwstbzv6F%a;f zLn2x~SPbepA5=fii=#?fej{s$p%U3hn*(p%wid>M}BYchiQufI87XE%J7%CHgdz&J!Hv(N&IO znkDWY4qu7DrJN|B%;z+wzO+nSxncYyFEcyG*06owp{+9NwI=nL=Kz}f;SA_1S8gxZD%B^L>^Tv>n{m!eJC#Zhj6NCM|OC{VWP#(}_Vp@Ixl)*{fMk!Uo-BV8GTsUakLtZ8wO&le~Qf|l_ zQT$*0bo5R%Hjnaae;8IVPhyJ8MZdR0=wZPATwT-OhoRPf}UZ>ZC1f?22@s zy0Wm%*H+*h_V=wtE=2)r`ZEv_IHAW4Vm zKkf1(6uC%I3tYK2e0eux;KGE4BBkVHg^?>9Ia?~(UtjE(WVP1q-I4vJg96L+Q`g}} zLF-9*DMkio{xj(L8?dgu4z0^#;bN9;31c<6@HvcgYL1o6w^Iwc$|5-zr~2Q_cW zX;%z7VP7bWE0LyZDu8CCO#zu7agsdag9<*E!<3@8Zw&si|^c11-;Fqp1 z7*o~EN1dE3qUEQ>B!#$I9M6wW!fk5OUnfL(UpLvhH5`2C^79e${@EBtixnkIh$$o6 z-o4zFhDm^XeFBG?z9e---Q>2vhJzjyd{4Nqsmq&O`tTY7-`LSx@Eql$7;0{ucNw zuOuv(;e-3{o^&B?oouY~9Lo)WC7)t&z=||qv+`r2+j5EB(hDpcxw~&wH1t22?vOgJ zC!|^89Nv&(A`Lo?*qmvWBHF-4m4)-g6z#0cBroN61~Qv34E%ym_+Gld7q19S25WP} z6de_*PXMNXuU{~q0GV__QjLN^r^MR?2$a;32!G-KUYPJOY<`hF@1Rdx1wjaoiY_o6 zwTZ=Op|#Kc=QDT*(iTr{=+GKXcF6$;I){br&toJBo}!OZ(*KCCyx@YazD5;!Tw7VP zw2^-RW<@ZHf+Wy(x+B1H)WShqUniybCgJv*A_ zcR%#oFZbMe(KKk~ST()^Fz~!{41Yg?L(N$>y{^+zEmNR` zs?$gW3UBe6HM$1nqYh|{ci>+pSmkBk!eI(6MI5hpJ&^6t(iTlq0hml~)fGciw$Z}d zaw`Ye{#EI#e>#xmsoWPa zcchk<@;&i9MQxxL=*fl5XboMncWYhdUfJ{b`gw~M-T7Br*D*vOy(X&^T9{TO_uet0`mi&fiz4utWPNOpT zh#RA2S!pM?O`FL;-HLs5gOJ^##6v<}`}zC&@LSC-a4z7(Nt`Hxxmq9KM*~-Vw#k%H zK1rpY-rEa3SN)C1hZ62p<|6pdkd#)=xV0kiYgqw8uQUq7bD#xVDqUqGmB}CP=sohR zv3qgi`Q^aRR1;k6kJy+0X?=fUXIZI$*3$QFs@`*RIq|J&z1L>z4sCWTaDRx<+yvdE z`-dl%vBZOKSZ|VG?7|&G!(}C(bDC)%#He;f$8Z#Q%JWR@8KtsV_O%s#LBrIix{+9qRUOz9#FF~j@`wIAXOXpkI*`Pw#Ig73lEc$yM<-^Gnx zjK-}%;&3n5WAHmc2w))v@f7MdIWCeVwip`}389h=&e`T9%g6V!ZUhCH7ZG?BmODiq zgLqT?;j6Z7d4v52L5B)F-(Dg6};4<*$O$*zT511su63+KgmkAgsRO&eNDHLS1Y$pN-$i@ec zPB=SV6iOw3m;!B;N15YYp~+GssDeV9gtrE^D=w+FYc9Vgyb#-!bn`u=BF0G0vqwT+yLW8qD<*+= z-#j- z&(nlC!nlnHL)<7JM87<1xH7=#^J-UM(Yx|Sls5T>M|4fEH zyqcClMmj|fjv#U3{g31SQ0V-^@Y7@cTB_Vuu(!f`TDg#uaA=c&^^lu-ZUAH&e!I(} zNEI(UKEpTI&|a{cL-o4^oU(i0Vz-?uPS_A?MD{zYC`YG2!$&~D_8A@+2lURJBRtqV zd-KumepQw8Bi~D5C6X)PM8*w~Y!1SWSK=SPLQ}2G8I3y<_DQzGd5U+gM;S#l4u+2fh-5;?+~7JuKX-o)JyUqky7AD^sKw{az)>G@z$cO z>;-=Pok!Z|ZCHymb#2T5L;jI9usOLfMNqhY3~wRatLfk z3m(m)^?Un$dcUu6ART=oWF9>D=(%kty&HH83WDj%*R;nrp&!IA-CB#?r><8TF}<_C zD1lbAqYUeqB~D|T&01E@fw)@}=}d0_gQFRImHfNQ*}2c}BTOz3wtatOa%??Hbd=-V zSW8$BdCp5XIwQzVSTldxXVV`^M6mYrM zm}qqrF&iXw#bbfz)+!!T?N^z1p}+Mk0yzBg#;UVy(36 zISjr==3N>X{7`0?(T@}opuxXh<;Vy8-wtCyXB2j=3Vo;Z4KxB5iu6Bbrmv~8#P@_ z*q0jK+Sw|t>`Oag!#cuQ|I&&X5NgBeLcRXpieh))M`|7yw`L|Q;Ftq2uw#Ir30~7& zPpu9vUGQIU9O$!7-3f}yI|rt5`lGjLZ?hsJw~y{gNv+MagxC0Y_c)bEBD4*U0bdB- zrT09_Ewmm@mo|+LnEJW|B`dHlm0WxM`gKKS)ssJ<=yDKVvz_Ig#nDs0k3r*Cz$0wn zMThTFBbN7}zh5$dn{B3Mo5#>LnCQ+_CgJ`l{%{fG*(bbB3m?H4W%$_Z?j8|{;0MLp`6cN8{&ZP`@VK?aC3XvGu_N#)Z-2pSM9+x zlgeu0kG?%3t)vgm9AUe35F*T6W}!;MVJDokt+yk1!RHafy;_>=jU|=EX5*2k0DM&_ z)opQIw0YrQzYe!5sZ8ql!|a|KtFM*_ds0+mG;lkiWi1b9SY4nb_rVd9Kg7IT^14EH zR6yy1{2NNkLLcSdOrgjx()(h~7!J1M1f`SE$*e#4mYRV{WW3@HW52jeqF~oWq?AEV zS&?mGlfn%(L*5m!H{Oo#Xk!RnwJJ0vc{Yyl9KM77Q zDcMMTyHbppd|zYC>q|;vLG^M--xo>L&dh63(00;Oq-X{d@{iH3)P_7q&*fMBbr|a- zogPyU=O%)?ESfZ7LcshXi}L8Y0Dy`(R34A9+>oOFu!g$f z!J<890SDU|!Vf)ZFpR(*GOYzQ!0DY_KL=hoeIpUpGj~Rgs4maT4`s0jKDy1ywAtI_ z<;<_0%t~DE_m<=&ISv`B|j7_gru9 zD-oTE%EReZuEU*70Yrv`8f@_-bW1r8Bj{ z*fI)%#O%`Q8va-fbXiiDOs7yWx$&aLE(dwH?xjW$WK2o_^^KGE6F&q?XTVpSHf`hC zYt=WpuRquweu5b+ln-|@7L9J&!l!GTN#zG$oCd@^dXdbHNFP%uBO&D8Nc66}3to14 zJEO9qb{v&qew^U`<~J>{zaC{xJ4eeeMy!x+BBporYNjPj+KkkoTo+NQ!HRq~iMK;A zwrx$vlH87+XckL6pGV1E>KE}?%@p41ybB(`ui4nElL`qdo2|AD?E*TKIiST=a3$x} zGwD7wU-bKaY5uE0`>L?hws$kV(J$&6AYDXEI3uq~IC`R<^g=*KnGaP-_6 z<6)njc*MXTP@^#_V*fS-CTn~~E;LeLhCHybF)(BQC%q*SgA*Xk<#k;wWrc}&w6b!r? zzi4?j1YUX=b4d9BxgCTp%j(sdC$Bu)9?}YSKM|Y<+v8V4vGQB~kR~P1UrHELc&IYF zPfx|P*zqOO6btI`bGvbVTMcZ1r9m%QP0u5^q$w{(B6C`!1S$s^ON{f#KTCzXCvsCx zEn!Q{Y!bEjY6!Z@P@>>0Ru7hUMOsORa*LhlnPT~N8-Lr9VoK>dZ?pXZ8`VV9e|zjz zPEv(fCE(a@G-LJ%qA1lJP~CT{zr3L!6%iH(=rOWDrr=~5b5G>Ndm@^bAbf87mpt@1UyHwNQGj~g#*Kl%W(xM0q|#V_ZA2Ktil-kD8WsUycnI-+xQ zhWg&Vi@5*NPf}1&QvKpizg5&lhp!Q+o8R#K+W#>~i(i!Saq!)TJD<;WVKD4qsu&N8 z6kmAt_$N?xg2^<`5hSDwCjEq13egzQ)P zeX?-$u+}DbldH1x3q-6@THa@8WZU@lSr#@b>CP!I`#3Q5;^^r3xZx|{&I`mpqeaKh z+LLfvGUARx{7F5ymRRF<$ zL5XQfFy)T}j|?i*`7@*@61F*dDeorGhAWx+nfHNQ%%?Gg;#n)lv0v6wX*94@{DH|@ z10RGf8%|mEHD4d(rX1q~$_(7kA9U$Nr@22S-I~1f2^agNNoK2q^%==GT`3MH_6$Q% z2BxT1)MdxoMDku2-#34sIZJqxOuUw3Iu;6iS7|Ew`E&VV17|s$Rgn-fRemoT_BOdQ zxpp=MPb1f!8ykM!80D|npd@?!kK+9_NcWifWijFS2HK*!4)A99uuT`Jx7|kltK_&% zbGCW{OY5MMe^Nvjb#w-ZomYuiU#VvnqqC@*6JILNIb_wsY!aJMO&MU&5iGAE01dW`-isw0nKxq{~UxVW(_oO3CuJQrQxb* z1Lnc!Jw$0{XXZarJJWQUXlmyG@sCve1>zl1tI?$ZY@n<`zgAD$UE(mrDYQDp^`aW< zvE1il!IT4Q?~8>RGB<1lZ$X3LL7m7?Ge(ybW4;Vv_3S`a>%ViXoX|T|``~KLtI}z! zjmRd)?tzoZwJ3DZkzB+#nJ99Z-Vpgx_tcX6`dWld)$vLULzP>$h@aDGa5f+e6$Vt8%ndJ`MsF+DgW~?N zjAQ;r^41O4_G;;^%H%SBsMPAz-v^!mnE^L(P~|N65%ppu#?U6wYI8%qd`V!(=ZCz4 zLMJS-K|=7J_EK$jHI9p@uAVb)REircVj<4YAUA>N?CS`!aJ{&I|962;h_1zCAKBlG-K~VZ&$s$wowY z-VlgA(aCA2Hte=cJA$B>C0L+rxBY6EM#wQbfXy?wIfHw`CEKxtal5>DjAZQmr=ow+ z8&F_cXgh|ae_7K?O*=iBhaq4M^9c;|A8g_9UIyH0vS$2fjK5pKO|+|P{4NT8oNYl% z27IO!pI(YYtN^vA|JL5J;tQ^c1i62*J?{7wFBbRmE>U#>i=pSh^nsWxu-u0Ad~k!7 z2AerLBR%r4R+3mSm?rsl*P8_T23U;=?t_I-l#Y&Hr^Uwd*?G@QEM3@#V3jk-3Y+}~ zX^fs%x#r7n9tyCp_gdfPm1LXc*;W}a5nl4>JpDzu7)NGtM6}>zzpUSevI1gPtPFoa zp(u1)C;@h)bXFGsv-y5pa}eAr-*o{hfZIpRX*Ua-qbb2IpaS(N>3%K`_--VT02J(6 zxD}JNM&*MGu_wl8yZIrQR+EDV7Fi;KlycnuYcmmHEWyv0sjno-<6oTq#P)Xrsl2Y) zlsD=T#DZ_XUl<1}DH<=F#iqgYy!jLftsCX60O~B*UW&in_k{34%OJQc8(igsdaYBw z9Or)Zs@}XRvo(x00Mi5^9L`PDrdZ9k7C4B89-d4dOMBc!+j$2gD*9&NPq&MX9fwXh zw*T&}pb;Iy^_1)kM^osO?E|bg?{TxSmT1Jzr#?~#vM5g9nYbid3FH6B=vOA!4nAr9 zF`ky2lB4GNmjn0YcDDr=F*4$8d@%V=WDSj6Vu8^G;smnI7MHBZCf7Njs)WDfP^FRL z$TwDbeg;zmO~~_V+6l*jSH}pH*wI0(!+FqP-8|8OJnWjsxC8Nr6f0GnP?G~A)sm)_qJlN-Od&(a0bt@0G1o8MHW91x8) zTvj5&nO+BJUAf z4x4(j{=UQTn)97dPU1mRO0ePgY5JEP#)-nGMYFThBR6jmzC9-u^EUU2t=Yc}Aru$a zDUEF4;`DtU`dq~F%OzlzExIPgo>+}i%>?%)ISK7MKwy6f?KYIB<2yoxa&}Nx`~uG# zqn(gvq&sY2cJ|&HC_~j~(9$M5EzTfid0r6~?a`@Bl)kjc)_0tjex4Rv2YJh)c^EE= zF9a_Ti#Hq*FC|kRp$u)wJ-9*Y7UT9_1?~2eZ*^`lW^zB+iMOmqH*Y~>+h$nIHoeZy ztii)&z>fgOX4d(0%d4@Sc8E>MV+?M61r$5-vlkgV88{5zepjrJ|HFZ?86IdXs-RWZ zdME#(4K=3&(v z4S4nHlGUhdJUL97`dyuQ@jJ^Pcx)Cs*06&rO69bOCrF zsDBx%7^>GI74@NwDCF!U9CJy$r25{> z;HPpWy#_puno@5@X3KEskSdtb>Q~u2wKNHFmEoE1nO!C(0lFLLy7pG@xe`Xnz#>j$(&s4|H|^cA*{b_{qs1?19h?%7#a{n_DaM3#OsBq12r z9y)2vf$+~E@OIn8vxHO$S=Y7P2nDB+jH2deyz%5rMepkj(X3cIdxm4wCFYtwiKO*f_)OI1W`rj#TLwwMf>AKjL1lVnBT7*MeY^g&o`+^ zxJ>zfgx)epOn41gZ&+I09i9gR3|WbK^~GD@*r{4ohLN*Ptkl+m zuHXl;`>d5^!R2C$WJVP>*+4)(?vlaQo1I!U!2hfz0iIbg!d}ibvxyBC#WZ?( z5gT$+w>^M*u4b2}84=?jc5!x=IK)F5d!Iu&2@j-^0>Q~0Kfozr;yFO%y7{OlKX%yigiw*>P1FFZhev#8!akGpA1g$j4Z5vjc46g|^ z8t2wV?Jnd#e~Qo^a{iZ+CLOFNo|azejLb+3>HcV3F9I% zQy0pjoea=c}Y35FwdCmu(Q-sI1oc_$@iz z*6R&$1JXmF^(m@_6hAn3EbKda^)~yP@KII%rjYv8Ql?(1(tlQcQNeL#+7Qr00My4n znLLdb3m~}=QS$kxjHE|swd_FLyf&HjZqCW8`;R=PNPYj=7hFbu=qUWs?qV^rA|axw0_CFMB?dXqw!uUsis;z36`TTZ%#y>~Frk+so>iamyZ;eQ0BF}aTy1`~SLcR7Br+zH$2NtUEebtW-z{PZ z?R;VCSBicx5p%kwM;geE*zrqz^7kE6ZHpCh;Cz7^yxqu4g+vGB=C79EMq1Meg~-=K zQO&(KEEZ!FWMg%0xKGvY?jSN+k8k3j`+m#AjW(z{Bib+XP`GdUq%YE%7t^78FKazqe( z=!bvi6+z?27>Bxuq;K&$nj`sBNlAAfS}>wgbph22nwVfCcnU=#ty%5cbP~;5#8lx* z-+H`I7y6pt7k?r=Z_{iYtVuL<7IiheS9;vcN<54FZA;n|^}20cm4Lyl+UB*jgk1m% zo4|CtyqVs%{rgVlbq+qyGYy)?&W(P)&FEo9q6Url`1JNrET~$U(oOOmiyB zWKY1xiP?2eCX&m?U$1x39Ym?TDi3-dyzoY?}iT8|wwuy--$P4S^bcF?C+Gw}6EIqEV z7m2wHU0bpLNO)Z1KuCHiCem(if4a~Ml0(+^pX{SmQBHbf`D|O7yXNnwk#FB4B-hAK zmd`$6^R$2443|klb?#w5-S4fv*eMVtVj%!Df!GL?_2P^ftM&H6Ku&rZoOju%0ip~~ zz#%)^FE_$XColch66Q}wpRYu1FE5MbTdw&5A8Os$D56xq2S><{jU{dqks&RaptC1*v+M6*SKdQeS@YdE$*rm!Kne zz@1EZ&7fx5T!2M<3Ry1*u5l0gXgS56ui`T1$8$&bx#N!zUwj1Igl@4Byd5`#NdJo} zCXknJclnb&;M@;BG?Gw1H_=~QTW2_xinGoN%5hXH$CE=;>`CK6=&HBO)Swaac~jQv z!mF2>$um*ra!NZ`f+{2Rzc%gAz-!tC7nR19&E#&`o?ClCJe|Ee7XEj*V>JyPGi8IW z|J%)=Qd-kYCahILoj9KnaiWH0#53nU6nuV%-8FFuGwz#I7>DE!NO|x> za!88u=bM7mj*h1m5Q_EwV9o0M9y_?={R)(@*SZk@9SpVQ?=@AfHRJ;)YZC(^ocA*=u^x%-pz%X z*u$EH3AJ*RWzWtsP28M$Z^7zDg4>S9d0ZVs`fCJE#Js7@et9#%EdrH1vFO}d3l-|( zrT)!A6teij8=~yUVM(pMrx~V0=E>6LI9hi;I#3$@F{oR6X<=)IY4&XNQ{6P?GotU5 zbn8sr{{yAmRa=0fPhBU8KRTJR&9*kCk1vX^&A?Q+sgY^RVk%I)ahp+Mmk>?+z}#65 z%T9f-N{Dt$d{}Waf)~%l5*8=;MUvX9)@r^e5f<<0-YD?W_73!!Lg&4Ml1{m{M~|7- zr4!bXHUD`_m{Bs+ULZcs zUi}Q*Li#W%vO(>D76+PRQ8;@8QYqg?ZMe5N0$;SVh1_ayDs8Hh2C70&Ga9% zo?caM?vL^1#Y5Tcp%y5 z@g>3!PHWaTi7$VLYcc#u!|K^4(v~k-p09CXxO5x+c4|n|=rKIXRA)8%$^O{C+O>|i z^v!7-!>>7Y_`44T>@NX)S4&GiN++aXGzi@HpdQFAywm^T=`8%3{NA`PDj=Xk1te#n z5>nE_C<#%~QA!At9zDr>q@t9xlprutL5Z<3(i@Q+-2xjhP*730H$p-L@!9wHyq@PT zxVLkzbH%wn*Zb@)zOVc1rsDDBQsp0e4ylz(8%KYfKL#!+1B2$o|8_UJ9Vz9M)v5F{ zgr=U2dKt*HdKIiI94VPIj?es#(ho$mzXy5b>P`bWFJL}Qi+Vd48k<#U_|5Z%5c{2h&|vY7EY|M>>ZQ$5Z5$<^E;Tu!6W+!ac7 z?v0-HEH8uyKPNmwd4V>F7lLua#;m)3$91qjOWnz}d*>pZO&f0_HM?is3BSl~rnAVP zR>l0g?)9&JUbWy|iCtiEt1O4{XJk%@cqE#Asq5{W8q-g3C?FteRky=V)X_dlBMX@H z;PTm&DX9~<=D`rIXfGQ@YQSIKK+ya)2%8sN)ABZ?$8oxV|4$3BHqhw`=AZ)0?rdP= zn@iGPJ59#jaGlp;uFRqt2WYWpXL$#k&aXv-Ymb`VKp9u7a0mHQMrNmQgN;5LbX; zxfXl}CY>ou810(w#K66kyVTmt*Qk8iqdRH2U3JUy-98L<+nn|0k8vu`) zf~l$1#@`ftL{&OqWZe^pyK@VV$bTNhpk7cps`j))m-weA1KHLbv$9j29prDd1~BW| zob95`3j+$B#?zfj_WfxFP(rX%cAF)R^bL0FIe6lVDE;4ICJ$TUDDU?zAQ=Z3!+Nr&`_;;*;ne( zt{v^k14uEV!Bwz~`+Sm&f1r)wY*m?=7*vjmojkf`Av5ff#A~@Fu~Dsu=mKB;O&O}8 z$ho`-c<_~K{Cgw4F|x~qwC5ZUDY_em#afor=6Up+0D1l=M+!gJ>Q$##9bN9d z(Jq?-v`K=}+3@VF*BndcK4dF zWOu#3pBPDcAewt<_M06^4&h(4DIZHSRR6{)nmd^F*#8eZ(E%$e1cZhN&8%#t4HrDQ z-8n-K54y4`qd%B@+B%gy_8NY!rv+D)?#S%>GM7iFae zlF#v%z*sATY6v6;2XO~O4(Y0(kO*avbJfIWx-A=Z3|f1}nStZP)zk z)pU5*Cw%Ky*sZb$BtI8lFVddjA)nA`%TPDqLxpAa_O*3C=@OUue1u{#dgo3l(Jq^y zx6)pYLbhYZbHPsIUXMVq^s$F1p+dGy%(zgm&6FuZ{rxS<&`e0`HlUJJ=WxUxyU3<2 zacNaxC7Y@iws~|=Hd-0chj>D|k5@1LnS?QghhYnluu z4`6<7KXV6;HM2(1&f!n5qCGNRkTqFdYS6uN9QxwpXXgm;hF>C5L) zk9k*PIxu9`x;+7F*TZsOKJFV-UUcH&A^+$-eH9b;1mdW0l6yfutF|>siBoi)(+W(y zxN7^Us>ZMz3+P84el`W6?+?np%i%qFH_5a18^iqmdrvs`2TxsGO6x1s`r?<#eZ{nKm#YZr>7uCSahnUX;)7P`sN-&l%PMDl|zQ6SVuNF2?KD5~?L5UNh`4VQ+uC{jA)` zy79Km`v$SdbIIW>N>22_@;g*C}^}bbhI`eBrw57)wY#ZoERCE^-uB zNmSa7zT=dze3Z2f`4-hkjed)pd~sif^GLu+;ooW8np%iUnfFgv+ifJHO>G;ms~{6` z`%!jc^+u|loft^+Z$1DQ>8VFfFho+D1oz(N%fj2dcw@VEBoU@kW1=i8Db@s1$Z z*F#{N1fdmu)p{h2!t2EdxWa#zq9%D|+GR7w%j&F@J9}P-ryI3D<|}G; zffn7~Kq+1eB>EfehbyEYKf?RdgWq7B2vgD?S2j(WIWeBvm1KMht@`Aa zlH{})lvoHq^c3SI0i8{oZc7vYpZ}FAMvF6xiNH$crC-%A>H1Twh)$H4Qp#P^vlW%F zp)oU`uipBfWv`@c39hs})37LquUf7(RCnWQg%P{dk58!OClV&nzjA*Q_WTIw?m>*`7s)~vtSbhc3lr4^c*_E|| zQ3-NhEn+<#?XCXRbg|%f`zu93VE|z>HM;zHSLijd#w}~0l;lW)M@7*tefGM0{?T1t zx{4e}L9VB&jYzY1N_pVD8b$l8vmA(-LW08)2X=SqXUH756RPRqDf6z8f@9He%;7_I zk>an*I>Z{}!+R&hDTMjM)^#E{YU2UH9A?0F%Ge|>m2!2o8b~EL6;;)XUH@RCGr8$~J zWrVN_FGSGv{BrH01Z&(>BIYkmGuAK(1BuzM|(w08zT#`D!vi~?R@cyhlk;Xdb zqOve9`!qK{tXBOTf6dx`R^j8oIyqhta?W zh#~;xX+&%ks_L$)y(d<-Uj1RR{i{!9@#71(I%wEPw5uneA`MyKXFt13t4RC^-eqaW zA9k0%Tyu)xuNa*ee+W9;-{iQduIhsEv8&Qz9HuKouZ7mJ2WLsV)p)5YwtxH(w}?DL zHiCGKPys&#*fDHBh8hP-C{fl~^Boa!_rUs_mP?;LRKKTAO#k*DJ~=Y^THKnSpng!8 z6)?ig6c|>d|6=l(71Sk!w!A?)GOZSgZZIZ{Tk}W2;l&a!rkXI=&)=yNk1ex6eV@J{ zf&oh~bRtH5-sN@wp`T_+jXs&Up{ zYv}yJ?dEGzXy+RDpMk$8uxO$FK}>%;(>CEba1WGeRnRk~dSoPkv=XYY?_Pa-vVR7C z1VDw$bM`xQV5s=*zxNtv2cw#3tdE_cRXM?*w`Fn8o`gfB1hvo&@?dgXvMEC$oQd%_y-K_hIwl}p)EJiFcIYl_Q}|E;>$c?kdG1gA-J85<k>Gq*jLsZ3y#)R80N1Ba@8o)B76>4~HuJ$^y<>2`vsk6KD z8omN(EJ{T}HUT5u4)e_#D*x$NVjy2OA0YE3#=Vk%?8ZCg4}=R~t%uob_Ax815Xb+W z#Y`!wKIrJ$AzRNQp--Q7vm{@&y$lvP1jM3&=ZGKVAx?_#^yP^rqO%UY)aI^nfTT0CJAWLw z!6=u``wV`9t_05+o1~Kmgbx0ShK{n=%9_$yT)~d<< z9$f%@;N6L`sHU!$oH<2UoZ2`VLcGO$bDi+tk#4td5FJ1kB3l_8@FIbirz9j8cf0u6Uap=Q-g zX&mz6EL7;$eN%$x)5P-jM_qgNMRJk9_zjJ1bA8akXog3Y2mGiN{xjpmws@S+Fcbgp@4 zj(s&xAorR+X)$lH;23BpNHALb&B1;;e{l*?^T~xSk(wLzSL`$(lV4Xq_zncp#w>o1 z3;oKzUaqt)_KF7oU%)&KGz_q@M#4LAKTOt?yCMlCnGz?&6|U3&WqnXQ=KZT@q0#$H zUT%7im=V!DT{HXhw!N;8ER5yU$gNn%=aUW5%ilY&FRZyMD-?K+*4; z5P*jf_MZ~C_v&gsDDXCGI&0IrOpoD_2~^rE)nDkjfimM%h~b@&!lu^)l#Z17e;;0{ zQY#ys+UA)m`l~OTr=BE6!}F9LGC_zRKR(`@9~`_ZMJDIczHZLW(!lX?)m7R@ZMBUj z@(4kPcT}QCg>PBC=9d%CZ%Az$+MuyVW!{I~+zT6IgV~RtF3WzYI6?|_AzQH{EMWj@ z03SuDI7xBwzx70o4Eebd%U`Cey=-Gta<{Hcp?5;x;iBM*;KzF^QWl9xs^B}d-;9oq zm3@ITOuIBz=0|O4E{<2ZSsxqgRkR$7z z#!$yAme=W6Fo;8XA*Mw|!&0!Jyf{Jp&AxSR9Oa zP97xwboiqfv*$o7B$&fNOEySju&`F3-*2+*Y{n+CSmhW*dGd~_#(fkcs1Q4G!6FxR z%6(~ID6=9Z8?GH@Hn4lzItk35}lg9t@7N zkC)j+vif4g!Pum=u&4cd_e*%?+qEsAC^3l*hzq<5=8eQmogDxtx1#DC+OT4_n4 zIfos(ouGR80?MrRb7k<99%QnrPmdc`IC$_$a?kU#fK7xyvwY42@=Xxl*o97bsWq%t zUl9iUd`tDsGa{?E&|>eb^B>-0^{y>E=) zj0^uXG-ku+g~EhvBr171*`Vg*m*KWWIt@JCjGGD*9qd=Ng#iWPGEOpB`lP`yCUks& zxULEAk#OcOvv&kGz8b63gqG^Mw3cnQ%n!KxI?6yWwvx>KWu+_@jBLk5Z$tMI2T#ti z;5QsUqHf?9W;y6YFHiQfIX@hMHJP=Y+!KSQrW@i^O)!huY-w8NM8KWY=zb%0{ok%Z z>TFP$W-@c6K#OB~fZo_~|1Kr)tH0(RVfB~S@I%fKBf7;!`7_VFq@f$)t}EwSKdUzz zHCpDMv#6YLtV~bRLn5Npf2$+p4?&!SAo zFTWiMlt=vjZ$0?hF>!5p=jS*D#91f7m2w>f8$$hgAz@{-PU;sI$%G9Ws`(ueP5JHK zgNj8uPrYwr)SWxrEDcQ0EK|T6v_V^X9!@_ z3Ef^GZEjZ&KuSCB>QLq=y|+X>V%?IHPGS=7{I`Bc&)W}t9cna+@z}YkRUNKL zNkYzr{I&-G5$1fwVs<*l@}Cbg%i4duV=@Ikb+C*$&xICePVpu`d~@<5wOT0YhE__C z(UP8~i+4|jy@z~69=ZI{(;bh%V>#yQ59qA?0Ve}tI zt;@tu4L}M(md~=xT1G^1UbCtBsL{L3@<5leF0glQQ(Xyh7`*fXpJSvzGGY~jHs5Cc z<)Q~oXGQ{J5vW~Tyk4qN;tqdO1?g(Zh#Ej&)Ic7m)i5aV#O0UvvboK~7wrotBWP>CGkEL4#L zzyQ1m^mtD^0(EN)e<>7DC;#acc?*xl{euQn5O07F?%g}81_Q8yg;U%^Xi55E6NGZ{ z)nIdi%YN@0PLdj*PTw;ibPB3wc9kBmJq!KEF8(x|?x>!%}|$#$6nj>LBD-Msfw7CpZsto?9nUQGqpE8loe!79Ncr==V8mo=jqQ& z*fbwFX5&P*+jn*@F@7jbc3D~C39b#7lJhg3Q$IKmY|fy&bAje-^spzd&Pr+7#H{co z!~rPFMsk`FA7EH-K!LouXlV)d;E_ejLTafaawmA1xN*p-H|qm zB}dwl*{#;2I~ct@cnR); zMu8QkFF}&6AnJ$UJvN6Wy~IHl$m|aN=BMM%Pr7+F zjTUt2FN}#nA^z>axCMM2dL$-R`n&tn2T%wY=P4I6{PS1av>lcMh+yYWQGV8=Zr~cY zNuef8AT-O2o=Da2gj6!;wzv@M=UMktPD%XThegsTFMbae6^$0L zIP7qCCBLE>a{_0_kS~&-+zLQRWV#g&b9}z;GqJi+t4kw!W)sW}vrI2cL=^vUtdz1$ z6-Yqy#}Qki;EMwN#Nqc}tvYFAGnz-b^v z$H;yvr$z5y7gJUQ0ujtxPmOQD^2YM5fg6(9{l(+Gsronb6TF{4`YedfD#}ZLI(g02 z@;~pL=?yG%$ukezc_6hzT!%SS=utpe;}Slh z$DS~j=*k%V%8_lz?WZ@kZjz3${LOY8P;GhGcHHLkxD^Y6R%=sOMn^hgFUAIMB_GXK zmKO@UbD#mby56w*eM+6&BK@9eR+%oqSG{bw6RkdEcL@G7$b+32V5l{oY*tq7}kos^Fts6Ez{`Ew9<7#=zexC{4Vh@ zFe6Gj%evp%vO~%^_li9nhAQH zTreVF*k&7=X@Z+rdpv;62t|A~fBhc?Vv z2l1CIZ(WRU48uU+V=`5_nv;1BTNVj>y4kod02j)kd(ZEyVEjv8#XGjIdSVA35+b=8 zH)ei){JZiq7nhL>x;;$d$eb370u6(;N zl1;5gDdt&Z(&_O#MJIVTePTjh+(mE{8Q2$HHeyXn;=FL1-B4(Dp-Wzpw_!8ie^_&8 zc6IM-XTjVyBgygOPFj|i_Tzv4Lz=41C9sgj4r{ryfD+ZQ<%^{$NfEs>+c9*G8-b_3 z5@T({)Pd#f^cMh`?$=af20pb3M#yEY7qf24_3@vg1b&i3e*b8C{W>6i7))TV1m5cv zeZv?{LfbB}qLL8wje@Y7zj+LN;-^|)ovWVF=AUl3ptGG_NKxc&PC@(Yu>*`Jaxy;) zoekyd13#i2D_!6tcqI;Puc{j9{FCFnkoua&t2A(ScIu4W2|7LkcL7C{N5}wsTx7~j zm|DH+-tzau1kWD@V^DSg1Z3WxmPcIak6ZuZR04ERp&$t#{>R3lO2_rvkU6yZgD*~% z+6T-Y_Ub>TP&T2xlQJ<`{= zS19qr<_p7^$auv_*UAy^|EC3*H5$j5u%Qe-hFo}L(wc?6(j7wCU3B1g0&M@Y|J-ua z(7?20T|x^_=rt4ZeLEH)MC?sLE*Ho$ct(nH-)V5@E!>SZ`BK*eEo zhB{J(oPy>my&Mw_rsI;mC{SKjcM#X!J-dYSzmrN0&PsErbROTRyqm%Gda@TIJxta7 zB|phD!U&u^qC=e(oX@bBD1t?2BBJ19vJ4yrt+)`{gv?}H``&?f_k&iX>@BBUz&{o} z(DvP2Q8lPDoTyI{$5Yjm5sLt|6(7nOWbXT3B7ebj{`a8#Yn#^oyU8;eclmXG;tiz= ztoW@Nn|2o+%KR(;XM7as^)&80^j@sHn40OG932ChI&7$6?d6UE+;|003?B_d3I!bn87-G-y6CK$NWe*;*65AdR1B>k zAhLLLL>a+?PqF1afo$4+f=#I@7$yxxP5B;R0V!adE=VgNl_@1AA-_fpqK4ZL9;S~EEvp}~wfiE>U z{0yI#0r=ejZoWZ%#f zJlsinK7ALrAX4yx+WVJIayaSl6{(Bk-Y=Ms@c{nYN%ld;g;%(2jqA*4-VVhnfVxbi zbD3L~VyzTvR}A{Nx2rt`wq|a!{n(mJ+(+X8 z($iN*AYSNRvdq<$1vks=`BFEe-z20E?s{C%e(I?wDJyGRhJ2D4Q|yVZD6};{5c(3h z7b);Y)y*JXi8Afzzfi6O8K)V#R~4xXtJ2#D;MbJ`)_=Ml?T5GHm#3yG$cgw zkvC+i!FG?1jSO3Ub>Fx|hBi+-Wja5_24-u;se+IFNJcVoAH+#Es%foENx!*tE9QiQ zxI6#Mi>{E_+PBp2Iq?0PJECaIJ3AqZ?($y#`eyq^b20WygG1+@NJFrI zm>*17K}JOgh;abkg*_*H4{nOjH8@^ZQ z@2BDja0dR*SzM&ReV$LiIV0d-t=zgjs-0%lWT>|1+_3lJ(bVr>tgSV=O6;?QuPKAb z*k3EK1Lh4uy3@HhS=J7G9rw8kAkJau8-J{AZR7{>IQuELA7asT@{NI^KGbBmfomSd z(rd;s*u$o(wm4`K7U&oH^F&La(S#1NFDOzshbGMZpTE3DR$#ztsT21SSsN;fDA(ho zQ}P=U^sPH-4vGQ6?HYHV5-PU)`SH~IyT0BZ=G|MU6Ur-VB`sg8s2sC0BZVhP;{A;~ zmo7lZ85vY>eANZW=M(0#SPwduab;-q6Q*Ju?dm918TjnVLpLwhQq#o#bur1mmQkL- z|BT>3fJ@%9IRXiV0aUyH`EJ| zmIDA^HqjX4b~k^KDPO-8o|ymItl?ltZTk=1Y9?u7sVgBc3OH@K-FQZ*=%@=nC&vT#)9T}N&6~NUQd}h1+_P?m}h?*2z_lWJ~-swjqD4zA@$om zv?f(>&9cz-2k|Zw_>Ve?s6V}OboBP4M`DqO#mZPKimCLlt&u=+{6h>FW+EEK{UaesJ4nz)o*lo`Bhhn};}3P+?! zDXbLgT}S|Gt8v$3D-&|yWD0zUm_l$9AH>%^ifFj`->-TYstCTNnkLtwv2ywdS;B>q z7}74fNt~_@Ct+hu*3_?FkLsSZ=5&qm45d=27r>@IXn& zbm_&R{bDqZ&Ma$(qUvx=75jsbw75@@{^-!jVL?szSJhmvP>p)2$AOg-f=;Tw{H?d6 z0QKG$L-?Mj*B6XNCbqvZz*3(ON51i>@9x9Wvtb6LnH$8JNwvxUCFc&nfcg*e6$(!G z1uLa!T9A|=H9IQ6*{p#sKvi0tps(?yJF(I?5LrHo_33fhI19uPje(kA>}IDvGmd4w z-zq5;lxhK(%qix5nUqvfIS#t#7WJm2F~K(K{>O%J4WY98UV|3sWa_DfJZcP>X6pmHtrPp zl}n7;8A(!lif6}_L&Y?gxz`jVQ;_)rXjNNa3Rz#Oml_fqcCx+_(&6zdS{X794YKcv zrEcOT-QniGxuQ%O)0Hv4na=aG_mi70n~JERyAPq{?dX9-?*&{nb$2mkrtq_f=K!W* zFJMdSs5KoB?Tg8+5$shnxWh(rNP+05rOmugnf3eqjb<(oyXm3rn;}`xp?y`>J?`PXE!4{|PgL460y+m~3Y8A5p zEB~(;+UHP~c+Tv{eAFW$n4Y#v-2^d{dZNyGkmMxGD4!@FsCnU(lGn7L;Jw}XC0*l~ zy*9rci3ISgAov`DM`Yho+XCbMk+51tz9>rp{`T|NPu#v3uj)$}H^5P0&CvBes@3nHU4>TJdlB$fhNdK9$*GqN z%*EHN_=)wu^9l*PQ<5AMJL9-U?ha_eUyQFh5Icu8?88jP(sIlJrHGn53t+PQ^omMC zE9Mj3q%{?AMe+L+Jc%`=N6RyBR9w17bBwDSLc#dJHTY~M4Hs4}j?|g%jX2v5ySMnS zHeg?_T~-1#DPr4(Dx3EV!Cp;p;y%Kn*^0;8GSejIawPb!kThq9|3!FRgPQg^x?U}j>BOts(-yKUek7-YXk+UOl~kZVAFiZ zB*^!4*Sic|n^aY*pa*^A?@5KWTW5M+gwD@3{~VI_{m_Iu<{^GwR1miqjg4pO( zAYx5Xb74Psof^%_q`MT=5n|V__A%s)W=V=#lQkIM(FA9im04cSd5!P6)&;=0yE}WC z&QGVp{5K)M_%Qm>^OV4iA#RCyDTK`&tV{nCrOJ(>EVAw$AvX~v38STS6tBz*mNsWdX9o0n46@I3s z9|BESfMW;(Q3P2lUuYGcFb1=4u(GL?^e($$GFx7GR#%eyB=)N1>=wpOsdi@e^+CP^ z?nwa1jfmCY1~8xS0+85#)IwzI&>xEaV8Qv6n(JNnsiIQr?*cAQk(@4ix;Ok%+7Jpj z_2HJu0C0f5ddcGaOJ|V1{c|q9!QJ8F=+sni;T?)_sOZEMcy`Nud(KriVfaJ%-3N zwI5BZ9CM;NC92gE`jB>MnjAi%`=wK%<_p9R$AY_0zL`g=ymaUuW2~|OMTenMeQMoI zhKUj@Q19+)k2RVG5+>rS>HA6XVR8r=EkM!5`O7rraX3%K%6xyLX?@>N7SL}koY1H^ z#uy+(rpL)GM>z(#QNFl~fB$gU#U5LIxlcCv$7cFl-h`t@GX3wvESR%1^i*nG`mvUz zKS^z8h)**=)JoK7g5WfHj*Dpo5wvgbs=bDji#axlNUFm)>>;J00*&*+v70tuG#R_a z+BS3>{9Cs?$wm!voqCnz_RYe?Gm@0F8r%2eN#1o6>lKZc{lJ&uW;vp!y~Qcb#_OGozrTMzI}+ zz$#DG8Rb%Me>X8Eqo_YY_Y5F2a^OwEUidGrv8)V1N?gjshAcZ^L8;;=5-;-0_Ffi3 z=w0GvOZRe03P)X-l{o}G2j=gqP z?%ma-%hnAiGf;0$j)FU{As8K`XZ(jKk3DSdN~^5wE!DF&0y$HPzP2aV;&$0-0W%mK z?~L_M(4|>-&&tIz;4Q^|C6S}6rzf}qitrtHpNXwH)6T@|+{&x(pz>g0w*#bCoV^e8 zi~lzK_51r%?ognwD<+@$y<$jz#@$8LDU&vKdX#a-2xPHP5zpE}vS*j#Q6J3iHqbXb zk{rZi1=DjU`tVNk#-E+RXMLE>*)rk7paU{E=h~x8xVlw4jfpwQi$--6%fRESu>}?71&_Ca>Fo%af-)CBKciW7Ra*e!-8WThq zw`H9r;5MU9>ifNHyupPD{s#_4Sb^gzW}xaF>#_yoJV-~kM~RG=`S(CwZd=d z`mK3A2yn>_V*u!oIs%s3A`cUjab{*M^fS61vfSR}gy!JQ+0Ht5*#HYrQh! z43FSxSsG}>1fDgMyg_p}a5kYfshP)R%5)6#_g0${;JjHPlVuX7)yaJT%b+1HGsk_i zbVPCoBxk!ce3-A0B{1KpN-m3DCC|4|sdVowg%IT5nrj}7AMf?SCTM^6QvP>PTxH+t-WXe zQh=D75;tzhKCkgt{x|lAa>@Ng%3VkInJ;gg&i`CUyVTUE zzI=Y{eME2q1Dm~L` z%n3)_UELcp6lx9_R!F40$t;ztZY7Y3=#lTb{&Aoog<~ZdI^O5a3MMB`^H~5+B~SjO zJQP6GrQ#x@9K+^fX`Hv1Lu7KP_m5zGA(`-9;e&&r3IYt}S?MnBTHaZ_VMR@CvF}_ zc>hH1&wi3*P#$`UJ`;6_)Q(DGWqUP9#5&6`XA z&ZNeFmkL$b;IigV$D>UC3w2i1x^Lc@G`m$>#oAnX0_jwAHQl4`0sehIDr@kZyzE8B zm8+3yS>~1W-ntr#X<{bpcQwj7*Vz?Xdmm%LdXXYk?p`JuZ&(PWsg*%TgG_Z>n1i2- zcoaU{X;rAKxk6(zE_m~nBtk?Q#$xc+gbkRt6&FsB`$;9%9GRAp*_AiAYfWuys?2@g zFZf=H)L3D$XFYpiV$xEIBVyUSz8*ygOg3=vvlPZRAg$ z(1*ld#5~hW0qr=%+4I;^{d=gd@6;sE1nOw~V%L3m#2Y$-Gdb$QJSs^0DKQu9^FA08 zmohz2*YLyXjpvdXXJ)^JvDI6FRzwl%jm^(huGLAE`t7}37wif}I(zEkGVPTdg$zUm zyaZNgc3kb4g|U~(PR4Q^{^5|^>RD^|snaf5SAU#-D$**Qnq*y1@Uq=~xA6SAz><#Z z!{0Af4?*8E6HaI{Gk7IPe@aG_pP>cdo1?)|MgAFvo7{HVa>MTL*T1b9m=4|cyd+cSvkBf z?h>PO0LGZLp1Xe~{Fj3E$o)!$+1nChYl^i)!<@`CNq9HW)%fxB?&d5E#U<~tT&WG4{^reeAFEy|9D>Nik&I}q>6e_pt6g{mxB(wa{jz}}c{ zdyR8|H(yH|jH(guuIUbB9K*g|Z5^3h$&CpduUxrFxv*(6-4rOV3#U@H>=N3C5}dk` z2shUQS`@0d=O&1id6FPH$^<67)8Uq~P)>6{T}N5HDIRrk;KG9bn5n_5{QnM(uAs}# z;NH#?EwA`E<#TtR;o9I=K*UW%D$vr%eEK1DiD!B{wab^v&mrU^bgZRQ4AcxFxmfF3 zhbhXp+>~j0&L=w5s~;m$%P!deUf>cTkwR}{?_yjhAEfG72+YqWsFcb^&n|bISx8u0 zkp5JpC2%gs8UEg7>4U1`$5-Uzb;?oUzND=ayb8{s*RcW4S~%J zcXfB0zrE@M=WmiT?|X|!=Z^rTH65Yk|N6g7A`kPTzrBm$L#Zo$z;zUH%IvCpL*0Gz zhT!tD?oA$NDs*x2K)zqpi4WVmO1Zd;GMl}jV$5wYXL>{a*Zju{!c3M{J)if=Ee)m; zjyQOL>?Z5opsTd@oXM!i+2=pMa{F|kyIrf~l3mi#my36Nl3wTiInjKwM7%sTkg4#r z-!a*Q>U(=@LdcmY&)c^V7Z@Hke;yoiM<>biw$d(ouE>5G>}&Y(-QW_fxSH|!OEvmx z(%%_8_xo^lxykraC|fwQ%*k$xzndK68(H?nNp-qBm36B8IYKSA_-vdB?6+st`xdy+!P~3DxZ8i?K%fe^( zUgMHSVt;i1k^5@y0@pEm9pl;QY*ps0n|9zufM4wD-(>Z$wm39OnX&z_(f&oMj> z>+uVW&6eWQ7S>pIISv!fOV2RMT@}zoG`yo^Nnu2C>=V^EpfP=5&NTW$c}Yn-#4sMj`@T&5la_x?d}WN z4t+3%*C&?eDXVnlU9B!6kX|z3GJbq8gva#ysf@c(a30{QXZLGbFYowM+QR@!Qtt=x<3LCF-z$Oi!((`&Db>Dq{37RB@`hwa zjU2tNzQo@B)y++A1gu$(M~dTrdTLJ!>6fP=8?Vf5iI-Vjzk4?}YMz2Qn(qizTf9h> zT;vB>HwR`P}SQTv^6X8%w{la>x=f?E4r z)cEsWcJKE4wd=1A21>mzpS%ayssE?Z3;nIrjxltu86Y3Hc(8b$0Ri0^ZyENgN|`LW z7-Qckxc$-jBi;>fv#r&l+FxYxYJ#+g+5|krNHlW6&JEA;DAlO$%5y%pssC;EuJ%=W z-5XwaUwaG9lWlhWtru{N6TW@o`APR<;|tVXmipS52sA1wog~go!XMmR&xezUj{8we z@SR*P*Q+MI>D{b*UnjcqtzQ%x(_M(=#gA`Qd0LKk18yxX5}1k@tmZf zfs>wSbkvnjxm!WZH5o6p*H6Jq+9CP#wJA|YL| z3o%PBHIpob&Y(F0Nt}@S18PJIm!QIsCt788&;thsGuhk@;`eEchzz($(OM?$=KRML zK?76{g){uI-8zNn~3#WwnkW`3Z0t6{QkP1ib|z?KJz{6OEyCAn>6~d682s& zfl_q(-J4D(}%ttlg&3sh~v(IIrJh zwhn_tI*PVjRWoC6dqMP+X2@Lz$)C$ozJ5@}|Fl%J6Spz-Dnj5MFI!;@q>SM&agaKn z$bqL$LyhfMuCW&PEw?&V_etUp;4gIgJmQ~gUs){Cny9dd;JF6ozJmZODI^z_Pv0CU zqh5H2%4xQfdDiZ>G5yuO`8uZW6h{4W`UaCF_|%X3nEC7>kKHA2Q<}z}`LRg`ZyEd2 zqO%a2)H7!oim}K>epWo-t7kAHzbB7Zy7I{cAaop{d%{gL-D?ZWJ{&YUoYsp zu#z%o`kH)` zqS_ziTY87M8uZAY&l&EG*vKVZROARsT@S{eI@I-t+tW6mQv87keNKk^kR|1S8J<_D zS(QZij<~orvri9kRVYBP6@sD}a-N?FB3%@5v(^s_uHHq=+H|`}d`ujNS^F!xqZh)k_C}hXncfETbnT?m@}$GC_12E>E!Z3ygYYle_?ZG5q6T;aZKhFO4z_ef=Wyh zkAxdsIKj%U;i&E9M8BUWqhbyL{T1k8pN_$@RlkkZ$fsaU&Fi4I&1!IRT;SW;!f&q# z(qlV2qhsSDtaTEaVSf75rVHhnKr~?X&R7;15BK*>jYr3@z;BWAyyaFnJDVBlV)M&Q zeYW4L{I%h7JQg6m>)&MwBW=(<9D!rRpGFG5sV$1!FU$gF9maQXye2F7GTFuqM}G~XBwzB#Gk7MA0`n<34+$|^ZwXQ6 zJrP-Sdw#IiZTLT}-wtnz4?uhcZ;6{GCUBtCk-_I^d2Wk7otGkC_U$**yi;)2Ifith zt$FhISxdb-Pi*Ri zY&s0K=$J;T>-P{HHrROJP(*`@QE{gDSbT(pFmMU>az+wr>F4nZTxteyy$SO3*G03) z;d=Ybubw192O!K>agpKtYho#+%V&Q6Z`)NNYO6u%zoV`6UrT+erohr^hT|fo3!}kc ziMWwLnL5A9zQptUSiQoRTDje+hZ6Gcdo`6!ib+8?NmC#AL*Y_@2M=zmG2RF~)mx^zhMsdM_3g-cK05jjbC<^haBA8_au0`-zEbQTm1s})|8vbjy0>id`s zm`@N&EX-e!IEAU@Qa?H`dK(5vUyh94+%=(M7fBWv*er{;yZMjtz}-HfsF679tI8&T~&t2ywU>9;eiri%*s8SN!sC&ig<5X1vt0!X?6B#r|7RRc z)m9R&yL0jdPfsploM^hDwcr?yJsRv-CVGW3f!u6eP|`ODg-rL>Ss@#^IkhZ-7E_;8s^|G8UAMMO4%$@R={n02EWAHLwGcfDV+jxX;$5%K& z>0b@;v@cogATsyF8iHxZAwojxy&t14)A;J623VHwJ~ft*%94hfiK&E)&NVmJI7i_0 zmd-jwrlLgtc}t2|poDoM+PUiyL-|)TPD1deSgoMjnJ9%ixiXv6b^$LGj+5%XUvU&y zrLiymdg7S#Os0eD<*jmwJiU6ZeS+Wa*Ku<%LMN|$Kd%4vJs}t_oBa{Ip)K=bSC`^v zpYP7y#4q(@In#fcFAX)t)y;1cQ$uA4Na=PO*J;cKjfwfvx8OI9T4zpu!BY7I%V&Zv zR-+nrNmRJEU(nSiJNuXXhE~z0OFF>q3WSN&CBn*;cgyEBWZOy1tALho_-q74=8jRzp+5J zT}xp+HA&=1=C^{A?C(F>;fB6cVl>`fE;~Wad8gu(?}u%SpWtsV)1^+tWx+QJ{mYEY zOZ!OvTKSAX=Rq8epq+A|o4?$P(|4cPH|SjJ;IUT95pn2hor_@|rpHiHLTB6Pzlw?5 zDmzRt?G*Zj3juBh?V~!u6|!MQQfsN2={t+?neK$A{~SbFk-Yf|I9Ug6%ZjYy>TUxz zmD`d`no~*sM;J?l3!sww*kTN)H=4Ydq@T9!CFJCvY*j?1VV~>%v zD?X;qbMT&Jg$z0O>fZb@9 zCK(ye|N6Xr!mw<@G9$~{PR(@*eu449zr_8K!n|^sS|Xl9YGfaTg9)?HU!?7peM8OoJ8gHi5&p`+7{PU34ZqR`MudN{k62Ynip&L zaES1@w(>)k>5&-l!%E3PB;Y^RGL9Ak)yS$_V^K}(WXJfhtbzr#+z2bN0zoR zKQM4h*-M(4nSKT;E&~PFH7d42mzn36yA!GLk|oohpNl0-&r96D z-lSu*Kc-BylKSsAbX}44*^P3&v1-k8OP6%te`J1WT45`aY50@h(-gIKao|luqg-YD@&kvKPyIf`!>wttoare`)Q#NYvI{hYdGG+fV}P?X(zeIv zNGp@&v(N9ePtey2zil>V}=e8l$A&t9%glmXhh_{b74bc=I!_&=t za1ao9LVG+c?A)!Gq+DL?pl4Q`T{LjsPUgPCdm0zW+vjuQ&wFGQ4R=WjP_SQIU>5{yz9((=%HUG?O0(TY z^G;5LteKt7p&Je#V{Rsdup&J#>uMwAOLLYK`OEX`#w0o8E)gx z1tX$4^wWZNqjcZ>OCR6^tDwNy(MB-0$ej#6hTj3IXyOIDUi7ovWn_qW{F%)GMt1Q< z{;s3JE zw-G$)|LwA4doWOmibisSlIq_eoga3wg2W3N)}F_>X^n5Ybbf$Fg*z=#)m`_+^XSsRfwPxP-7-fNzGNJm*@jM=?B( zcur0sxsEHbC{t3WFl=n4O8j34lc+;8K4<2ETAV1(JJ?O2wH4_EZ1C`*B}M{!3%SVK zvhu6H8AemiE%D@db!e6%V`2)8ZyYyC%!@rCd}{nCsWkT^)5TgivBjO8D3S!*9b!L- zJomQ1y{ahPwMsfGfiE4QMUs`k2id%QWv6y7SGIYLg#nb(FS&fZ=*m4Kl16U{KL!j# zbqNuuUcIq6d-0S=YFWbHQ}MC5qdMn`J~BVzhKwhPmM@cdjMc}8f-nhuHSY}7v?m^R ztv8y9r`_{8h14sCF=R#ROypQAp1JSy)C11Wz~Hmn+TL@avhjk#Jg|mw#AzO^qsXKa z62WOGxx2O~OhVI-8uW)f-HZOpl=(B~D_k>`1wAxB)pwLVm26?NiafNytaxIY=Xm!l)jL1bS_!t&dFw zC;9AZcdILM#Vl*yF!CH2rP+4BDY$nb;F7mQM%K4OBK6Fbw7DReom-7JOURItTxZNd zUY#qWzd)vCO34S4!6(iazLEE$fWsvFzbuD7TI)5jk?jwX>}QMRUABWi(q?WBVpPiP ztj`=J5xxK2$X1Y~*N%{!WF7#Bvw+EtAUBfx+=OYGBR2nZ2^YBgduArx;;;2j-PTyY za4*SYG8uRl?dl~T?U^W}Wg(=oDZJ}3H`;8^A;ms7pIeXOOb})Mu(<_#so@VkLA0P0 z8^PG63NI3Fh`-L(EDSrL=(=G1-DhTF&SUL~4e`g)n(E*3{Deq?y3ad)+l|;Vp9k{+ zPFO^musz;<$@(nl|HN)+(aqTV-&+C#^FdjkLEzNv^~h=ZEe!q$pcK%f*ymZvKejgfDo@U~hK_u1@54p#5>zUL(l+oTcGt|l0hlmnb~2=z_)QP~?T-=!E1 zG%tuP5M#SeEM5)g80;pfELooF9;5@@45Fpn*PfBKK?1ppTy}*SvgbgJUlXpAK+QJA zyHgbwq)xSN{9)B*sF*@v07mf_gdW$k4XKjraM%viyiX5b4xW=#bPT%xHU?)M8>nN< zXVMj$M_)SOa6bfa4vcw0JbIa~N~u*={h9S)B2Q1|%k(p6s}6(<&GG;g-e>!)>?-nu z(h=bKLA3smaY%|Q{%$;)OpC1ApZhw^*?TndeL3qWTo$jkM6{{gc-H6b}f#J^2^Bne;?ql{Fy84Jdz!u|a&UivQ`&*cC zI5fonCKaV9RgbF@Vvbg#d_7h#Bxm7rkGpc~r^8y-5SDnIx>+nUJ0~M!Aolw`@lK6O zNMz_y@$}5hH#WO>wI9RX|2fRkSX|CRwkkU^3lj&6-Z0npjvnoM_47oKnW27+3|=L{ zDt1=!;pLp9Q_c$ch(_ZY#L>`s_g;?oLh%$B&Mevcp`=;K?5vX!9@dicU5Z1zO~!}g zj`2M&yi4C)VAFWTQWX5>K652tB{uY~R*xI~YfZ6S91@>`A@ym4iWQ8%MmfLp2{DdU zn_?xDBc^WDuSAqupG$R7)q|}&l+P$dF&9RW=@olC@lfO)J>?l645shwo(|{IP!}&= z?=Nf6&Ru*P(7SLDC)b>%F-r54MHT?a7<^zQmJ4sikJO0MAI?*})%OOFTcqi~reFA8 zVNuc9z_>gAsr#4Fo10^iYNjiX&%(@{c7BDThIxM+ROLE}Zll>WjASej$@@uV_}3rb zcNCq`Z*1LD*P=o-jWx%L&;`ig+xa}V@k=XwDrjlXD&`gVRO%*zm9>?_C=P0<_O-C# z!iMRUx7ey@!2my%E=sbfu>T^m`N~9hlp{z~>#QUR9CN4$`C0?rnE!yp`EAcG?>^WG zZ;4MwW$&KIp|kE&dbb(2YZcD?*N~!_;uuuL>{>5>dCzydmIQjg_aB9 zzVIMK!VX39u3UsDc?${10Z|{|xm=sa;2A0^iIfxv7GFk$r8R#>$#%`EmZj>f-TrPT zNa@u4G5z8`r?SWcpbJ{@Rr>p;o{W!My3tad%XRJAKBq+B9Qz%v_O=xHY=!EBH-na? zpYRH+t_l)Reg9z0={B?dLi-^(me^=aN8eK7r5Q-N*V zwKKp*=7#p%=qj#sQgNj&!ArwkYrl+6*4#hM1Ly;;2i!h2r@j|xy!cq-W@+f}fXsld z0bTU*4XonmT+^uX7y_}ATjesvIV{FwOpkNF0G8nPYq~CqNlf9goMj3$bdOU@va0%= zi#y6**G%DA_jQiYMuaZmu?mR@$QzpmvMt5g$#;G{2sp`1*71r^cFJVZ)>+dwCTut~ zC>n7AZVO{~ux~;Bg6%#*Y4!6en#3-;@<+_n=HmbWnmd7wfHvp)N~X{xoZk^&R_NM- z4sf_hafIgd{7OCojku#kVb5A46?-!I$GowqyPP+R; zo}#_Hfk}@~ik}(UDpE~KZGz4XaZ9>LUc|MNi@hwqyjkz3Q%^iXBr1*#@1vT1jlW7t zB6-U7XOrcHQ&VqI-Dl%h6`vgkO%B-#w#{k?ZQsg@Rp6~qr4y{{j4}}+Wm3A8A!--a zMvE?4F93IWzcKFhb{o$bD0coZGaiwajNnrdcR{-^9~F~yLH}B?t_zASKw?IxEP!NZ z!w7y*No;BG3E6XzLaZouoP(=9+uy*2htk^J4;F!^AaW!)T_5Z>`0`s!-mQJ-$Nj2&Rn+3X+!lXzA2#X&_e?O7bMH-ijqqtM z^_9pG8tY2+L*Cu{Q;X2wQ92S|ouo&3oQJO6o@I`XLeS^m-CvES7M{Lb4X*iWmR375 zo(D#?h4@V}(>_hjnW{Ods$J`E()p2f8f|bK+a8NZD7$sYaYB>>#P9;QQ;$F6G5yF} zUPlO{*eWLY`KP|dY_LOGadz-`m(x04dD=X^cIlIbrgkU}0Dy-N`OoX<^z1MeKmVRT z;A$iNWyY4!;hw*FFTxAW)^6%juQ+)4>^L(&#~|^}i-1IDUz2a6+|iC39B2aEwBR{( z-ZAwX(eYcn)Hzq3z?4^wMZ-~VEMMNzz@|2^e#wiVCsvQdS41cjWygmmpqt&`ttoHx z6Eq%R$M(Bzl|$7j}vUa zBDqgHK@FK-65K-h54(Ru7yk~Jw2#n$-@}C20FP?hgmR8~3-+T{N@9A-*w~)+r}UYB z5wCv(Z9*~6%7lLgILTZc4_%=cNBTR|j!>xw@gwMTCsthby3&ZCy}IkQ*j}mV^Wzla zzm50tpzG>UO_*<{FgF8PWMtNei;?9TJ@1Lo^8@6%iWphF^st5x1IeJ#tSJjn`k$CduT8cX0kP z*UjczH)4?S2tRu*(x28>|IejI><#1#4)SASv!A_2MZQoBSL9$ReI0<-!BKyKj4G0a zUtr+pcBbW%nUP)Pcg#-ttNkJMd?}B``#ua@pKNWZ(s`~)U-)!! z(Wfi+TiB12FS)_WEVt&EyKg1!ElEYej@Z+7Y04s4<~v>^{J`&C)Yj+X(fX^N`H!$I ziqkshzVM4d{bC($hjQo?#XbsIR7Y-%E|wQ0c=&51%Knk@_jnB?EAkcEXq_u&tbptI zj_R{fJ=y)sBBKtGI7DNEb6vY({yT*f=3%Pvw~O_bjOrqmb;|E zu=l1iNnS;O|3@qe43Ug@~3#Ot{Uc);W=w%rrh z4+VJQ4>25b_Q{y3yf<|`(5ks}@_npla2uJ%Sy}!)*YU^hHeLP^Vt4)-*1eH_Sp#`E z`u8u2&({Fp$9Wt!zmG!AJLhIY8p5!Zf=0-v&TE(?O8@)+j|*TDYX(NVF0?KhM3c6O zHgYVBZ(ZAqBB!D2C(U6XF=6QN*aeqv#WKK$M)@#GBk*Vd8&S-U8P%#zc=MuHou`){ z%tJiQv|n%jwrJvDO}Tm_lHNP~GMo?q@Bq(w0Ef{?r`PpUv&P-oF5?vZm(50pcd|pj zW$oqtsr83Rdi^kL`YT6+<1bV)2^_EiTHA0y2ZqJfO}$2GQOwN}@N)3IvJY)c=vt%` z<9!#tZQSoDAHGCI6MUeX9iPfbgIm3g;L(owvGpb&jIdjkb07S0gP|MiOB=-I6n79% zwWz@kH4`lgUJBKS_vBuH@wcP98qP1~69h?I@JT1euQajLwzxyB8S?;-HZdJ^cch z$^&3pXlpUY96yHVVx;tyQ?4GzG%@S`2{CcC6ysJ~KK$U<{FnH7<4+^;bkPhL=0yIS0t~dH{wKKCG||AE9J*G-5RWPbL5|x{U8h78A;?Tw?MG2z(k@lJmt{ zGGDfN>Ho$M<|=|R-n|V*Ix#;`wq)enVmcX>?13N25$BEmTWm8kq>$bhi^yaR{99dh ze|IO;ogv1oo$MI>Y1vvE{;rAWEX*v<2E(6~>$;%UD-`K8<-rHZ2AW7#@Z5sw!99I_ zPhNVQmvlE4*pM(eKrQK?IrWk;QJ5>gtt{BK7(|nfQ4D&Z9>BHevmARa@$jb z4gk<RsX0=ZSOjkd~RT@iR;EOuF*qiLHtoHR?EW7`T}x* zm)J-b{BQ6*VD4KSCN^UEC&O=wI||15e1x(AkJjyiQNTh8&p!=DxF(;YwuRF;We)nS zE-Hv^cazz;xVc<+b8oBcl-Hx@jjsE)XA+(e-qMrzgN(w-_V1L7!$+OgC=RIu&+!&O z#PJ$NdSko>$fM{tZWo@oXDy21h|jhz2wSagT99LVF}>XPO>@|1ss=fP;QSBF6G?P6>102})RB6{FjTGg2C*(=3pWha` zh&h19>$=?4^YZ}KC0*d2WMW$Wd!GL^iVqn87^-vZhak8L~=wtfMl*ibFUF&dpjuZVoayYVgq4zx)nFBP?wme7P z6zDp&7+0WKk?gt>X#|{W)ge;n)*CG!ptTW)q~+VT#?U?-OOB*Iy+W`vSk@wg!L|N= z?M_1v|K*F_Z&)-w=NKh@9RbC6EV*0&X)kDpATeggGg^>6GyGJWaAmQ87A>tZDps2e zu1cllsQ{f_K9~u9lHS4G{XkR+2NDg`#$%$>>Cztf;fmvg7~{W?&v#tAX`K&5SYx3j zok8tT&jh^nD!w%VbtailH{D22Ctuu#S(fiwu;XqYibA`w)>HuAJW72 z!T*1f6^!r&ynTQg6;R6q_~RY(R%yiTKr8CtH&j6PI12x(W`hFN;pJ)%1M)lC|86zH zg3~0g!YNXKA6jXI6sURd+O69}?#>QI zgt-oh@bQGxi-)0?_ib!C0cMM6kefJ91ig=@3KGd*Z1HHD2kRVrtA0#tl@Rh|Lp}vz z!i@W(y%dkGHb4jGoG~&`4u0AR=%U;mHFnK+%+F$R*2#j~1;hJa)33dVFQX_UjgdXj zwM$TVm54lrIeKM%?17&d>{Jg;Q6^U=(g>@TkqYRuf?-Y)oz^AHOl?NXNW$HiH%R6n9pMCHyAjQAC zPLGv(jO%a@oS5PC0VKD>>S8ekC#s56kRyy3%Y0{BJ>;iklae|0mgDACq(739^NUXd_GuqA#!QJ^t_GXx!+A2;a}o_gU7L^TD1TkO=-W z{6=Z?Y1ELJHf|)c!~1TSLBIBfs^OX+&v5ml3i}Q7nakjF7L6m*uVg!0!z`H-f-ctL zMYW9)NB1mb7~&s=)G|bu5@0ME%?hJ5RE61u7blk|&1tf^I2gz;O{{r7vg_o$QQrMv zy^GfdF1*uCJPE~%EN}rb+(<((9S;+0D2}D_J*RCxcw{y}Lk(q)arjh)hN+Y6zryX6 zT;b=>Aq^lkq$G^9EBXWK==w=4XeXF}9yR_)k1c(`ez`O0{bPiIM+1}4o&`?h9jlaG z)t5VKCet~=dQRjBQ_d&xdydvGir3@~4%CN(MQ|)srtb?(otB&qo zmDN4>1e7DMx~Fb!oav3DxsI6DPTFO0oK>-|v%gOEZ)g?x&t?~4=<9f!vSw)3zb}D+ zvM17_WkeW?kuSi<^ulM_=7%oT%+m{g{xUyqqZ03gxa@%N3vC_;@^YMHI&HIGL|&@cvt+|GxNi>m z66`jI8Qq2|ItikmZrg0}C-i{D%zfz}!PgDNU|ZVGw`aeuv=D_{Wg1S7LD$5eOJ9$p z)fTBRh-TXJyW$@K4a2_cft(+WL?CMQU!WF+obl9@% z_L7iChEizJ${;?YWvH;3 z4|GO*URTL&wTJodWM4D9r3Nq6xCP=(=+wHml+yFp*K_s+FhD}ypc1^u_A1m{57Shv z$0-)z$lykiA987)BBARZ5>X5cpxPr)!G!0frpg)ImH#QObcpvBc;gve5XQNPs)BJl1R8I>Pbja^#Uc~}Q(aXS?Bs$PUe_!4C1aW9?eA`lr9%-_pW5 zsEbRIr@r>UjjS8k&;aZop&f~-r#5nOW7B0RI`kj*t~M8!ep7q)l;UK!PyP2OKxQJa zI2K`Yx=$(>_}4H=&lB#2Uu?-duFT^5YL$Ga(rP_dWE0fDE4C+JSoN3fMKXrX%=D2= zcEWk&esJNlIT(aF)~S?0KhV`hpgl1k94s&6CG(A?qB^sk*B-y@@?n9Uz{#V^*edM? zZpzmy>n9yIG_#EJb>8cAE59^_`Ld=Be^R38rLn{*^KH?+ZRmjO+y{@q z7ndh>Nss7Ha^j_i#fQ6#GK8KWHJ&=7QoV=MZuOVvIL`WfcC1Pwb)JXr?G3XaC*6XP zL`8f6e_F2eaM>q0_x4gtmojIy&dfotK1mm=0q7Y8dWb3KGrvLJ(~-h2qWqw`COOHq zy*1tupXO0Xo>nx3mFw1vs5rZPdox(>Dy~Or%$uCl@A#8XP#1p8>Ttj(q`5b+lye3> zqhrJk6FXQ+tx zS+q05d_Es$d`j^;G@rR9#Ri%xF2XCym zCxkI1DQR8sdt{z!EMCsK7rKGm2kdg}3E&*8I#o6&a5orY5P!nEL`3He>j|PsMMe*C z?#3vAvnFZ&wn+rY0t~HY<>CB)Ja3ABRR#hq;DNos#2fJKkpTLWg@w3r{ zac4M=p%&?8`8k0}Nk0Kl#BR{PqkJ*e$KNd1s|S5vfbr3T3x&dL5AE%<9?TAd4;*g? zGI8ro1nnoy`KAp9p0s~BHP!nzYvaz3kgRphSzcS0VsB>u^;&*H`z1B=DWg*lx#u9c z$-%+FeDP3%vDaOxOuq(H24#Lb@2kyB#?^sE>EOuoMt+ zWyeFY({^vTJ2{xowkjkvWIV{SSkmOcVjJ^2%1=;SPh*apo(QI2h%~R^Yt2Ua`8}M! zbVSb%4ypfr`5R@{cAU!`3-n~ul&OfC;ai0Uy5h)^n1*-szY_7;nMTo?Rc6^)eZ~`% zNP;nUe+CVfoIdI&C+!68~-NjWi?&AN&0EQz|=HdiT|^dq2$wyF_S&TbQ$I z!SBv8(~4v1CG(cmu0s-AHm<>YBX)-&xPCIv!&ECod&L47PJkWsD1}N0Nt}Z(bvn-_ zjYL|t+oX?Ze0M(TwGJtbfBts2=|J2*4qgwTj|Z^Z(Y(H zU-F&ANXk>kL-)1vnpdoS^0BhPd9L`M3|>DQ+afhL8ocE25|gl@uj}viomZds)%%`z z@CoRlJ5@Ui5q=*Ih7Yn=Y9PIrV#%Gk;0tj&EY-F?uI%FvG$wnDx@ z4|sqNCVZ}?ie<1g0wygAfQRdM9hHBBPR4`FI7 zvnUc^=!%LIxhK;eX;qnY%MKZ!UTnj(ygSJ0P@-hyXQM=i#hV(u?x}3u?6Z%mdr$}m z%$RE+Z3bT3A-~59S0rUHKDL~3@93RjVX~H|w`hJ+?tC<~Y$8Fi?01|aNG~Oc8Qpli zH`Q(mrI~Wsx5u$Pmi|1ga>V#mjcO&Bat@hs$TM~Lr2L6F#==5i;Nlo3#hn+g&fVxE z^q5ckmwcD%)iG`ZE}TuL){cn&k$Dpcd(euUC}Pdid~C)}J*t zatPwaLBO?AW(cjHN%&*K3U982Pz0Uui14*c;p=b*VU9U3#1m@%g5qXB=xPVPUJZQB z&a|xK1s3UNofY7@{!uk2kJx;DjYL-RJ@VE08d;_4r}ixsXRXtHeZn}~c${b;BKadg zDceRE4y7EW;gwn|N9GNKd_`4z@gT{^VGBmB8h*uyhYEk2!OswIAF{^Jt zz`A;RFT#CKLZ~!^AJr-W3)<>W+wT2A!6g-}=o5Ow?YQ>iUv7A&s(Px^XFrc})DuK8 zOWw5KlV>kn%Dnb)ff9vc+=vVSB=NbC|5ou7`0VTlFWQUa}r?&*q%}{57ai zU?WXkv{MrC^kHx@0~g{mkoAa1BFk5174zzO+?iS729*N&_JWueN_;hv0a^A<70WQj zA!)9dg}$nncHkp2y4qfKu%2t!or5f@{zNeAOq5rIjsY{q{D&1<^15<)f#{WIxmjBqmcCN1Ar{YRiMHTYzfh8)V zs?iukPC=UrowRTKl)X)A2vO=B4=#gET%0My@G?>B%k69lrTr1R@j>TGY6cf$r{o46 zB>+%dO+d{{N z`Eq>a?)bOw+^kjCDYa$+U~&fGgtCle5NY;tNqA38exZMNPS{phjIg*25TAmXa-Tun zP44ugS89&D`N4)K3tgn8pb$k2kB^sq7!UQIzZjWpeZ{m*Lo!>*bwx66(CdCJt$sf8o7 z^BF@o@Om5dUM;tdt|Ka4X>*Dw93H7mvVe1dc3b~Bh0}vL7oC(J@%2Rxo;iT<8wE98 z>uyo3>nz2)>*+{&%YI*Vc}0`%S>~2GvI9+^U#ms#Od$SS<$h{3hK1Er=BOD|6m_8t z_@m5`evW?w(=tn$AOzsaV})e___|W5G!!?Nd{^{xP}1HuRL&Qb0j1fYEGNHE78t>?e8P6gCiq~e(IWtDbmvVyoUE;ma^D(M}vF#*@SkMJs&U$ zliarrTUC}L=5CamRwqrh)*3mv8&t8~BpgDkpdp;xeqzlM2N}v%Qi@v3+%7MNDQRSE zWgh0cRBBkAKCm`L$(N>s zT?%HRT9j=gd5 zcn@m)cV?3&a_Q&cESDc+H{wBMrBT|MhwnhcF3SW_jK!6 z5jNOLQ0XpKG@o?Yj#Daaa-*lXH-JxJpX&crI)K|+jAXxAg|OpFa_=k54h5M_oR>3a zL#l=!cdzQF_{eL>)FHNE{NwPqOTT|jVfDbP8MRmaO>T**4l9poYc8MpA}2JEN*Fn< ze~o$e+BzG1Rd(Vz`}tnP?rp5hr&^D_%?PNh+yA^)kuO%Bz)N#Sv=C|iD&z4%N0(nz&XFb&Y9?g z=43^)FeU;p;D>{7xKbzUQcTuK8kG~1j+8!%VyFHv4w#z?je?9vh@$ z$=c5n*7FyVFQ2u4RsM09Z}jApS$&GOy?Q}4cQidDp3L^hH&+4kss^;yi|M+nS60Ow zg-dyPTM?b>bP9c*SEJ2XIk?Z>P8+un`{E!8hw(C9nYl^xE893}+X;nYj*;}n|m*GU*y^6VX zNu10YQ5G$a&0q65zxRb;OW&}nqG?4Xdwj#Ufo{|#@7pRw#GQaHy6oR@sCcXvTqrqw z23R!eM6mI&Y#Og`)JFZ$)z7K1STISi$6*CrJq_gA*5q9t_M^~=Wn!kq)>j~ryl-FM zoPed@FhsYj^dgVa{AuMWh+!s0!ZOyALX^^~5zFcvB%s_O${954IOtv(&$zvQ%=0Jk zSk&c@Su6FHp5K1q>M%@k_0>NCAfzK+%fzX#^n(SV@Ui zfu=#m| zwIvK&P&^OZCx<_Y=aWb}NmfWu{4(S-y+bni^9OWs+0oXUyMk+4G{3po0Ohr%92`}8 z;rrq~jn+1Ej4*7~-H5VH=z^cl8(~@`dT4zUn~uJ>P4GJpA>`HCy;5O+yO0cr z!@4?b{Myf@x_*q7L4WzuHjD`z7)Yf6uaj}|6o}umq5@}Ri9y+HaBrt3rWI-eiW^j? zW16nU)MzK6Xf;2p*UUs-K?giRX@qr=R~(z|FUG3-r)mbDh~EYEDA!Tp49(YxD}Vt< z3N&w0hZvAeEkg?QgOZuhGjh2vwbDLr3re7It}HC(i=1!Q7q@)+mmG6lPiz8N7d-el zus(^nJy!RCEh5`mx$SuaYDBM#GXZ1mhYgH@O>a#;-@fo zD?@)d&D%cGmL7HGhgjwKU@`AQ5pDjtP2T}Jm%D>9kKP0&{k6LIvZyP|Ny^)!LVivm z$r%6We5Oivla)c)v6<31`eZd!Pkz8fo%52BPI=krN0h70a@+1wa!yB-aOoqPA@{P< zUbQ>W);EEDxAunYSEf%hrntFa%P7-17M{=dKfx*6IkyCaY;(0NyVO2TTAiu7lvUj+ z5tik5R?LAkET8N#l&(Ba=nl)d2u1bUps?|SHB()VFDejt^c+eNodgfL;$;nUqPG-Y zVL%~Y+A8|ARI+SgDDH&`0mw==3^Ci|MvC>sn*FtF`QG}l*3kY4qM;iJsh?`7!%^LO zkc&cx$zi;(t~|8#C3B7E=dZA=ObSs81`E#SD9k!`*zJ3l^LUuo*6c23Ul9$+Pu~$p z)UNf38f;1O{vZj)iFp-u~JZiCb#fOzOu4p zVfRd9;fgc;3M9&)3w~jkR+@E?{wBchU0CBr6)zI#c;3Xx zTH3}kHUce4UZi%1{yO`(%6+lvxq2r~>t78yYNm{ky{qwI%CWYk1iBL=U!xJt!^a%@ zb$F`J=T^4+9lK0P$~LBE6_gKmr}a}*)T)L5d|zF8zu9o+@c7iSqcVvtYBP1o{j}gc z!@|##??vLn3^feS(%jhw@5AoZ_h!0m&Pl%7IP>z(|{5|A+4vlrtG(TP%xDMEJ zlWstgw)<}(V<*L8lT$R9u?~OM9B=EpGEx3{P)U5}x~u~q&YQ91IW@I5(*=znLq_01 zimkqNY3La`)tR9QYRZRn{`oJ0i^=z-X1Q(99gawK9y_V84%V)$r5R&fE28ZzfU?aL zCrj}?@Y}Yx%dySmaJM1}qHUL7_;Kbc(R3Gxv4>&+(S+$FJp|eA+Cc%dot2fuz%h=h zV2|quxjF|5(BONYBZ{!$KAnpT6p-O4D)BoUrDO;e+;rn7Rss5NAe!$yiXq7ZeR|iw zGt-aEU^hd(Dr`&q-~#BJk&#vidF8TH#G+f?18=DyXW|)FM;GLh4%a67Mu@p)Y~x- z@lu+xo)hM~A;4HT1p7(a1^!s!3?x@i8!~ddy2rcr`Wfl{heEkeci&*&8@gmQx;#(} zL>E2fpk|k2We!vp!{uSh8h>uxZ?}q;3CTdiQy6D$Ttp8c6tPZiolxWl^2YN1^p`4z zpbfq^l4iXY*n`tQT;F#|Eu@{rUn^}d^477i8R1025K4LL805%$3I=+*?bFNdWxJ5Y z6)_b^rt*mXX|EY>1%P*;K!-(wk9oe`?3vVK`Za(0Z8&lyIc<1fb<_&LekTQW{&fv0 z^WZTCA>!bZHq4VSHsIm%y1a7797)LmwG{lY(ro!{m3#n-8vxp z^Kku%lN#SYN2c;eI4Y*%8}Z!u#X{?l$x3fOyH(XoAxixJnBP- zHQ1p5MHtSr2N|9=tDTuykgL^!i{vkjKC`6<;D93S;wyF~qkMF9EZg+)&4N5|(Q!j$ z<*Do*^4bh5pfN(fvRNIzv&Zjb_;HP~J=>&f`}I`*gUr35Fy}^SouGf%D{ckH1DhKt zi>NF(oA4$pdl7v9o*UuerH6F3zt*6H{(sXn7M!*EMTJFKVIyL+JdBq|U ziF`@415AT&?-vCg-~10TA!Zn@ Vb(ib$n%X5oUo_^Ydhh6*e*jJe4-WtU literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@2x.png b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b5c813009bd59004293092198e9a4ec46425908f GIT binary patch literal 58040 zcmd41g;$ip_diaDgtRmZyL3oOBTMZr-H1q|gmf$^(jB`?DJif>C?MT}gp^1~NQWp5 zf^JGgAzh?i(yhHwn z-~8A3AMK8FCwEnMa)&SfU)3G>Kk}Wv{7>`$A>0Lg=XLepu>W5#cZ55c|8J&u$=;Q_ z$I_wy=sV6`xpTb3M=ycp>;Hg9|9!mc>v=2?)qJyfd3%@RUCUj-ZS~22^6fppJplmk zpa0RFz-{X+;9q$DruDY#_SQS|I=%Js^X{$a8z8Uy+^^vr_rHDMCfuss0)$BbSqeaM z3+Pt>NwfDDPVdF2%G`_?*RrC;N3YOxd^Bq1CIg#fm-0=7ARQ+9;X0Be*u|UKrS3m z8UUQD0r_Y^H4_k71p-?DwM#&u9SEoa1m^(3Ex`7U83Mox0nhZf?D2fPh25x)@Mu00c$=zDXeG3lQ=Z z@cRy=4*}0xZs^W{j%#3I6HxpBY#ajdzksJ10JH!g5;;k&2kd%p%ohNitj~8Zf6$oG z;oZW(@io*n!g+vCeV^nW9xfp>FB`eCfGSKt&xlT%$P6MPZXqlw29j0`*8(|M%LU1& zxPcwK^~^M!eFHq=Ua4qCJa);*wJ@@t=;)el__kVE9@jijyV2kG?k20S6xDuvJ6V^x za$CArRJwNa@zl56VKO!@rf1Ch1W+3P@B8=v1BEk|!TJLS2ZV!AgBl0S?Jnv#8Er8W zdj`)*Zo!J&-0Y1WCvtHAdgseGr~6mr&%|4#$uqv6>YRyR%txzOa_kO@HdXFL=)dQ$ zLqnn4MGH&nkz^%_oZi)1(89HWP2J|IsH>3A2f?}%6;eyitFI;=rt#tD7XTZ9-w`in zM65sGT4%s}e;tC@381vbwGC}oqQ);@zLYR#GnII-Fcq}>v!=T6>C>l3RkkoWh#%E9 z)vK-t{2{Cb#CrY9gq?4JC+8CK@;imh%*+`5^{nAyXx}G!h!`bG@$04?j3Xxo@IFGY!}Zj6{z_c$y4kr7ucM~+5i`L z<)%qp!zHE`HdmHn${(tx$fW&#IF0=womM`knoWT*dU9Hfq?GubgStR=hHe8jZRLml!1U z)L^fv=_X`UNqmYkn}F6r4x*SZA?&6+7>lXiqZrgFe)6%Vp_CZZ@S$XJug$MhCn12x zPK{MX$m#K9d_@C5p!CO^OPfY};+L(En;QSZ@i;pf@x# zxNQYK^j=hGPF!EiPPzrJ-eDeIf~0|@Y^G%9Q*9wJZWhfd$m^i#nZ^{b>yDkD;;ab9 zmxQmyOkgaKtGKMV2ihAIH_}Da_dY7^3pP_*Xt16j-C@s0PjCynt~Yu)ck5sQ35#S6>UIPjSq`>&qlWv#yy zB))#IJk44BEozYy~0Zu@g>qGxLj!QRcuz{e)7{Q;b`TbH?b&!Mc zv(jN)D(`24yL4KszCgozQ(AL{>@>s~p?8i612~sClY}kK=K7R8_6WPdLW1H6G4_mr z*N!TK9tRUaZlw2 zxQrop14hQ&A(L?Os#&A6G}wUN$4caG53fE8ldD6KsYvHl$I}fd7 z)Y$xx6en7s6I^baNsDCaXlh^I6fXO?uH39ksMdjuheAA$CQohDKbk&6Da|0ma&P@3 z^qprc-PVqc0<#@_mQ@T-UDjW(pNxoBV{%+JPL1lvh3-qn$1z*5!~`j79ul6Bl4QQ| z+Z5G7_XCy1yZPDy!Gz-j>bZD_)n zhsjUr8Q|kmvvCRuVZ{-CXdT${`E#uyA01EOik9=~_S9YJ>+Ab)P@wecL|=-Orxsh| zV1&4At0*+q!IZA=MEX#3@JT5=;Y!K&St9|JC{srnh%8LCB*`RwEVBK^DM=7vs;?I) zZv5N3#rNiNcdgh$Kb2ire=H_9Sceu}6E#$ibGjIk5brfv@s^?=Dw#N09-x_i zcu?^9l3KKeJDI!CHz3D_muCHN*kUci{gKz8$ z)n8t{!UJEEI~igqtlZAXPZU*|VUgH@_~MRV=5`M@>a{j8>Tu;UKi)F)5@ibI| z-3ui9WBfBM9Soli)?8kfq;Zrb(h;3PSql%bS@^`E8fPwh5q1CL-!zZ!^oeliy_2V2 z`&k0Wb+6SW3bWnQeMmvRQ`tnjj3&6`aMdDfO<_&tCw z0nD7@@aS??-mX66`J#zrd4fPA`8YDQ!1!}Rqp46g@yx=N|6{}0Qi}Y&gU>Y>PyM2o z2f0!KkhE)qUmp^{?R;sMatdfVNCb0xX1BRyFXru7HfKh+UtW7jyfzFUzxgPpit@qI zHz%3YwDBPazC>3V^)vDi5mF8+Yi;LHzbbh8F>>Q#G*?sE+4q?1#DDdal9=0EHcFRI zmW-GK@wtK$?i=XCS{%pAU0leJvfZX@0po8Uv{0NAj--W+O&z-W23`!+bx-mn4U8sL z@{@j4;d?tY!%n~WRB%Z&UFC+&Ac0)7p2%NfQ0u5+Ff5AWXIvlcDn(DGBV@+FQkV5@ zr8Fg6jvZ>jJ1n=i?PNrpWL5QGsC&Sme(jrD=J@gPw`1~95N{j9hGV9q^+V4^VG*kL z@~=VM%o!Rjoppu1!Jat?7R{sdkZ86ofdn&3(nQ-~)U&%)!Up5t-dQ;0qk&CMQKUkR znyf^!H19UhXgziv7j{mrbZp5XM401RtgbJ*jqDtny;2wOi_NI}R+qOV9G5LUr3g}h z_^c$HIx#Uv7{y<;fucDSZbC8`g7c!;*6LsZQuwlSKi*q-_^tc;^?*fLE%_H7j*Mop zX)voS{CN997mTnYWn$71+Pck|@kk&W$J4C!8!N$&#gJ>Vz{N|`uDidOIVcwaXDUWr zd5a=G46Y*+hu}$Ahotz`r)r z^CSIAi?hOr@VDl!8(z-f7=7`Ja@f5uaT**DDZTe3`)x~Sf^TLf!@5TL%5ZmgaxiTn zf|RIa8Xe| zA-F%oIlaAH)>WqX|3xu5y4Y!-C(c)+$L&E;DWCollb852YObE{_q>Ie$(+NFxc^bJ zQXTqc+mlKo&zWblpV`gOK_n)ooc-)e{7Y#LwxUc zVI9wz@dDL=+o~kyTfLljdeMdgI_?wWau zDzVP^^PK~}Wbm~5#S5+GhF?1-TTv6hPx+jg##)J_`C6qt8E1;d>;nQhUr!pB(nO4? zwiprZ+5LeoU+TX_*q!}$CgEV`>P5S={jy2!FD2Jv#+gt+yhnHF0vow zYBA>Iv)p6d z!r%~6RL`$YYO=)=iM=s{^}Kiu;$%jRu)G`G>Ac#BMYPiL@MmHNh>`s53t|~X(_H&S zU8ah8b121M2$fk^%C=7dwbI@M{)fWPEN~oQU){-J)&ePbqv^$l2xB;>nr?m4KsW*u zJ&}m?v8j|c%m;bv$M%LjF9zL7UA)q)UYPN&a1vkrU+NwY=`;c-9bwz3`f9(ua zTlVJ+d1yk5i0($`@so@n89S{x(gzVUi5R*Kq$?bpO)|!N`$31ds%y`l}op^?qm_bNHElrN#o!psa0{UBs4f5 z54-ll2$=tiw+??OYMK&V$HHbD$0-{Wkm#cp1-;56Qk7W= z)+v(Yk0H; zbxb)at}?|wWWNnBYNLJAcAiFG^Nv&a_9hK+Xqli+gGvX#gw-M6x@ve7%gE+E$U=e5 z*{NXMC+nB|2~rZczoKAxL_`Gp!4uOQRGD+9PNGh%h`3Ldq-A~QTOf#7gYU)7rI zZR504r!^bR38df8Sj>Q0*~?XeHy!g&EMC1+@f1kY_KAgXr`15zv%t6ph7oFBs+8sv z!44r`#!we`1mRnp5+h<>MW&1wy~BVJyVAX3YvnvgR}D5UwRX8_IyjirQxP{5w6C% zr$Eb^oDPerOM{RaVEnaKqTyq2C=}!(rap{2FL~04*%H9;16P+^aZ{+b&&2d)Dv(5A z#*;TC`G2-X`~^Ho-nP|X=pyL~Gql8E;tg7bZK^)~7JN>sr2SMi{+?j4W1zWYRMldR+zZMp==dk>#E^)Yb+%YjR zOE`rD`!ZdU&%U2D!T383eWJa& zLiGbJFU5ZCzcV_R>kfg2w0p==yfPw;fPNy72I96GR-8P;1B)8JUrW8iXFuaj1pzoh zD@}bAWbhVzJ_TZHW8b|eBrS>w?KCF$C?5mi%B*kFK`l>uqDb*#{q}jBeP9y)bm8+D zy5P1l&-8-+=jf{M;xd$dcx8rc-D9O~@^|N@q?_z|{cLuqihVY|<06FaR&T@z?LtCHulvo0 zYPD6GL_olr?4_jL;A{N_Yxg2tHWYuy0qb4n9TR58-iRW(AR>t7d@k8i|%_3fkD zbtl_!H#R#6L!_hzs_W;g3%SY15#b*0oLjx$x5`+bUHdJP{jC+NHZ4DQ+QZ@QbS9e+ z1KIu*{dpqLpFq{VIERrD(|qjy(bhZG&0Gs35(VLR=T-+hy~!s?`Lpr-mI8EGLy^8P z{#B`E^}0Aa{aV}GVE}F8lnrg|iBM~^<8)pz6=IZ6Mh7ZIe&E?f<;m^+`9=2dexGW1 z{cxoJsA4g_mUE9N$({iX$b_%=F6!Qtvw1H9QZjKq{arBp~7O! z96~7XQ&Ljif6G;)nW5=pR!04_&9L~w_#RCxa+TJQAXRA^c5~!D^A0|U3i0^V3xsR# ze{27O!OFPD8QVw;EB(g8)o9$_Lk)&Xj*eU&eBYJvJ4YF`H}|r71rjt;ie~4krPqUQ z4DVrF`>GThlkytU4mt*@5$Ki8lb28$qxC7apYqUYT@pieJt8znYN*cua}Np)i;{iT z(qlOTZE`G2y?!o)d}XPNH^7ITz{E@GTSr-L2hFYfV)dyv^9uEUbf zl=7HFrf5a?oh0{NGW4{V442eC|({ue6?(G4;fMoxG?DNUnYL7@FTh`f_i z13uxzOPu(;yhQnnZEmL5TF{`uv%gXyqjKM%;q{hZ3}T?=bLh;gt0{k`vd5P_NLJgL4Oo4fRgVrI^C*N)0#qWm4{cq(ry9F#@ ztnolcZyZ#dFg~b8-1$6f7OL`Q{k!)UG;6I-pms*y;;K*gy~ZP{n|Ghh-hV7SF;1?C zV4nI*b4=|>98_=VwnDC2Z79;@pK9fIn%YVvLj-+Idw+g~;`0qh@XWtCji8vBOt-;W z%QADA3_o|UK){3SU13gNBZkYJ`e5^)nH@q}n;lAYMov+z`)1nfOwRY#|8OCw5^m>Kfydz`TVXt(vHY@08C<(&~ zx1HHV9YeI~;3oD2h|8reI5`Bjq}0~0)BFVpm#M~a#<$*nW{>@;>e53svqF{-$>Ym) z(POZdva)umdyz<=gd>gWm3kRp!Ye(-_tgB6y#681Ct@- zwdmmalDk8{Z>E>it8&h<+d>{r>bl_dc|lZaPzM}oGylDdRvAx4+{?K7!^2k&Z;$V% zVJ1F6CXurN%ONZj^K=X54<~+oM%imV{S&h*z)2c&7Ag|ULv9We zA?oGn-KeX*I+QP`pGXtk5B`-*JE(@AO;H;wdfX@<3JJ=HO1$4%-UTQwF~DN>|SW^PF8c6wk? ztSqUjxs|S_V=l{|gX{b~Ic9CHk;%a zf$q^Ht|l1?L?0;`3sI#wyoX5EHRev&W{fXR!2Za#DW=86RzxK@V7)WQu&O>v{H`P_ z*^13ul_?+);dha1owpN03+ediz2A=av+HcNo zdRibiSE0)E0F+$@3=@@`meqA9ic&Qrfo!>TITfO6Jzd} z^f@n0OuRf4fpE*hb=Do7`16N(grw4F(*~+zI5ubZ4BR^&qp+BWJtg7JC(Vye_D@~G z&zgjS5ThxTL}@51MFVin$T1}i7@@nL7js3igvXRg*wM={Z}4~CnMaz*{z1G08NaAUL?8UQR!;KEaGm`qmIv6&W5SMwp5gk zS57{WH4Wh+h#{YkxcmEpLOkZAt)H}E@)ZggnWEp7_uvjve{F<%KZ`woP`A4_3`#av zV$Az_f4rBHld4zQWwPCd?kg4k-cJvUDMpU+{c-kgl16M)<6?bf+42?)*HmK<{SI4I z-)=-{XxOJbA$y<4YfV>nMPo(Rxox*RN40oW?t7?*ce1gAU%yHcx#z1@MuNS77`xM> z;cn9+g{NRl+Q=e39>y4%jaur#_0gTRjW*Wm=6mt#2Zrbqjl%0suX{zi|CLb_8wOfa z8H!5ULOcSQm`@Chy5PAsG#B}goyj!tK(b;=a~T8F9`xt7O@?AJ<^Qh1OdfGY7wmwxQVB=8m+~8!oYE93-E_>5b1up%wIqm4c9n#Y zqrrQF448d5DY`sKkNQ3F=~A1tm!f0^P^a5VA{{I9^Tw@FkITJzq#p?x!( z@i)BS91dyOI8*ATYIc_HVorb4IBcl$FVH&)oR3rbl^^LqiU?^)T(Gk2Zg=-{VwQeFT$;6xBHpO?XwJ*iBUGFnQ9&%G$QKQf}6A0Bqv@*uz^ZIVa z*_!A*ZZ>o9Xsrt#JX8AgCUc&Gs0&s}=_0o`D&|TTflMxd&fh3*g|Q$b4It0w+$ob| zMqUzu4HhWtD8%R9&A~SpR+SBH#l*&A!-f`H1AX2H8%tI}V#0dw3A9xbmhXoCRZW;h zd{{N(2)%X01sB#TPn*}quA9&<`THixGZUn&T&=6EkyzQv#l83$r%l9{x?6Uj#{Lg| zkUHn?PaxynF!;&BKwLMn)ccd*GP>OA`d?GbqH?%=<(>mN9oLyXIYw%3rzhtRw*KyK z$XG(d@+dN{LUcWO6y(@4oQR+1-eDu>4$UL{>icHO;;l7uQ#dUixdotpU8<;ns_-<0 zZi8G@3Rk*s06`R^W-7Z5E8axDMk=<5$t2b{>i0PI$JXWAOfu(#_4_<*O^@Fq(<;G+ ze$46_=jAijum5SzQ@nADWNI#qT0yY)55QAsPSU)u@SSJ}xDoP$|rs)34N%dJ)D% zy0zjHY)^K-#B5GWRhBJo!6H%!I1xG<$Y(wFW$z5k^ps&Xh)?6;coT_rW-*I4F7iE1 z!(o!K8a=J8QmN8jTAFi1gMrxOWC~r9%A%Q1asH5UC0F+TE6o|2-{aYhOy}ZuZ>p!1 zH3j7JT)IeohFyY8DM$uY83Ro?8Z5h%BYs;nQyPL0EXz3DFu1(WcRpEt_fH0TCJCTxLP~QdMQtcHGDBYGCSLf{C#hK01r~` z=d+Xh(1gvn%2PAZbB3|nRi6zKU_JM?hUwc;nHrkp1>}og`OrRD$?iK9E?SCVhO%b_ zNkmh))P@AzDNLlki-Uz#sLyh~lzY-Rh&uF$@DrI^kkg6$o+-#TFIC2y7V%D#98OHm zjmwEcy;g4MwfHa5XG$|jT7BLv9QSjs!9;sriJ<(a1PiUUogEns=$GaN!<6~QpAb$= zwadOOvaL&BqeU*eGc8`gGls#fp$uX52~rb=6^>{7qe|y%b$X9Ap2PSm&}<|6G{(5; zl4D6xttnBKk+$osa$WF-BdhIrrX_u5sa1>XOlZ^N!{W~%?oBodBHe}KOrpqc43_Ic zJ+8vvy0cy8HTv#gv}pdh!sA`Lz@7&lW@ye9aCDBYLR&`XjQrlK@qX+OF5=pP7D{cD zOfhAGmzaZ?^4T7tm4MXb?(cRQcznrdOc!C80DsuMEg7!jRje1@1B*wt^Q2Ff1KOp( zlo4lF@lrX&J_2Zddaea1tw+eMOzX_QZv$B#ys1zXFpT}XjDVY<=q!|j6 ze5}zkm&X>kk@cvH|D_qWqwgpIlD$jdOdRdQ345{qZ zgcP$}ec|oAIJVbEHpYWJT;sE(iCC1=5#jH!T@Xk*a@^B{@gAQdO*({e@bG}wzYH&7 z{~w*}ed9c~Aqf5?Y)^Oou+%N70tqY5w3y+Sn|hrgW4GQLrbbf@%p^KK6a=)Ukj*P1 z_!A2G*C+D+NYN)j$zf5nhpIndOAzO7Sq4#Rk^IOy4MXp(RC=&(_A4o0pYdpSFuMENU-NQg}*v zQtq*|;swZ*nRRzLNXL0`CD)$cdDFXlBc`JTmXgSGEIBj8Mk zIEVgAy5RlK0V~r@kWn$lKZ-`?$8BgBaZXLGa>Q&r`w~{6K3bdQ+)8h`B4pXMvuIH# zvnd%5!v)2Iy9eXBNA66^>xynM%Erj?Zc~a7qH%f8h7-w}iW}H|?-`>VzV3;GMv})r z`H*>x+C$1ev1t!5IJ?!X8awit@2(Gr%SSfxd|gMcs$r*A{HdY)oqF6`3m#^ znmR~A?tyDWYJ5Fb!AIBpE-MF!>0>z-c)(!80*DyvWr}U8^@K5znYnKs3CB#bn?@MsMmQk@m7L z3_4v8+P3=gQ_o(F21B@Qvt<7XntN%wd@8oS9sT!O$!a)dW~e1w_{KeJmCgARE?ZH%FW9wm&A#eR81vWJCG!13ENY(c0^hFQuATw5Q!(>cEj z`7NslpFfoh3Vn2!@J-lxAwU1jDsuB0ImfJ)Kxgs&7tb{zXzQ@998^&5Mmk|hfGC02 zGUNzuGea0v;;4$b^SI*Cs??!6tO|1#CD3H=-?dKXRe~%`*=CMv^ssU)1zogC>){Fn zQF;Q27eK_iNA@Sh;I3?5UvLr{a!{O>GZea`xf-tK%Hw)_Y82pKT&aHr7z{U1EncHdU{7|G$7PdmyH>&KEGTSW0v{&1`B?3f4i z(rKiwhFg>7DW3(<%ZUyXi8MXsk_0yl+LX` znsFoSym~m{OEQa&q*yOBIFE*ZVx=qWN%3oyZ{Q23c+}{_4Rmj)Dg=A7tCq(u72~&k z`j3GaviMTp1Z#bJVUOlQgBGpdSk8IM;9*RoAqdpD%V*Y>`vujbGyF6vQKLc_H4?@5 z_m4z=64P0yh|P7QsAcDxXbt>%Rl65bbDZZj)w#7y72J(PA0%Yni^dDlg)j|ypKC%8 z-@7gj53c{RnP3zN2IVz+WajYG!3KiegGS=DstB47rY$e&moDOH8a@umJ0P5hG$62( zMe?d`gVN^SC?hQ8Zz)MF^!tb3*X>#O;ACX~$ii(tKuziPk(j|>s|B0(8cJw zUi62Y@Z0u!rb$cXk&c+A&JSjH;k`JjvBYymKBD#INZ98qF;A&{pq}A0&8@yRt;qYa zxJ*Q_A+B2v%_auJ;|9^zg7qP@6%|c*>g2<{$j`4ATkM|_Xv$f|OTu?aIRCv0ksAlx*F;Lrc9Dz(yOuBYyTC_vqUv*CBcveJslzWed)!rl4 zh;_nZ+axG6F-Z&@ec7BM$!GdHu>o1nj=Av&D29X237tIyH7fgh~YBa4< zL8_T3da37^;S>B*T#}+P$}^CiHoe{nnq4<4>48QHD`mx?=n;_YgsGo1$gfSiO@bLN zL4UN%!0>{Fkn}t5RIF9^cso)tvCW`+|DKq-g`7q%M~3uE!G@<4Gm>g0?0siv zzqaTcA8XK+kc2+a?E4k%pfJeD9nBTZiZU#4c8~nYjm?g_+rS27z4PU$)tKT(h+Jm( zeg2@=E2-P-3VV2)R1H7mb$a-EA9FW0jc(|sN}}fGqOSJD_d@^fzwP9!whej#)=paxx8H>X~ zFgT10c8Mjh&$`UaS3CqXguA7l+Lc@8RKMWvgXmX$B=UQS3#0fa7M?-GA9zFfFS%PN?DQOp-C(y(rO=?gy>50zoURzE9Ru6jsa#5$O|H4fPvC;WT?XHoK&B5d$7LhZm3pF^yRuXL>N z)sU|*PfE64G8tku8|I8r5nsA??eGzQ+tz4oB@ZM^C#YIJR8q3+Y_GMlZc(qpT69*N zN}j1ExikbM5X5)O6*(1@=DPM2S(6=NeLlnI&@Hk&a2Lbgb3LQ^?^6(hx&759^U~^* zjZl;B!&Iz&Y5XiFd`!GjYoA2kE=Zt?WtCJgN!6u48?PeW z=UZ+LszeU{!24rDeSb}l`_>cSU2RW-T_}EVbm;?}R~=oeF{&4Rg6>uQXEv z{!V{o9qjG8JD*k%u-d9QKH*mlr&B>m&hPh+xXKd@=0Gm4UIXKiAth;xu?(wXiCo%$&}PZY5L5Gz6Z?6FYuJL>>O7|fw5j$ zeEhX7=Y(wo?Wqqsaf@C^IW^O8P3v2_=d4WJ6b}X zqrjl}0?*p>Zz5F>xwO>X@E)ZmHU=O)%mc{j3)gp9|G2)-WnVD>>UoL6F z(`)2yq-sUz~ytB*jplh%5HkKt<=&v zg%Y^sO&4`6vb!GK6dsEXx;)?3YDm&LUq1Sy)!FIq5HNALGvMgyN@V?S+hBNS7~XyJ zm5x6Q~>1KpoMsG#7Ob9=Ewy#R+*)kZ=wbqM0auy@`}_8MP5Tg5l(ufD%^ z$afrmG=ccDr$Jom;#$s7YA@rtIufu0u@!A_a4=Hyl42A;YuW!`0s#0R7vGlyj@CW| zhnMHYdLrMyrxZ6aF3EYkbuxA~H|uXpw-HMRSFLbtsMZq}$`m|KjT@s>meX)U;urU< zOT;yTl6HumA@g=m-7h`#UfD+)X=GCZzFV zP?NDxHnzA$-{qm(JSp{@;$zfp(=>3?;hzh=rx!8lca+AvtZ?J~!o?A02|0#Hu@2SYpL9}~cPF74?317IHrAW|1v|z&ZK%{c! zN-81B=TCqbh7SyN@c(UgVBL;Hk$T|3f%nZTwnSV~kDA#q;(V%V2Br#i=0Th(lbh7S zT+;+w``PeRr7ZSHl3f>>S>qmRsH0FO!&C`d!rBYw`A7myA-F00!gnn#?)d>O0xgFM zU9A{60!8;L7iGA@EK-obiHF|ZXDA777;YCIt;PX~euWjV*t`tZz)w-MNs;_2&+s+c zd8WDq4ibwz5`VE&I4bt*+Okb( z->;E)bM$G|R z)v<%y4fmvbZ=`N8S9zP&UYG1FKD;LLyHDVu1m#n-%fk1RL^8PyyrNHQph5TAHVIv1 zo&0RL*JJG{F$W$oFWR#7uy$ix$RcS3C+FI?P~~qTZ&Z#5a*Th_P;ijHfUNc?OWN|O z4X%tyjqsp$6*m^H6q?dAL+d>gxp7Za=dEuyt`7psuSK~-X`lUY7Ze~=ESh7lf+6G>&IblyFKH>S+0A= zhsyS#;Z&01{CwTibdZ4q49doM@6kM!Z4_%wDKDi4da7cJTK_rAQTu6o^Z00}90v;K z&4%`HST&gsDYX1LxZO!ko^&k}bnv!&iJoEFnYEm+rBi6UPXR7}nr>%9T*a1+Y@ZNu z)En!yON-YaVd`DdT2Mnf^MASC^cA5bQ$cxt5nuLyc+98bXCi|-(#w@Qi>)iL+dA90 zhhOerAI>Gu!8q4z@MAa0^>y0n%7?!dXZ61+>Uy4GNVo-@HZvx`OC{fb$MyRypS@%D z6S=2OQ^%Cc3Ss{+B_(fLAR#&#o3O6z{qy0+XW1)>=;O7%A~+gU|I zKRnTq$jlH`R1y9$WQ=a4O6l&LW5urAE4+cF6kHO#mkJ4r3HQx^{+=9?@Jm1=9DGW+ z4W=91siW06)#zh+_D_GGCo|x&5V*W4@*ollji9c?Fn(F2rY-7&#Thy}mj2+B`bTk6 zsJF3OI>hlcj`%o^I*RpX10fM%Z(pQFj#h@|*VoohEI$US|8f4)dn63^nm;ok{zF9c zl@WG29Z$-aVH_dKl}S}n(fOy>x5A;83dUeCt&dHn5+4Fb8H&|QY^n{q%r*A1N^Hb! zz%=add1AHRnOBT&8Ze?xIpjmOCy5JkP$Wge2d zy6M@WSmyg@E|o6T3^R(R_N`Z`V;}8%Wm`8#+?8N82fZneaP@;UtL%FdHhTQkQ3$b+nO<)GeDngiO-k2}p#oSE_P%?6y>TQxSWEK*Q?yq*Sk{o(NCU%AY6xot6XRul_Q=0i7s3 zrAs>uvS}UEx}Uno8d(OTn=XeG{slcE(a(!Po7~Vh2dE0-Oib8J1%cRpMuGa=9tPir z+)gbh+XMxz!u(Ba{DNFpE>Eg<{1+)Wg{U=!Oop@)62Blzl5%UsHPNWzXMRWVE>b8+ zd88hO!fvl*{Mi$(eOpF{sD)^n&22X8f7RJ*6!IdY3OZz1@4`r@Yy5-z?sbHoo ztxE$V*^z-i07gW3f$i;CLF91f??c`k1X_FQ$7q(7B^#MgGrjuZ;{q3-6C$G4Rw{1c z#(#t@lo_ujzFJM8mYxuSI1v$Gewg^tAg^|sVVd(%yhwO9n#P+EM)eaK3JD?Fj?s~X zxEs^cv18MlDA{C#($jpm@fkzB|B&-f2oWQkUDfnhWLhEn_8p&jTXdI68C7C zTlv)uWnBzj#T?w-d)p=HY0b)oRI(1p{$%}z#=@Tr+gNVs`#SoCbkLnwy&evub2PWK zyxE#k#t}8V9vb=yT1$TR7Zs{!z;5@rEM?WB4*Vd++O8V|bT2x^AO`$#VN9%Qp2U(?lXL?Li!~uSFBsuuXzFA zZ~BWEU9PkQAH9rlSZ#to4R#&lxyP5URdV&SSFO0^-Mg_QFSg^uyeK&nHHzN}uV@-= z1OGpot}?92H|o=!qkC+lOA(}zksBZ}8U&>zq(r);JGK!@iLik*e;Pq)q?8z464H&N zgucA*b-kaTFVAy5=RWs2=NBhmxfP3w%DAe?zoD&-f^f;Tl0af2&ce4~RXw`*(BVFV zr32}UIgT$VXn9q@gFZ8!vIwrqe7=kz7^?H^aMhN}bzIgNx5A|SL7YnKxZW3iAAIGQ z66_31bL1WMQHag(#D!A(Hn>z66u?s!f5HxGAv%c#s8 z&;f&y(Wt5s_LFF0m!lHm!H}(;U!DZv^vLy&iU?z)WhplECnf+BwNdtV(c~0kxMws- zu6{-E-7;v^=0RY;xi*DIrBuQyvQ@E*etHJ_lzVDsr!-CCp%_?5NLkqxY6sl$hup5M z*vN+i82H5D1XyiytmVH4jQ$e!am;gtK2QK*q56+H zpV%*it{n?w2OF#c;}dlOH?-|-EhgI0?RLOBD<+5q%60eEJb#H+wN0O~&{^y>iw-q& zi6jY{jX>c?!*IF>s$~i(#9wgCPm#{o_7N{6R7w|u|$%4vZ|2S}bw zR>TcQK4{qEaHBy$vM~c?W)`ychS9uKn9*ekV*R}`I&cHWtyI#&IXq* z+T&N)lkRQ)p6$&aAI095YW~7%iB@wQak$!W%#tTg+c_k?2SvPU2H<$x;2bwi_%KR# z7-5?%)GOpZJY`q#*f!Vt#y5F^(A)l%l>8E;FaM;cYn-hDJ4DQ{g_keDBaM?}5$yO&?jP~oPF81BV6AZoX zJF!pamOp6Sx=l|Tb`_vN;=JK-!%!u10BZ6vCo4xh>oRm|tVyPE653Hi^jc8l*Cp(e zH1fX~yR^y4cW44szL2!sM7Qja_!tE{t5=9ZCCw*Ul^3K8oxhs*FznbenJ;axWIj0y za07RMn{W$I(}YUvjF_FAgimgpru1p>nnlmQL4{mo%aK%S|HIuY$%pN)k`um>u@_-? z9z3vi)Uu`Jn`q!8^ZUNJVmFW!r^>8tm>so0lH*8iXC@er4H#&W9YhfJ7_-LdIUmyj zx;y_fIH7x_a8~zG^y|Lu+tJ~pjVB}T)p>{3WfI_oN|P58N2ehL&GF9*csR}kW;C4t z!~*&|3iRVT*~25H z-%|a>x{PVfb$Ah@A<<~5MBigphyi4sEH73AT%McVH7r`;|L5Jxqa;VIPpN*TYzI`h ze66`r!}V!-@%|J6eL6^oDTD$0`ftU_;FTIIvwett`Rj3-j}%X@_1i%16*7=Q8Yvth z1n{ezF4qZdvCAB)b!-&xG9+cTbLsMLw$#z(T5ZuOm=0ceE(W{kc^W^yL-BEYvh3o* z=r|RIR(JE~O2<+fqll433E#z3UWFSr{s%Xf;( zmuQpjFRaPNwR4!FW?vyW4i>l0>0jDhr`V{GWA7(h>yPs6J5pzYCzhD!N5YqUYY51> zj)c)*Z0T2G8-;t_KLmO`izk# zOjA?r;D|BufJm$#9MJD)7N!HFCWL{SC>`_W7y@@X2>R4*vit^7srRY^`s20C-7L;eU2B=Ha*Yl7u&yo<-3jn{O=Fn1E-P||Jbfs ze)*a!gNcTaIgaLg1AvX1bz3RwAR8YXDO7 z7FR-zpp<(oDI{v7emRMWCICI*ibM0gJ|vu94hF@Krg<&~F|>Or(XrLSxgb8_(R zUmda|%d_8hf1xb((F;Oidd7>@W7{X8&K>%EzLPAfsYRWr058Dc01A--BG(AI{ULL@ zCWg!X@FBZI=F!*xT7j&Vbk8F%k93JHzSDa`@!zd&D7n=q~%vo?0r_$!VwlO`8QWnGr6CBdxM2)Y6*=PC1flGmipE zzX(_F?G^WkO^$}=mk0hK{%^b2l&Y#+)vvIOSBI;tbW`2rYw|80hB?Ae?hE21zL)`W zKVlX_Ru_S~loG1kQCyZwLF6;@LB@mh&r~fxST{$W(UJp<(;k#WrK!Sf8?N{N_Vz|M z#!IydAU|H-Sj&AAWlAuDkR3!r+{2)eSUz|T%>d3j<$Qv8Gjjjh2 zC|c`#L(;+PrHs$-#}_JPgGlVTq%2=$W_W4JK~{j`J(blP_-QLz6^01(6_ zS|IOUGr)=zT_n>F!ac4Kd2QYGpR@}u*AMC$Y>fACk-ddnFD6A6qV9S9(DAWndz{BX z#lS%4339JVl_-X>af9&OGfFp&Cc8@DT+$Qz=j$XGpv5WvdsQ6xk+*%Y7uuB4Iwh2BG1zfc<3HwJ9prxuWvQq&>nvlS`Hm#RtD$q5Z&$Vi$OgTs$i)hmBb23KRe zr+$89M+ez|6$C}AN^4}qju_r^WfE$-yF0oc8>FkCUnGT{pX44rg7JhZOA-L91i)+V z5HW~*RowINsj6IVkWLN=G#F3P{I|piG6r5DQ>LPKMHqyXM0PBuS6^~Ni>3swE|VB{ zGMnGOA%e8wxx}darc70(M?hkog6kl_c?Vk0IDEV}R|JK>-2TNl7k;BkY(oUzit_heJA(~(&z^Iwvh^dd?t=*ne`cM!D zz=7m_D<&oo$ZfQ@yjCy*D^UzoYT^4zvS-NRGQW`pQqn?#zN4uQ%+s2P{TEqMi_V-l z{~K5g??2%x*1jRYt=fD7V3l3XO+x+Zlm<$9O&(uR3#L9(@>V0{YI4Kt5HB!e>1lk( zLGy0GE&F%pS{?YDb;{&@mW+cI-FO$7g6Itn(ipyEU@f7t>&&&?p$@cOAmCz8zLG(g zWw_bLlc%;YI1l5&+k4&YEN-P1J@IxF68Di{0M>;8VySMucUcCUHbSja{C_`J`f5IYhq!6t*;M1Y)fEhEV zq+L32&`0h3{P%oZUBQepo1B@>ll}K=#%eSSOmQyJzz}_h*EYzf+VH8x%vqB zdhSx)O9u}3YPMJ`kl~;c$A~j$3Zk7ZE=rhvgf%&?w2uACW*IpLbU0{;`u8xRm1RpW zl?bRo$Jd26LGcg19+=)&Iayk+Z<4v2-9hB?7XL}fvTo+ydDDE6 z4)nb*baR`r&-+=B25IWXaZ1Y?e$O}>zQQX@ONuS|w6w%nGbJ@w?z%SkRLZCiTl4VX z1jrG;hM*e>UM&Fjy#${uU>*DTmV+JREM@0bLzs-j5*0Z_W<(Bn#HiR6M98To*)j=H z=o!Aa?C*@;HI-IvTO#qZ_va3)bB{4-ok6MrmBRkAswq72ihikP_r{14K?1L9s*lAl zuJPmiud}+ad12RJ{+Ds$B<9k~@K>{Zk~#xpW@dj7X*~FG`_5$D4V4T@7v#AzqupyW z<;PNtOiYw{IFLf{-$CK-_^++Z7rW-E5Y4emVK@lh4cXa0pN^m`ux`IyjZAU_sBx5uQWQ<#Y~Ve}zi z(*&*wCT&^X&#KW6{l#ylrSzHD>ZAXEUm0#KZSX zuHKAi(r%8iu^FPfS*rG^k;Ig_9@02$Wp5VtPiV3IK9KfL2g};&IvLXXx7Oagd41(B zCC(_M>@2&sYT+ORg|Ho7&gV6-d!hB$walTG9|4u|yCo0mdMMB|2bDHGxs743k_qhp zaoo4v?=Ut|h4&w4!RR8XW&zJ~?eBLqyVA2(`_Mcmy}s=$pFh&zTB+quiw2&Iq+2?@ z?I22^to^#>g@EhzPOpprTjvD?Bi9E=Hq6`lPDa8}t5ZVBzjt`y?(PmUoK>arVO)C8 z!!C9-h<4_s**^AFgOuK!;bA@>Ka<*hl%!+Dv1;*c07v;y_EJI{6%Ou(ol6p(+gC&c zw@_UpHUI*V?qinV>W^7STM zktnVyqrizQs@v^Ad{R%%z>{tq zd6JCSncS9Q!IAE_mZw1v^&?sMDRP&V|Bn7Cs6nom>QqMVchz`AR2N>8#P4l8^Hj^f zuBA%YfQ!}{*+d2VrxSub0tvMg9Q@SHhP@;@co2Mv2j=L>FaJJyC9lbJ{QVhz?NWvtBk`R22kx$T zB{yb5g=SYL_ykS<=COo(6wMiNveab;^~OZV@3SAT6NwLB+Bt7g2kd!uK5Lu}7*-Dk{F4gW z0Kx}%G)`DNZexzeZFEQ^0G$cRlqaWet2P|0{9Mw)-MgX za3%CHdi3b8Sd$wUdrt6q8otMW?qf<0I3jI*IY6&uqQS0}yml@0&QS!gq4g|%?c{{C zpZe5F2j#aXgxCm|x)UsT$mmjthgMHCW-mdDb0_`rSgSsE^r5a=8?E+xexC_ReU5S; ztEv8Ko~_X~yAEno1wCQ!9{FhBK!Fyn6@{u2bpDBzUgnkvrdxc71ga67wpEujTIi%^ zm}wo9Nh+Xa@7wa>K z;{G9`1VWCu*28`sAv6;cnm*Pb!M1aK|87Yi)h(2P6ihVA1(7A6GC>;B8VFD0KQuXy zGs`8$!}~Vs2${1V^}Cw7ml3j3!_W*^sPAy`I~M!2e6oADAaNMp&hcuN*cZSsF?sO* zd*;v9;{G3LNUlHL8=%#PwH0b=dPBa;N6`P}nzdi|Bh>!ev^jF;#hKu7vC4dB{YiE9 zZwe%T5mW@pF*T|NRt}IMaQ##V!wK&+1ozdUub+aY7<^~zd5MM|T5C!|lv$Gu@0j@Nw zfD)0z6?lpEn?J)kHIAVPf0sIUC!H!T1yhspDQZ8xXF;`E1+_&&m7u#lL}hXO0{7JW z@VFm$#!n(|$qo}D&bi18_L^C=_^>8hc}okmYWamswp_knB-ul$4|4s=`)OYgXbb4) z8=x8G-ZiGP1x9s~AYZa+C(NccM{sbnsV zvjMkpk|-|edBZ(_jfm(%YpL|W_n+_O><~DwRJr*Yc{=@}3p3GMe;sSV3Bw>ZC5z{c zzm;1au+y-HF7LLoHnW>M%sgVm$*pnu1wfeP*|&jjzVaX-WUBx5)957(}zlxzfen*{4mV|E_FYi(t&DIbw8$0Fi?ZlN}{n~ z5~^B{R3MQ+oA-e|-$~03%$rp#uA@e_m4a$!$@<6813~V7`O1;;tE*Rz z(xUWpCLow6XCj2pCGvsLaoUDp2J=ozn0sy+&K(YD^|yCws$74feW&sctX7l@i~6>9 z`?E(&pCZ-RX$&uf#ZA47x$Va*IKp+zRZZjfn!9q%aPx=VnjdTbMbDY`%axbJeq=Y1 z_inq}n~}!IK+V3f_krJgr7P>@ion^~5*$3O#&9nf;I=wjWRVIELMfs#($+(pMNRpg z005g`9YKX{Z>0Ri;7{Lszu3~dg}@0ue*PiHSNYu2h1@C2f=%qYqk8IonP?C%%xpP) z0?0%z3xekWDdH)Lvek5VUN3m<{&J1G@>nL|E=8T)~qUjpPZ&ZZ2*E5`k z^y^X4-j`ME)FJ&cH?EY)!;*;)`c59nt+^7+83I?V79eOaM!_TM| zS+>z6j1)b;^B-jgx)&}P)rOkb>K=un5tDt$ACE%9KSt+|E|f*{aVP(s(|rfB2>3i; z_p|BX9}EdY`c$Oas{@zbCF!eQHuwDVOGM~~MF@I(_5Q7%>3{NP4iP!2@-&W#3=r!_ zJLq9>e<~iN@>4Jnvl3V_?wEi1|PV0!VzVa7I)%>g*~z zbh-IRiJgolsU=kaMn}rc5xvQ~LU!pr^-D@t0((uczbxAj66`1Afo64CDl11pley$% z%a=|V4iG#`S_plG^s~Od%0_7K&kseiVf=qMpR+m$o{MHW^{N*&5u+XETo#&Mq-H5G z7uEk0AkYj9-u?O9b|V3R4U7ZgYhKR9#UJFoGNnca%t`YLCc=jogtT5e)2>Bg3sR*k zYMC~rA0Wk_{^J{cs&H|#7E9n#7tZ-G9$jYqTWDvJ=;rGnIsI9tK9;VerphWFA!Tnz z1Y8>Y_p@FSh#G%E0jP}O%m8l&yp1f@`l!Y06iM`&D^+idKlDqa@d4@ReB2#XMELMh z=fkzVh%3j0CcAX#XsWsK*7F5%K@e}zDahc zf}=C)jdbGxrsm&)7NeqVy~A6lp6@|j^Y=MS!W)gX=#PmHi@EG!VgPIL!YPoJm~wkY z2^{9+?LY0ycH6xK4{2w2YNqu2U=(d2ghq}EMnpp*uJAPB3hb%s-G&P1N--e}XK>l4 zx~>M9Vg3hJd5}SR(j9$&ZmQ`jGS~|#ALpOVa;(FIZ9g$;Jv}`+<9J(Ev&}d%gr~ob zKjr>^!5#SPWci-Wxh2Ea=Cbn*=7H7o%ZWXb`8DW%=i&-}$ z&th??wULnvoT8)G7P8l|nE%#!@{+Be9L_bI?VOx#`Q~BJU-8=35%=j>3tqxn-nWdl zi%k7KU*Z3RT67a*bHBc)3~~aLEg8y1=c}r6HdaR25*o%Y&UQ8iM9_V)&$QXS{ zA&VfpQGB9YFnW5o$=X~ZmC}tc>JX$dIZ#leMxNN3-(u4qlgNWy}j%>eO;Dg#-F7m--r&d^Lg*(2} zs9di|)GulfXcSGRx)#N2Z}9`JZIiY6zdh_D^Z}{pV@u(}n#_GW6P)q5qbSwXmF{Jy zg8cS#hr3g+!*Z)og=eNwWk?!lda zXYol>e_!kuKN-y-A_~o~=lt=K6L?<6%4z`Vvp_|2m(}EsXd>X~k!|8~>!tiR`QjBL zvs7Qjp>{_L?$Q~2FrFVJ2vImj6_pr{T7xNbay&H%GlOK6Cf2R3>t3s?ABK4U`Mtpa zCE9s{)k?%%>R&qiEDP|^Bn(`BlKHFr!gjx+rit6*=z(UGDDq-Ia7uLec^?Bcb&E06 z)3%(%lAz4IV?UrZr#mFsbi1*gVU$xFeA^%~he$mjxx2!%t&=|#iH-SY?Uw^_ z$%*SUvB(%mim{sbLxuGY?4nEeNqy9&6A4MaVmWEqAOi=crz?Dd_{u#X2>1B@gul3{A~U4yrT~k!=Sw zLYDUd>{|Ek=D&9i{Tx3{(P#b-m;kqzbn3y-?F@e-po$$#i1zAEoKFqF}pfLf~MjWY5n?U5UU>9lp5?H*Q?k$AtRab6Xi7o>a z(@#R82UQun7)P1)#6Na7BO#{X&22aRtF@>nif!X77z#L*w1Hi7e}G@PB7&3a5UEM{ z7} zb_gWl{JTHfmuZjGJRw9SShs}ce)kkdZ8B+w5}xk<`X;E&S=Lg#jFN!4j6b(FJVnR2vy9(BefKRPvVDqx6 zU)-_!y{RIAMz(ESsWE6ql`D$Ol~v5x`vM=#%HwoS&h=tReo4O2b1I2@Y$=2``}P0{ zsqv2*cAstg=hPwZ$|pp}N*qLR`;ANm?P=d1Ndyowe8BqMQG5}DseJ~r#sJ_(L{!w~ zsiL}%f)-qxLp^COMYdihGe6WR981wN+lya^^>zAp+%6NBRjRZSm2m}73mt-&K7rt& zxNJ10Z;sOyH*oLNCz2kquQ;ubQm8jFG73avCV*Oah?7Ny%uzhV6n??z)*E^1wmgLv z8?z1iJ(TW#=bxW3X2YuyXdIYUNxY`II=Uc7+RF8Z6x}GHd-;-{MgEs@gjkT43ANbz z#9W>Sp?yDNqjU0#J^(9EjT;VL6@C%CCLoTACI<9{tU7(1$rSBh2ur>l3(X=9f5OQSlFy6_@hMMe!J5ho#)_D+ zon6kyaug5*HtMBO&!LiMIHwB~Hw@gN0Qexi`j@UvK5Y8__koppamLtW+HJ0(^>vj2 zc$eUw0wL?2LzS6{hlp`9$`?!t2!R=Eh{b|G0khd%7EJM6{^@9?aW|u-HqEY%GsdJ^ zmn54h5$+kIy|Le)&zL`cj{+7tl4e`m0>x8eOIOGYceRkM38{Kj8im*?Q0ix!GOhQM zT{VBp?2)spvzK;}XR6ucXnc4a!Op`QKVg2OKau>~v*Z z>>DiI=f=1P!M8)I;QzkYBkl&u^E6Z~aGefv)0Fp)q2mH9xnCL)Ezu*=B$5oQ!o*y? zM0Wkm@=|=)+2HYq1U0c!OB!XElm$-f8u)d_-r2ZtF(e}K>zagQM=W6F9oOlW4{E)? zdc1UiQBN>dvUc|P*h{xbn1GEEQP5K>VxFAG8Nn;_Xf`d^fKi>Y0Y><=04$m8@gOg6_>tk%wxo?@Sp}WT8VO>!o2pD4KEljL{-bn=cp@|Ma)y zvU0%j#+H(0>H|#@J>{tJmDA6;wQUesm1h%*+qXr&w(5+8JcVRdZ*lP%^(zxzXoW|4 zGwXObXXPBgDt?E)G7C(H%{uTcLDm(IQz*&BPr({7E|^0DZ8K6dan5i#K9r+`u~UZ56NSqlE^e+&4a;OHO^zHaP}(DAgPIH{k@~9+_Ut*Qfp*5P*KLOE z4rJ`kh;)PLp9NpG?$i9giL~#}2seFCPYBli*g@|&Q6Ro-es?hlOCl;AawcG&M$#eo zO)qNA`3aw&tYiP1LQKmpUkIuSr=WBJISPw= z_50rLpg=oD(PR$^(72Q0b96b4ye#0S46QWhabt6%q1`8~sCck6JCx`#FCV=&m9=E$xL*J-JPPR;8{ipya zEE)OnPAxEPGG&)iTdpAv2+%{;)iXmFu>`K@p?!)I=ZG`G!z@UATox!X8^CF8=m@Z& zns?ehN)6LW$lva3B_0e~cFde}L1IDpaF=(Wt{s_Mfdu2}EI0(d3hV7cCD1pEkDIx* z8wAWp*+mzIv;Wy@(~&c&eR;`2DJ$7#V*6{b8hqgX49Io(fD2Nv0~}|-p39zoO?=sI zHd&>S7U9tu$vytt_z^u(B1o=0JkO&(|2`?9s#7XKfuib)IG|h+3;ZLD2si$#V+zRC zqp@P&icdMxeRVZ9CR>gGK9(C4wDyt~K~Mp_q9J{hb)+JEu<6qS;F8LHHuQ2}o@BVj@K)X}V$R z4!IZuPpK4-(NW|36J&X1wyKwwJaAmLS*$Ddkz4MIXa6JYbdS6Hfhn?bJRDO_`ur2h zQ!#2%H%>s&H-*1qA-UMF>UFLA9*nkBI5{i$)w!zpo>stDzIB+C?bnw>)#L&lW_7^N z(Cd7(QR~x_rqI9a28?{MIJ^-r5DV2yDa}iPIBOtrjVlrf{=Q2wU(UdURu>dX1n2i; zADK;R*<+R8fU5S6j<`d9zgkeO(~AlmbJ}blHbAzUp%`I1$qS#RPfn1q-~X1QBQ<+c0g=6zNaH}rsXS_S)o+dPqt;au=TAL64vA@L z54BX&Q&keYgiWI#dCjtZ#)kNEOD|sqaCBIuO^_?8Ml0GGGd-;1G<@j<4cgg0`L+2t zM)6$+mp-A!4&2TK(W}(KfMt*cVz*3wiNeMMcy}+@I9=6^sKjGEocUTKSz}aZYM#@c zU+y*vc`4L;#mjp0`3b^$Q6As21YqyzAa+8s(pOi1?O_KvJq>n{GZLco)(IjcIu##I zA<=Dg<$4yx%PV^=;lITJxD4$-DRxmXko)RfTKJ)V$M-`vd)3<cF8M6Y@ibxYSmz-z~bN5j$xKGP+6~Tl{OmTny=xSQby6QMA~st7b}ek8{UM=R&j?glyTHBpGLhuz&`MvalY~*f zq!iIK&Aofu^HNwOz4zoP8xw3Xaq64ixt)j@bV%Ka6sRNe}I5EkigkJ zn)wof%FocW!+YnNm*`L1EK%g;=<4#V`QPcw+*H`+arQ%z>$jKY z7w$s1dy-iOwvP!Uv@r?O)n$ahH4Oz@lGr1Q413aLg1lZ3z_mNRxN_f5N1p{903ol( zj>{>^K3B)c%5QlQ5hGkG#P5mhL`$w=>$*@28FBi}|HSvrKvAE?*c7<6kOMYLDg zZ1k@WqD({tVZb7N7@IL{y98hHZ0|e)eb;bI=-XsT7+~!v3)_=mqV(yJrY7k#GBzk?k z1AgyE7RRk(akE??YTy5K);-`Htm88ESX*Jl<2!>lh607*u+!+SVK^pnnt7A`l*=1S zsIWTl(C>3*Im;^|p@}>$VO-BrqIxZttbzp05Z~n_n~f2d2r1(ZwWK<4?c$E3CzRN1 zjw9#gY$MdzeR{V$hOaW@>g=1sC1#e1EiBKJ1t2Xsm4vH)jep#om^%7x(f%mh(4TFP z{x$Z}F=i6|(JeGgLO?(PdpR*U?Wy!DrPCQo(K1eOkHv}C3E5ehy|{Q=Aj@mU_m%c7 zXqDyDXnsBa4*)EFeE0fWxH?s0UXwEC>usweXDhY(%K#sTvqE_ld~Yl-=qW)Y&UC@~ z@wUHZW`GOT-!arfU~CIM&RnAmB1!4h@O{6|18VG_V|O%Y`3@a8x|v zW1Jt|O;a!0EOXy=XXO0gj}>J!eYnq-WrP1l0jVHb&{^;uGs60_5wEn_-FDvEHdv|L zHH+i~pg_T%+HrSr(mkJeSSRTlq@Sbpa@#n|-Lbxk@~Zo~Qgpr}-nEm&9ZK=OKan|u z`44B}7fvDQH8j-S(8%bGeSREvdoA^$5w+3*lM^(t0La6oY*ROwn!lOt& zsr+VD5OEn$fvRe>erAC&kl_+pux$7)7U$?qW_aj(%q;HPQflgX@hk{4`Ks0-TQTU| z)`%Hs+2Ti9;C%G^>MwLHfy8lt=trHW?moMvTdHN=hLs|lvBqTd14g5bkZ>s#Ks5me)_BydH*b@a3I z?wK^-%4HpViOEMf?gfM<+OBZdBemoYYxRwd#H=a0`uhJUDIo?}pC>|q&+<^gDs6BL z$gujsAi{wafWBhWGKzDR`n%Eejn`c!V;~gb_TJ3gg&p zwxx_v=A;T9EAIB;gUiOI&wM#i@guTuo0_JeWBvWUk&({K3d-nls$0mg(ai_Ee}^YH z(_F9?a_EWoS!v$~=P@U{3YVMdY<7ci%hELUwA--YEPO#cemsbc*40l37L!gS(+?QVzsTrh5ttX$M6jI)oV+GVzp&)#E*sT-?;#7Q+$jfe5^ze045i{ok*!SaVsjc%?{@dOj^?p|19G^I)Ie)FTA#< zM@D?V38s#I|Ni{#*X4iBT0=Td9n8!Q-B!@Q2ePl4G~4$AT* zp920!etl|f-ixPyd}3hW_@G9msWLV{J$SlW>vA07V4?x=v!Z*b851&N?k=XnI25*( zKeWH7Lf0%CJsSC$0iDdhv~)#KVV>zT>UUOkHW+GOzs#Pjsd-Pk!;;dH+%%&wQbo~C zdJk0N_X9r?#Zi(!_Khvp+D&>|1si>6ucCC#<#ndkgAzhIZ(AkjQ_byQc~4dDLwQFS zaBt`%UM;HE0_+pg>>O<|#Y$^)gku6Kdz5t*+M1NVxEa({6bttFggQA1jIj3E)m}Aj zpB2y0JRW~h=lONN&2R|3M+6|Hu39G^$1@$j%lf33Uv;o6Z>Ym+_{^!P5X4LW!QkVC zp`VA~v(oC%jC0;{ML+2*x`3e~WT?W^SV{^oGx*Jq*-Mp#y82XZB_eJBT$mL#7j z`N$vS=<6DqlL{X6-GA_Y??^dPa%Aqn8q5n5NwPtP^IjQse4%=Kd@fGP=79(OeRz=L zj`_81IQ7CiKJt_Oa1ay3U6(wLl(3I*iU{w z%rhaD-j$DNqS*zvUk>wRO)X08a!KxKN4AGFg_*0zFuC!)y!`JkvLDE3sEx%Alniv2 zRthe^%k{lF?}?FNso@>{V%y1xV^uinkuxeV$_n5;46R2E2N;DLG z9P;mPccuvDhH$6K)s#iyn%Q1pV?5Avq2n@ zkAdf~Y5ix`AFx#BJcfh`X45f#4~>gr21GR0w&OwFr9g@IZ&+u}MUdXfpD~j6YgJ&l zI9))vkEfoebLUYBLI%OCWu=`4`#&a~5`qoi2&8GucfSAoO)lAZi8HF#w|6`wD1_pC zJ7#mcKNd{EzE1%EA?2`X5G9D?VZ$z*eYiQ=5G#gpBi9_dH>aoPr%BN^$gFo4vkTI} z{8374ooWz|U~CSPFpPy^PnYo1_?~g75|f zjuN()%jU`%3)$KK#ddM?JH6z<>pBR23h384zkM zQy0)_#)5+{ng8zj_%xN3eBN`|8N9BpPE1Vn$N&=wxmi5cZht+mK=+%a)QIViYev_* zPjZRoqVNZ#JPq|and;o}-OTDS=GlJ;ENXsz&G7`>FYDol!_iOfmy73HIZy7eeM4n3 zt?@Fvblb9%nsh*$GkBvWRKyXcpiJEc)98l@^Z;uniGx0yLC|hrb-nd~BpN7hQ6lvN z?GD@3aO2fjiq7ZjaqTUJ;P(y4Kcyo@89W#}QVNZeV+~fgbK<-f;0x%4sPlllMfvFMjwrQKr4{okcuPycJ9loX}LrYXzM%(JVecFq#t0z!g& z>sRxHse~p2Ub8==$=}O>R?kg?-nw~6)H;8+piM_5dN)uK6QY7_2G#F@^KXC zU~v%L;LEa3t)Q4`v+Oxi;4e+iT$HDYmR9h|F>Obxw!*Ypxe(z|22;1^=!No>OVf#!q$0yvg{aBK*y!QV;usKZ~VES z!u8JuX!S_`CBIT)^y|O)BfXEC%HKZDtWt=Ri~5G8+A>DnJG;P#{DW@|l!*xdj|O~$ z72MDl&e4!}3#+w2?+jQRpFX)lmDVoSuHC6iJo0x? z%XYm#(rbc?fdfDGM3oYa<7;109Yuw$ehykAiz4}4p;0rA*wB0G__3U#pNmXNf=5x< z9!bP^Z%cgeg&4&Q^7B#V)5zV9e0evARNb)A0$wgvq3HVesij1na_wsCky1RHhW4z_5>PTGlc$Bd#G$LcCR(9SDiBTN% zf9UNX0+4pFK$SpI+^xVn#vj(UU#MI@5F#$W|4wdfdbJb7=0DX#Ln46041%aTt^jkDinRw4;fiG?9op5SJcROPhAzJ&PRZ9sD|S)!={ z#?l3~3y2KPLeSR@c9%RRI7%I8k?0fX17S}Yd7D+GIBcNMX9vE%SxUm3o`R=Iz8rFt z=(hTpT&M~o+uQEGk+|4v;25Y6*MQ>p<)Zun*RpMOTFzx`a0JFcBR+(Sip2K!!TFG! zvZ%MPEibbdV=4bHEgg`^`FW*((Q%-HCZa;N7ZBu%I z37?G|5!`6!)ukPQdRdITB3Zi!-Z@MN!6|~Zaz6M^(e6Du8AZMaXvNQE2*9v*QT}w^ zm3?Wzc~9?R%G4@(i?^i3^PiJKK*7znYrDjR~Nbn8*&I z*Lvbro3b27D_pB=XvC(1AK;>J{S6o^^81&_1>r^$Vfb?4|cZ zCquIL99QXrDAOkg_hP1FU9xQP9@-%}HU0;(Kux~_ijqi?5hQLKB@m)S_Bb6^MmqQ= zjwG&Aa`gN{pU4r-spfFBi#d|yn--oFv9l9J8ka_rW9_CNuHAD$njguLr0!){9jQTr zG4U9*Zs#Apmb+&$*@JbEm#jU-0upib9*U%c2J2L;0;%F~yHy`4kUFfOA&kbZBGRVh zXkSaX6$+%<=2{FI0&-sqPtSQO12Ds?II6CaoIyBZlsxpwlS`0}w(0STnZq_z<(|@Q zbVIZCY|i>h7>Tt*Z)cf1hooLj(&?VQ@%7a~h0%)QXaF&a%a9!_5@G~zLz1OPLr&^# zHDDQ`G^7kP&Y>91|$j%v# zb{suM1K+@2q`x0H3b(YPuQLUbU+yReeeqV+*VZCP)m1vMlffVhCIl!OA&VicLiG5` zcGs2cG!Ni{&rY9Ml5Fjg z#1S4kN(>FTQC<4+kSGxc-|0l!DvuOLN{;ULDO-=+W|>QQZ7lK-1~UZ0x8Dh;EE*qCM0(S6lsc3QY5__Bs@A1c7yN6=Eh^~R?Kkt zulH{~>y#o<2Z=9^ z)hIDeSvJlK`U3)FTe$#F(0R&(CCJB4+{p_2jpc2_MN-6SRHCij*3!~)b9WL%cS2Gx zk%acHuW$UO!U$dlq_H2FrOz|TOcFRs5===9zp;fh|Xd$r0N+iM@p* zKjsV@=uz>4#5EF^NL(YW3?N9;)2la`h3?^L1W8p%bBiD6G+&oy9FXwF7DbXUM<>!j zj6S{kDg7#g^z0c+(ni)!CK{wdLXP$xRU93%*KWDCE*w^B)Zf+y^-)Vj2s=nTVaJm{ zh={MM_8el=P=zG1AR!|}66bItKVF}$LVlMZWT?8@Us+Qc#PxTZWSwNbN6^}&)d=;7 zL4reqd@Uy4`C^gOPb48q7oH5R48*mu8+)6{e3_x`#F3<=BBr6uzQ zO(JKf2tq+ZTSrY;sE+=2;h<9<;rS!sC|i$AVE0SXqvGYr)JTMq0x2G+A_<<^K~5W% z)~82>q|wXvtrVh44oJYt-_411u97b0D$=v5J5$8cyFn^x4u`WNJ5wH+oB=T64Gj^% z=u|)G5f(_H(B3BQ#FDcEYp3e?K*PBv7zL;Ls0!;N#LO=RkM;QM2mcO#z#vpj3{``n z+lqHpTz4rAMuy5GD;=AqD&ag5f+I?QSAp*I?;U zfV!tXpUaLTK)MhT6(WkfJIY}jD=vA^PYKW>P$6NBw3R`EwrrV@ zL<=NgH#l5ZalHM|k)ECd2m96(O7ZXGV@i_7zFb&XP$VgufFljjQ}T4~Gn7bk+C3!r z&}WJnOb-@a-N6Iko9xcc&d!aH zBte}ty}ncsr0C=%FHyB5>BUc6CE1Gf-8YLFwaB z&YU=LT%q*a{fH7Sgd7`FAb}<+mXZ)PBqd_7g@{Wdj2P1P%UHTZ;vb?$ukMfcQRa=+ zktIhG*Wp}HL5_;u*oh*ITZ+UWv9}w1BKv}7rAksH%@qdevw0v1?cJrx*vRXwxItbD zq(1>jQX;)eksxSr4)ZlOR&=ym?IYXPfgm00ICDaAMDtHFt3g5#JOS^Xa_8m=x>d}*$9!uJV8OaN5wLBHP|&^^v)o*b z>8Z>jiA2dp>15yd+Rs1#m%MX{i89LqaD33+qarw9WT6lqEf}g(i<+{?kfA1x_~0pR z4+;#B04a;!x^XpeOu3puSfFWY2x)&&bFTo|m6@0@ebJ@?hCS1(^;Z1*!`1gX*LEv=-lSS=(cN7qw)<8ygid0RXuTCAE;Q)fX)J85BKYc90ZB>NSYRk}yGvPT7?PY!3=vhiwN%ctJo{TXQIAmGPqoFupg* zx%0(y6&c@NK^#?A^X)I@RzE{hb6cF^yA#KbeWp-qR{>s9P@>?D!#mK@KP`%Q{p%KQ zx*|olbVT=rQX?NmULA3IB;~ro9KG)#m3(piIv6SAgmy1(%;tVjAmyOkAP1T=>D=@p zR2=ML!{&N&C16+sRUE!GASEz$x_n8qq(HijApHdIg+bzBkk-)&+&!`n!vaS%ZeBXZK+nQH>l5Y-?qUlAkC7$ae9R{$j4(V*ixl^*#y zVjn4t1_>mpdn+NuAVH#yBK7JIx5VHq1#r}=L4t3E5DiUIV?ll*Azp{88tP<*5DyWm z`Q>$}rjWkaLClzafrXCk^u2x(-L=34Fyy6yvz?uS((&W#S&}j$&*!$(yGG} zH_}lT(0xjaG(}5^Bo(|3MiA9`!a9>472@dq_mO3!%?$4&&ErNAgOtmrbKfL%E6`Rh zt590Z=JNWex%x}fk9_@io5YO)Y4~gP#APN4c9|YHMp`Zel2vFRmb&{ooO+$bkzhm| zo#9T^Sa-`2Fw#+#qPMCi;Dr#=LL!h%mgIWL6_8nx#>#zlwY8q{Cpad*I(YHyyFjYE zXz{bwph*J~kwla5+)H2pr4xYCXB^=vmL6)BzJK!ls=a`w`v{M65HWP0AfhTAQN%Hw zjgg7!P)C#=ahXBy!xkS$V5I+2Xop5ZkQ7R2B*~JtG$;Sbr*jbQrS<%pe0EWRwdL&^ z+JHRCHqy%4HK~eYt>MZ8$4G@h+ICbnkXT93q}po_-ID#IF2o2x>N|hFYeFh^xD~5@ zb%(LFM!gQ2vG`y|c2c!7UUF8*NGODVHFB_`+#T6qty+4h4Oq_LE+h-@9dpPce!P>z zn+T+G`}n%K1l?pyVkMm|B@-FNk_8i}$=Hd)IJ6KZMT#M%B2Ce$<5;NkVdUrt!#Y#0 z3*u;d5pd+>NL*}~Cyzvm#N!8`(OxO7y1yRS~V2>0UCa7X}P6?^L1t=PAV~l{UR>F%@B&yqy zYa;S!8^}2+OQJ^lTF z5f>V`!w!XnMrv)2s}};K$rReP7$dQgIJ^smT!P?vUeT}$=o+lE=H|G0&{H_hArP}B z&nz!nSiZ&d@liQG?neh__gU3&eW#K_$z_SfluEi*;@`~x)f`k4}uVu&GP zicm%dBZ}%Q9ZA)j%5}xdk!2*J>2o%cC@GavS<{750}1+6y&&Bsj>t(0 zq}!H}mIFYdj)rcqQ9Y$cy}jOTSR|=$P;t~hs52xjq-G@~ypA;< zs+1%V9!GhhuxCZX;mE;CIUvht%yC|Wg^+y?y?k%r!iiljR{|A}5_d*U;ziJ9bdT|V znq~~+h0MuWEhQTzttDm&U;>&5C&kkE%50*-W!WcHEn07(cEkd(@%Ah_EqFePXupA81-A{OpU`CA+53TH|W93$Pk z_vgPVkldg6?jugiw>#oQPbKaHjL1hlT1as$H9)Z;HX2v21QZgaNHJWk6564Zpl3Ba z&PjC7((pO%vam8K7NuEu7x=FIJ4$gnip-AO zv*%;?z+Xe9ixStv&l;8pCZy@q`1tq*ScwGxcPA3~H6%oU5HN%gX^K!rCa5Ef;PMXA zksH<(F-Mk>fP$qRJ=}~IyE+STr;F0B8pf>7DjedS6$=m!WJ_}V5FZtAU#Hqkfd24 zNdCNpwtuN~)Aq2_(mzDnBbbQxidgR$Y;@pVV zpcc}kWJsf<@yYX@ilYJit=cx)M#b-#>>^18T;t?01kXW0*YK`OYHCc{FB4|~6R4W1 z4=D?kmOx_h;r8tS5|UKvkmO3qN|gvDi0}+c3=`0VIGt=qqS}wQBRcKO4*WTa;Xq|Xtg`5P}VNmB0$BuJAKNr`|nNQu-mft1wa+^2DV z(8hBW+Re-?`pVz3W(Xu8>A4umEi}~E*XyQ+?yg?XVOTf~+tWc5b@X=(I+X?`q-MMn z9PS0@z)nSWZ7~5!$_*_RHTo05>M&BI&ze^>{6DLpuju36rkb)n`*)UX{|J|&2suI{ zm3-im#8R^2I~OI<60oFULYjytWD4F+!XvL^%n(DQDZ zoxlXilo@)=kQ0T`$^QOc#nCa=5pqO<-NfX`NM|R21RRY*t)UGAJ0VG=Bx*vcFy3py zq`HxY-c`6KWO;@|MFW2j&?@98dV}>z5Ki_K6=tZZy6od^J3sgpyz1b!_aW&BIbtIb zNIR`TtXy-buB~%X;%m`~B?A-Cgg6OM&Z2JsLW&`TD9%4El2I6E!W{HtwA-U2f4Qz$ zII?r3d}awjdi>acq>O|lrGccm{ID;wOA$zNecZ}WI<>gCaP{kJ7}+@>VXwm_A&Ek} zwY4>3>E5yn(yxDGIvVO#derOQHPP3H7@cGznSvdF6bFhFNASfY1$JyCDK_9Mgq;K* zgr-(HNE;!FDxCweJgT9xLOxW;Qx$p+ihiQf$6f$b<#n}(KH0W|5F&mQK?sp1>3~sE zsXfxzBPD|pYKcQUEhYsM(1bV{q%3|2p|b-F5&fWqA}V;>7(rC$)se$darD=@M!I?3 zW2DFPk1}FeA>I7ZVo^lnof)GhgAs2YaaiZ)sE9x+`QqkH3#277()@QfZV*aya|)z%R-`oh zG-0|NHu5AKF5KD0hSE>Fb%NC0)#1c- zP_I)MwWE!CF+*aK&_If!LB-Jt0O>qMb{OyDK2i*WyH+WIs};j{<|!IFd{9or%E|Id z`kY3c;23W+yFtF14Kj7ryZ7ubbu7dH`4@sj!JYTM3L_=95|tf9OBCWE zOJpXZ>3{5y9$4&(?5K|Am z_UK`HnYyq+NU=!PDLu#%L7GtTWKh^4%fbrvn85T1CnM=lk*h|Ht;+*>@(R zldP9ze)&Dm^FHtM5>K=iMoIr2dI2Zna{sBEVo{aHdg ztTwntGPh2Ek1##*I6{oF*&@Vs#k`9V1$NwTH%*brg%el(CNXNBs_9GHC32GA#qcDZ-@iISS_gmBK3jFl+D!@A*dIkseA8b2>MO**-ulOxJTz_@{kFNm@f z`52w4hf$+)ghtwDkhY}Q@Dhyl`mq>k13+4@I94iE?B59^;0Rw$wE+v_U?k$m1L=tx z$>vCZWstrlDH)Kk(7=bw=dR8RNArBHoNScMf{*64V8=p2iWc%yg#{AQG?%`wFCj*( zq>Iubqm1LF6ETr=N`|#%r?IF5TL68WgPeUNd zoUVJuNPpQ?jNUxo{!BpXG1psCdPITU{AICGE?1=VD4(5Yj5tGLsF0!p26mhvk&+g< zXvgJ-(NRgLN2TpW&&2XjtxVd$l+l4(Jp3e;iSHiuzSMGxr@vsN&QU@Nirwg zj2!Y!-057hNPWaxjvpPFfi}9VHr+qeE@CC`a02lC~Hmsy2v` zm?Vtus*aY*_+XTt(6dz4y4{_MRKVf!Bm&arAHH#l4YdeJ($N54*Dec1OcG&~1CAiB zLnY1g77|i)g~PlB_R+MaNY}|oOA^~rl5}a54kcZfxwWnGvgl}hW{ z+;C8(J=sU_a6$TlLE=UsF;eY~4eod!36D-tXm|Cp_-I}+VvYc#qINXoX_^~uDS?s} z3i&A#(liQbi8;DWDU(;!ZDNRWN(IlTU1}W{nNy!UN89fq0 z$lGKMVPqkSCO{!({N-In=7XdpIRT!_(oy#sG-v2?$}3IkB{l{kDKEGbiHtmqz7Ol1 zs@?v9ZzNC>g7g@bw4qJ{j{NYh?8J9vRFWP@->X%ny8%Eth=RnK()K?IB%+8wq8zCS zhe6`=s)lvsq#{y89A!1CBZ|0nIgcFW5hL6}!rC{cNXr!2ansJrmQJ4}W~db^u@l7djX5m6jQ$%u&j8_i>&@t1Rp`w6jA>3gt;s2{$BBXjd*( zR7t~0O*tjEM*Ekpf6Q&Sq1JT1+Fb%V zp~z%Lwo<31I*U=G>qtVo{eQ8JjFB+3mXSAf4$-ji%Yd<$94Edn015uoeoQih@qsHY(t9BgP zp^?5;BT1IDx`AtbJ!B;l-&v3K z0*q$p3^WQUn=49BgE^O0BqO1Xrlj;;_zi6$Eo-e|k?&35ljXc^vNFzIB9_Kv%rZSb)P5(E+5iP{c)B$5f?CrVKe2T_XwRHtfZj6|W`*YDqPlEhA0#VqMT zEJ@+2L&;7z#<5-40Z~2J3xTw)N_zCiw_v2*H}XJ`PX34zq^mZpLyW*j#oSC$D&JQ~ zMtm~5$Z;J7dK%afMwBG+8H!7zI1X}B`d=o=N-Ha@CD}w7ALH?lw9le>lPMI_rALQH zy1M)6W`#sDpl1?D3WT2|V}sW_2FZGL1>ZzgLl&M0CUhTJP;%se)PP9!+~OPwm4vY!f`kQdG!l}uDoS!u z3f1q_N~KcOne6cD&OxpArEL}WL%Yq*M`|S5OWJ<@yHChRznCw=gL5OKXr?%mGd_Z6 zF30IoE~^DQ-b5;Jf<(23W$J8Lym^zm$W8-Cr+Gw!%nlNm#&kzgM?{WM-NB4pO;J6e zzXvb~S`$P>01tR3gZ2b|ih_W@P*VUHg5)xQp+GYBne2QBVy_GEliNE{hTO58}Wn=;YKnfewW23 zQpByR3mDpQw<1mw^EH#F4M_07n~~rFLR!%gsz}rX1$%(dk+Xy6 zfFVq(;gi$W+QxN)ptY?B)zq3uv<3+X>wupm_G!1Gr-Edpz^y6@#d4M**DSII=nKeT z)zEwMgJJrHK14=pBxb3vudjEo53o214}|H52B*j@hF4}O9Hwx-7ZC6h_QGa z;M7I--#I9WnCL6!9t;DKm`Ev#!AOM^%aJKw)?umt0P)R7HH3I1^fk!bg*=wKx1P+G)qbH+!qhm_|WX^NT}3jbm6{9c<* zvoJpWZPOMZY$Hh+5PD_Y?WUP-oy3%FQ|wZ#W2FjJ(-y3_xc(b+v2M zE?xc2B1WtdMncjVmZaI!e5JX%JKW}?(}PSs{Fi*UL@XJN$4wfKWS?P*3=&TLyqY?( zm}pJm+yH-co0T;n>NFq#4dAG#o_82@IZ?pO1_m-q|6Z6qL^7V_3fY z)F)Pnneu?5WgnsE2y9-Yb1=f|D$>?LT0?5n(K*h$@!)s-%h>iQPLM;HZyDl7DibP**pL!FjnBK_MW0Z;K5!M|z{)u1qM^YCA@f zs-C}VwxtA7y0`K8dfgyNI6H0#rw344|L?cP+95~Ut^B|k&3Pn&s=2v22$Inwt$s5` z$dOo#cNCeD4aL51(P4Y+5rNExBZ7|RcCk*kT4fAfz!8tILzGNLm0hs)k zMm=IN!ap&1IAWkXuvz3bjCvaOURz;7Vns3>J=S7_k)(3DwgXMtv3XK8T$Mb7cmMvz zy?3{~OzEGGaYl(1Y5fo4@BU;#dIfj)@@3`nOdzJ2syUXU<+){t(iGb|2Fc8{BaRjw zNo?&nN3!k?AT6CfbN2QZg~?ndpGw72L=k-uOn4+2C|Qq5dGF?RMhX=-+cyzXrZ~S> z3mpAzgVKsX-}Ez8v;-rjL4k3TdPSRVvRWkyGL-z${2WSFQ=N(mJB}QOj!A-SL7{=7 z_7+9&p-2WL?j3-H)B3*KGxI~j=I^>GrTnFmiX z4qkuqh3b?4?j2{MG>7VEpok6jKYYIma&iccG$!Edh@j-!e5{@83o=wkK!ht6VFSBvn0@LW&MyC~e%^c=z`4 zwQJXIJ>KJaL#0ybZV4!@uMZ1rxo9nREdjs*poeb zdu+TgkxQrPkmEcDw#0LFbcmHkLq>}%6j7W;{8~k z(*G8bLZ3AE^lRzQwH;7KlyD4v3_W#W-VFHHw%qWqg5Bg!Qy*wNj~nvR)u=#ycJ`DI6J;KvTFP1;j`>TAs06 zNL&oZ*$Hn{u_sBt#b{L&!HJ<$qvM6i^yFlEgg%8MB8hZ~lqs80eF9P>P_cYUh~m|W zHBJJhi(iz9Rw%DiQ!X&wIw!rVIsVKqDY%k!K!w7gkYG5xFJt}>g1pNwhH407gU$=? zdaFCp(J&>iamI@Id2c;Ib@lBQEeC@1Gyu}&@Aeg>HIk&~m2z1$spceU)2LEN%^}e0 zaQ|!l5pnde#E;U$yQOu>l?bG7U+nEVluV8^=LH05MidD!S&GhG#pIL4#j8${IGCZK zQ)6Sp6BHwpBOH_bp)?6l8Tw2!Oe9bgL{t_UmCc*Anlf+mC`KglZl93+iR*g0DJrc6 zQM0WYjoL3*##}`Kd05iwNG!<4A>jmv<;G#t+aBh%js*`HHXlA27VdA?Z4tFVc@L1D zGDzFj+9^n4?w+%^tJPGMsxDP(+7TW=$u_hyL-;6_M3D?gyNc4w-L=b-A^EWkV`n7^ zWxAp_Vs%kw+6hT3D@(IOL!;vq!{ful!-c|dVPc|CAf6Z{W(hE*^*KjOrNI;l6e|=P zKNcy@pi+XAE@R1^M4qHD5z`+4u|%-RAPOs|<+3^r0vdS>N#2hDH;}(ZY~J&K>#<|U z8cyxD9~wO&eF`l^K3I{!((f%uEJ=h?c?+UcN77-3D^`QYXG?1BfD?<--Mb9ZQ&FV1 zOi~HfPE;tMNs=Q$jEX#8Ram;JBUW{K{>swA?9kZg=orN~KY$a0P#T#)j7&^ULYtVX zkr4)Ja*|*oZ8A`iJP}h_4sv|itS)V2_EMLSmxLxJn({{>Tiz~DvfjrjH%v}8z>ejn z4x68372DqHhEWos5p3CR!SPYn?tlAl9*H86B(1^Q?Uaou)oRw=ZEkKJrb^!N*;Tl^ zhk_C`sr1Y#(wfA(Pf`|};hRK1<$;!T5|niO26=i4;CrOLviJ>bC3k&Y9S&30mrZLec!U;|f z0#zWVSCA)03g3?iR7R>yX+n}IE=*)lIzQM%%oPwEyUlCP~avg(V45CrZ95HHebmGURE}swk4w?*I~cJA=|%qatYo zxK0pKlo+B57ta$z5(~2u_MS{-Nn?CWf@l)AXQ47tkeDEyf?_FEQkno%&?h|Sa{7W# zR4a(B3~?0`)8|S8#-+&-QuluE&XBg^NP;9e(jN&Vc)M>1qt__h5lUNSn<$w|coR+X z&d>T2SyhutC0lk_6_C`}?d_HVAenvHX>9<E z%4yV?$)QXbqza1E1Y1Cb38qgg6gg9(NSjJJ2qIIL;Jq6Gr(>XNz#Y}`OJMEoEJE$A z{QZ*v2`G`Z+b5JBys?VJVMK{h+VND$WlEbN?RSkcSzXbgwEFCYQ6%5m-B66&fh3?b zRlM{S4*VU((%GUX~1X($nL z91ma6NV)cx&k$xg1H$-`m@2?X=qP z03@*@af8EFIXqK3h@|_$k*soWMQK&);0h9B1eD%cMVbkmM=BPJm;jC|FO{QLh-9sBs}-$YZc=Jqc<|imX0+imPksY4R@}#>DdU4P8@pczyb6V zIOqqGxLaDbIH5*5sjCMCZ2BZ+YVT>;yQ6FEwzsj5w7>s%N|KnPZ(ctKNk)`veyUV! zPEwt}^DE&_k^W&6Nj_PTZU{y`$fR!!RxEwF&mma%_HmSDZn4cacAL#6s!|1kKt(QsaJs}MB zC`xXC!Dqr#Yxx~HMS>bZk=WV^M?gt1+NzW*$ddw<@J3Z?Mv|vW1||aO#WPhT_I6Mt z%g{`*Xr>A2y)Ng>Ma+zmwRbLEnl~`1(=!@1E2hul1SuLHXWzz-4d|p&F>~4{8g*w; z`vjyu)A`eniCB8<4T{u(p0-Xo(#O+<24vz;m(e3`C}=+hcPHY+n)I2uu%fm6j%cxg zBndgvK6|?duTgMVs~AnH$kuE_`K}tesbr4NdI!;6(P7mp(w?VCni>@~bfC~VpCo4K zLQs^z)4bEBc}<+Ih%5;@V<;lPuc4%EdKcWLkZ!HI2Jiq(aeJhqUxdjXAr*HgXJxxF zH3;Y6PIMzvLeYU0$=th4KylTs9kSF~en(4@UZ^6mxBEYP*Y6rZRFI^@rk?rfiwGh|4K7-`Kh(xaG>0*1gx*RTKHUUnfUX^9v5CB@V4KuFLW z2s+&CkoeXN3by)y*tx)NAnOI4khv=R>v%U#J#`*SoOpVLLxritqaX()q*O2P!g(37 zDCHV#qNSuZXv;hIAF1pJPf76wJezYQVx)a$B+~7`NS2dC2+t|+aIH zw3A0bx*k8DxT=&Cuq1p0?xBJW%7N>3W>!?ZW-Cy$IqP-n#W~DaF$&_zkYI@%;0vim zim=u#_Ecpi4rxpUR-LE)^9zVZL90}i&4daXrCKSL!eQ4b5r&dNygPe65(tSH=>dS$ zI_`8jFWYS(iAagIlc-m6SlTtDls8oRMCATjE|C7AK>9-pNdhI5bpVfp&Nrc6!yt$~ zbt^hr70<$~3|85y(3Dk*I{OtcRTUqQu2aymA1pm);ZL`GWD9*HRCtobjXVe5E+<|h zW~w4&9+6ySG)gtZuO#tI*ble;KF4 zZdk5}WrrQ@lzKVx-P)%Z3FrZKq+$bhCcS5Y$EumR>WX(M$Ph?ItIOW`qyC?{MR^WS zi(2Wi{bxGrnuL_J#2eLpS)ge+L{}kaXaIpIB;XBJ2P8jICQDv+sE7_uIO3U7@g+Pf zjO2g>Hloi>0O`LTNVxC6kVl+bTIm`e+&HY5Uum`N?ZvhZmp}d4C8;+!LWMYGj=W#+^!5 zZ$nMG=Lde`pT)&s_nza0Bwg_H3ly9xGRjLrj3(YBzcQ@R4d+e#-2iD0L`@uQT*Czm zFjJ)@Y2hVk4eT5$d_!l6k?sm3xgKeMA3!2XdP!{(Rd>J=Luox!cV_7?43#!LknZGM zukfwXPF&hKM*3r|GH`Kpy1vqwx)$a@-vm?YwFpa7;`*`76#3^2Jt>~Jb6RF%^7Wor zz2_DKF)i3_Sth565XBQFwJ7jHq9TBFK?X}z)*WgXEtPAi?dY@;=1L`AwWF~twM&KS zaCUm6yPlC8lDge4l)FwF?^O9C$1D(z$@CAs@Ojm;c0kud~SAWe#%=D3@=Suf47=Z<5F)a zY)k|hVtJXB2*#3E%B``R{0=OR(~l$#5tNYN5P}v*q{bt|gHQPtD;|V!J^fuqVaJi# zbl4A_k^|Eawo*&Ju;bZMQZ!Acw@2c_4vchr{{|ST<%&C3-L>UOeHTVbp}hErNNv{#X$*U2RO!e z)OjJqA*kXU!^phkuYgISoi`5+z&?ZuuMFo<1ykBmt;R?GYH&F~6=&^EuQo863Lu?Q zMv~_k={7Y$1i1n6I3$4v_sO_J2bU@|Hm<&@}TNuh)! zaK2r^#vwht%3BtdQcbTnW4+>Bq9{67Fw&`Hq`f^B8}6ongyN1UiAd?Cc1jLPJXHF@ zXb!omLry>nRd_wI;4vc|{X0g==clINDZ@-Lp5_~AJVj_SScs>_Uxq)yU%G z@Y78Fl*@o3WP5E(o*QgXzS6ncy& z&rHjt=W)J}(h{>0VocQfV9c!}Dm+{i5zkMRM8jiF3bEQeRH~@`4($?_|JW8@d@E0_ zYNwUmPq&``a{mEOl8(LL&{ixFCt<3zl|@o+{+(vBDwuZADfMv0S$d=+Q`-H9e|CeX z`5;0_sh7@fFzb7GjWjoDWS420Ldzsr^1L(=sd%H>FCo=f?%j>?@k}v?LkzDD*2-np z9V)fKN=fTG-k}{b$qofCokm77Kq5wZaN_~hc83m0t!_t=)E4=UNhw0oMwXS*4!o@9 z(0lxfdn6GYJ_ATOcya_45M|3f(e)aQe0JjPazAd$U5#?evl=(B;8i>FCDzfB3{w$@u}EJc!@ zf=T8~*6cbLbi5FWIuQi%5ncimM=PD79Uk-7jIsL1cQFUcHf|f%@S#NDB>S_xu2k%! zpDrNX10#8h(0zv_SK9$ej+1~SD>)eZ-Nr`NGqiq^=Fkhx5(y5y*RP~UVkmuDwfo$9 zFOw={l)bpt^Ao3K;~<|kLB}EHZh_NMF{t&&D?%K&-uvSc&kK0GVgYBkhS$4psRY*$ zO_!LLG{Dg_(mAW;6<^u@R_y{K{q#Kn>Bc^ct6dm{9FAIHoYW5W9m=~cafEMVDQWi; zPS%}mWu&Jp+<}q)hhLO{rzPg67*3v=<^z}-mY95o5YbXye6FEg0yD+KrOPH)U+z@! zH81+mX-xOQy>oK~f+{=*CE=w~QmIf~$Kk=}0*WLGuaF`7x<}eOc=XoxNWw_eB?%*e zlJHc(3)P*t9gO6VC*WmJQxArSxN9p4ns^7_hV>s~0%gPCOH4^VCY#ZVwTEsk* zY+ZM*m@xG_Bi%bN&W4*{q#N8MF(rYN+#{<)fRc(1D&SdDc!)^Z%*HxwGnVeWe)a0{ znkA8sCNSET5(vbbV5depMVH)kMIPVeODyMe-6}!At$j zNLe6x-L3^r>UIyl`|#w+hg+{(UC&BhbJ*I*43&2C?pN5YznGDZdPkk#3`h~4E?=!L zlVEPC$!<8=rKV*ArBLHpEQNSHX_YSFCZ<_Q7YE)2XyJpLI5Y^=L2_A1dZ1>h=n=WTBoP&c|YA*lF<^|!%^amX+CS2vhd1$LtFxf&?8Y= z%21-}LlP7`t?QDa!s|aqdSObty9A{BU?dsJw)p4Ps=g<;-sRMHnQWApmNKXQw(mTS z8R<>u{{*Bgo|eMkHVAnt<%#uy?~^s_bu*_$-mxNS)L$7_;epbQS;9kL_&}NTJF(z# zFe^piRd|(F62q~tL`5Ad4?~6W(9ewY=xxkMuDAmu36dO+4j(>Q4e!JAXWf+Q;4MQb zFlD81C8f=`eZok&c$!|Eompzk+nW{fh)ILK{3_ra#}HvqYg#3`%1rg$rKr4vnYB7b ziIOy;tq%H-6dKV|Z8zi(SzrNB5+w@-yuW*-G$X;cs(ORNQTOoXYV_WtRmz{wuE*oq z_1(->C5FI)X}JX-1Cz$#SjlmS{{15pR4Nr!R3!R+j&G`%fb! z#W)1n0U90~!*Lv@!oDH!k>*McamE9bJRzn6QT@kAFAma-#I+qLiH$;keIEm9^~YNe zU456bIs{gV&3Eg0s>5bRZ1_{9@BZ&Fo<^tVn#(IDSw*AsL=kU>vwq^l(_B36l_*-0 zp(IN9@Og3IAp;KvJf!bqVM)^+FjJ~9EitbArLrU`PFeJMk0d}MwPD};gt~`&A6C=v z$@5mL)6Rj?R@Q)*cUDPCI|n63)8?~21X7Hr@rC;A%1r2(SQ2lB@sdtyy&kUXe~JwV z4D+&*t}F4%9|}A|61iDRIAtN*e>zrz_FzQu;`I+Bz3@HK_vn!bNYo^eyMcGk^MT~b zJBy@DeV5&>*o=^*j07;f>AmUnFC*pQ>C(j2MMqG6zGUq~yqbR1J6dIrR&!D=@;w6or|wCdx8nr_zv& zE!revvPAYJhHOP5>NlV7-@o_Y``+{3d+vG8xyyUc!~HP_XuU_Jxuw!5anJC-YJ>bP zir6k_BR{~j*i*yk)e3OVt!AJojvkVYhYd56n<&pjnRF4XOwg1 zxi^#H!k$_1#wBP~Pa5=c9<)ybJVbSmkWDYE-ZawT|EFo;a2@*BBS8bWOQ1Y?N&N#o ze?FmNH00Jng^0@XAivK=NCjtKm*@K3olg?82Dj>A)CjYh544JG&FuM$vaVgdn?8Qp zw+LTlC6-=JfAGwN7l~TL@TYMwN%OeKf1A~tz6qu)-Z=6|bcQ0lIrR`ZI4x#z*@P|M zSpc|Kk?yaj>UpsJ4LNo-_GfrC)5-nr--Sdfok5%q26-VFn_hoZ?)Y$PC`CBtNoaB z^$_pR}WYI##mhSyfw#n-ZZmMsE&+Y*;*Z#A@3us zU2hGa3c)6*?27Fx+5zHUoielG!`|b@K@g7*<;K;+SGaxtejvNcWRYs06&%o@Xa?JP zXzD(BHO{mCIN0UjytOUPu`;aK?ySyQXY~F1vWWi2$S=i7niBWLy(IcapZ{K#^ikuS7h}Cy7jFn1R(vS%XBh}ZH1O=iR(;LGlS04#tqQZRCJzKtcArzyi$VM7!8-uAE@aUmw!-JIDaJeo3&-2Wk*x?4hPU)hm)G5 z!CAo-FD(8oz$uSHXLi{|uFZk9Le=)A79($)uTaXem+H_>Tc1=mSjdBoI)O)->$Gnp zZ-4qv5BgB`p7!n^@t^%9#rXYf%71d4v^4n46<(knCjagVfB(#GsC1?YJ@BX?`35J0 zM~K&6*MQSPw@kStK2R@To5>n!m|7=f@ewCK|HRE^H2P;9)1(jjimjP8*FSS)JLq=<3LXBCJ6|Ju&c}wtRVWUX>yJ!dzD0hxE@t9M-7tx=P#2(1rC?@X7%`?fgPb{6)0sy7yfjiNmn zh?CK3E!|tNw|X?<~O-lMhiJX<~W~LAb{qt5NcI^S{gD-Q~MetqLOFz z;7CsNLb;|ZqM5K{98*ls*zgG=+y%I*=y`o9It0;qo=eo*7@vfM$T-zWM5=-b)*nHa zoIVE@RMcV$ZQk?7H9UJ(Vk4YSfZM-|x?t6Pr?7sj_T**YlXRew`?r3&iLNMUDX9nsOUWnNhC7daubCs?m*9+mZ4%IP?H zt~H^l(9O@L3C%%&pRld-sP#x|O4U$EV3ibGU=U>@QfoxHE}98#>&zhqCe6eM?aI&isF+FTzrr0~1(b$^}8 z#cI1MsjX>e{cNBP@4t6fFtEm6kR_NCLze^%b)&h+CqJN@1uzjh{)Z3m_l^vBhXiBq zTG5COD_zwR58G-+|Gaa5W1=85kv1>&|c1vlkYWY1FJT zH0=aORWKL+F7pcE)v7ISfnE)#g(ebYs`kKeh##0lAxl2-+e`@p14X43nx*<e89{vv9M+M4(hDN>hI=r2umw|H-nx7PMIa-uOU3HM zs_&vsol5_lRB-CXubKV^v>t<*nEiQ+!~|}Eqx)^Cqly?gMbh$Yukk|=_od>zTlfG4 zc9*ojXuRLR&RaRJzJF+RKZl&$gM4I#buWNRn{A)u#H_+uFHmG!B6*zv9D) zYWBRy`^R^{^>{>w@IY~^btP7^js*l&&5v@+YRTeEgN~CajHJM74M>uoBB4=HNT57oXWa6?DM*$@K+@L^%DVZbP)WWe=GvUIYWCAM3U=Ls6WY_LmE2J28y(c(7PY0qn{`?6D6+6FUVO&a3mYIm9z3afEa zd2m2L>)pqBZCIRifc?VApCW8FOY{^uT_7ja^349)o$cYZ!nia4pS*gmPg|d7+#+Z` zaDJ{kfh$#VuVz>f{>$T%g|lH=XhaqZqd#Gx-%Xx0*61KrB%k3z;&^-|JXBA7ib)PD zy&N%bSf@=GG@Y}rH*Fd)4SFZ_hEst4NV*3>(p%-Ezcaaf5c_*J`K^HRJ@S>G!V(c! z>znDAs7I=MZ3!Z|H|xOD{&!6ZplTAbw;}OEXGsgj*Zqfu7@#5@%WKkwqdBvYFEY|j z?;+Zkxj7}qb4180!!fe#m~57+YoHqky>gXV;f)aEy~okNn! zn=T}8o~r5y%hEW-gBncWgb;o06lB2pDIP;(LX0hO!ZnCsXsb-9rj-4=`4m;UIJ_Su zLwp}<{psUM02IW{%s8QO#w1f#M)7HBl4$u4Sp|~rd(1}ve|;l^6dz6q9;G}LVpowJ zL)SN|n{P>Ct?Jf7Wd&E9805Xf1zf&sXqPPlG^cT*uBR>Z>muk?aoHTHAm7|e^Azv+ zB248&vS9ZJH@k9sJ{F3A6aFee&9i?|;X5o8kBx6%n2JXyZHIZK2vk&4^{7|%G>S$) zm2V}Qrz8k5;z>Z2U_fy?NorW6B=IgOUIU)JE)K5O>Vm2C4C5}%5`M&be|Z+?uhgDr z%{lMWR7}jS6$|*6Q_C7lM5`b+Feuo*PW+!9lIX!8)ZMA3U13cS8|<&lr_7ZKl0u>R z`XZAndE;K54aU{>1MP)B9;vJyD;MJ?=>?KO?M6Mao~sSr)haLYS;)q%cS*^o$EpNr zSqH_CK*H0e&mxDeDkrIMd)z2a>g98lG!F3`J0eJOGbM3h)dOtSCO0s}C^i&daGl@C zTVMK96ZK(OpBO!Dp*(WEwgoP*Cc${oZN?y(XSrd<-`F@UjnSsNTfY4LhEku4koqdf z8Lni}^*NHM*=j|46f%94r9JK%5Q6K$T=JmEg~-AIGc_51rjFIxaim?UbfrX=0k)@}DV2TZp>Nhj3yN z$=UFpA4MKwb1DdJRpAi5YfcSEsJ?ZC(Vp)swlQRt;vw982z~@btft6tLLyP;P&fg| z+L2-J7`$SV-Z7bhTFaXfPo1j5nX7XUSu)yl&8C_F$DuM^h!bJ{MbOl@0+LE71+0ZS zNn?gk%z}X}?~&V$7X9vgb8L9^$0tI`0%Z@eSfNu({=-%qlFIC|H4v9LrvhS1c z$E@CM<9y?3MBRGny-@t$pLc>_;>fvxcUSMpaLypdi323ZaJkP#N;Fl6KLyVsfdDZM z@Y|+*u^jc=)bvraEI3MsE?|qA0goPm)C^`&@t=s6u|cQ>4Qy^TGBNbCNnNsHWQ*P0 z*gatybBm@{)kTEWBF1HZUxYKWdjAUYa$_ek<{>g%l7ZI7bc9p@HgqKi&M2btSxK_^ zc<9gS?sXHRcUL_`)kO}o8G>H>Ofog6431o8MkCR6(0~D|f0brhTI-zLd{Gm*SKb*v zfB+N8+x?S1Z!TmM(UyT(1VJK%%tC&`Y~XALV9ZzCz`rg8#-P#P^EG-+|Cj>n4Z2di zoQB}&^vR{43aG2Lm<*r{rU$9v%%CF7tNA%B6CweZ6L6SzarWz9fw_yE#S014qwa)n zZzdOon^KNZ+Lq8pC~A@sKXahw{ee*O*$9e!niKW&nC%_fSZ%SY`ITM@H>N$0Rvl`r z=WJ#6I~Vw-XEagt5BJ1`r1VVYSwSrxLO#nA$%-QHX1z@LJ$6zo&ZOI&n7xH~OyNVxs z=(eg2rk!6O-n=syT$05+ocr6&RP}ez0Jb(jItIwEXCtsyXu+(ZVVa=Z3cGN5;nP*T zuk#Oah8|ORlMI&7pU5JPc*MM98zRO197373c(HG7M~nt$U--dNZ5ML$ZNZh7D5qN5~B)E9bp zS!yKzBchA)17fIo3liy;&V%Bh0MF{Pl|I*bTy7(LD&wI>z7fBKu(Qlrx>v;2L-N({ zi3hqSFTbjT)d*xnNKUK<$eD#r(XXYmN&i_sk+H zj0|9}DIXkd94z%AAD)_drMq9D80s#>Pn%1o!-xz6wmZ$uuV;zp}ge8zB&u3aX$;;qDi$tq+G2E zwsE{uk^`u!(=&bFO^+Ee2#cpCJ8HhsR)e9h?gNeuOf=SOogU{j0gfOPoC^R4?IV@S z47zuIb7NAhxZjg@+g$%sW4=3f1!9EYW2d=NhrMuxBPwh-Bm~$5Xv!LD<&IcbL~Mm5 z@{E+b-dvp51uc1bs?kSNm@N1aY=g5Ir40}hch($*@#SI5yGO0jw(VAPK?2}| z9(?#{jyyO{jQaC%*pc-2@VE)vU6wNWBU(`4qIPo@DHBq!duhh`_ph#-F(d6E)*FXR z7pDp4uVFGGYftl=C>)3X-caD{tpczb_DiTkK9sLxX%>d`J!!HYkPIuY=im(q7Hd89 zsf6+7IiDGS=3+C9NKA%6d2wWHAo1^^-svPFTv|6W~H0yoabtctYC zLxJ0yTKUDYr?TI3)!5>Lg&zH0dTv#7jS)ra!+#)V%f|W>T>3@=qomc39lZDa%`lk6 zkStHcd_M0qduZmQ?b;cB39%aqqspL280IKlYulqTc3Pxc289kzq`cu%N+pp+xqr&1 zg;y^;(bX5*)F=2hT_k@8Ys>k+az()X&~{ptp-Tmr#|48UZD9xSZ70{!=JZ0yxd|BHHR>`U`F<#wJeXFw|| zz59Iz+E&Qc16LUW%N5ENMZgqks?Fv!XPG1DY>V0|&r4~i zSJ~i$7dLk=z*258iUa6=W(qclD+Cwie9}~xE5^I4s4%<9F0m$S%-^~vX!EM@=G7;M zi=sgE{r?;cVLij}v9?gNj%|Uy)7<0-Wa`9YoIay-gm4pQq;(dO<_|b4ZVT?GXdgKI z_-XS3|8;$aQFRL=s#EX#ShSSVUEChQlCLhhmMycDpj5LpyFSbvoxklFh`fuTmLy>) z9SB!CkLrEa& zb@u7M)$A5M5(A|11ZKmWVcnv?-TF}%=$UK?<0_Gn1)9Gl<<{DB!I&H;JxuL*2oq%R zHYAD8Qa0%tAskR-LbpQRvHMXS`l5YxH2Aiv&iJ!&TY9--mb)=WIkeg7cXi}wN!+qK zH+?!aS^RR+;r1%(rt~?Qrpla(7h{Ri2tUH)G!uOj9;-h z<9;bspw~om+DXMFikrF+3uVs=Ba8fdZNQYGa2OK<2Sw|(ZmW^spOsCZpDn}m(FqT- z4mL{p38rh{875MKR>c49p8_FiwDo+ly?PnxraAp#cHvE}ACD)s#HRh znQ?!j5&@HT{;PJ7GWuGH6ZbzS?)^#YA72KH6O#u81IE?B+FV!6((>};(ouT0)U<^r zaNDnAT-Vy8*;udGGyD-F;AEL1bE@aY4JqFK<+wyMB|=}yTWpbAdUbUUSR@P{!$b&k zF2vXWeNZOh)*rxrg);g6d{lD+_eHa)*k5lhz*f#}UTJ9j46HFdke-RI?%#uFcjSXU z6mwBlMg~?wO2{C(QsxbvsC)By=CEbkEpNy16r9uV=kgB}^anEld(wXqb{uQ-!p?V49U8ID-yZu5{#$3SBG}8TjcMhbb8FcU|kQ&J=)uJ^RS!qQKJnngX){T=*>3a;_sZ0X+Y?(3B8hM)pFgmb8GC?o|vO5M5L>U ze~;U-=ii;MlFR!Hu9NmBu$D$h%=H^@G=J`iM4rrJ_iW#2i{7x&R-s%O9r#j*(My}k zhh%WrHZ&X7U}UQ-phwA$c!6}y8CO`8*hP>H$#xqsO&3Kt$7Lp6@PWB=uDx!sAJ;^ zH%t`_Z&YC>Jd|R`5KeCR+dwD~YHQ0qEZ-tg3=8Sx&sy8A#rU9c0dmZO!MxT7`0bJ` zg*V)ciJ794OL2z9oZlA%F^CqGrIH9rpS{CZ6csZMma<4X+#LNmMn#^1)7 z1#&5^m)8ik4cN|rGr_~NC)-kkqKC6k#aHj) z5;>s6N`QyA@SLooQ-js)VjU(_Zv~xBP>_`dLiL@hgLBMt6l{*VOhld&pMGIVXNqn) zvqMX%#pirvS&PxG84|*{H~QQ=rfu_ClY9|Y8z}3}7a|efUXw$OjedXkBk^@J+ z@{K^(`B&qIziu};gKsnF`kyRv4VR9*+KNePcl;qSO4;R3$d}uc_l7Ws zK$c{3$N3m`sx$v1iKmsL4O2O$Buh*B(L*Wii*SI&Ovj6eBM)V6ZpT|R=HgCN%Rljj zBGpf*5{?`D*z(r|2K>$sFLj{cYP+aBD>v61qa*iz-e1u&KC##r6X;s^$2GhLX!7j1 z;^!;ZXXFSZ-qSHD2^z7uZ^F|L( z<(x0$PH?t2QKvzeIEb9QFHV4clC2jTiXCfPwdd3$u1%}ms z9BNxPe2T7uH}V;|m&n8;5HQb6k$3^k_Ju$6U5m)9+gF_qV)bd?PF;z|emivRS+0C4 zgZMcUo-33>#jNy&{3(QXc39=mTUeh^%k&X{?*3>P+T724tR@q`VIHY_DUgZ@5Y$j(Hdmo@lHWUc3~6veAj)rkj0kKr;MZ8BJu9E*e3rggY1*#ZX;s-anW5b zS-ux4!ya9VZ9Za7ltVq}_!k>znW@kjM<6Y&NqV{$wiy}4ZACCN8+lm1puokAfh@g@AOB-XQ^`cj+BM zRS^3>aK^Veg$&aaEt zwESPXM%pg^BY(|c{#>mBz{1(pbyHo}dN>7&doFUm{)cy-x_Vc2nKXCRa|JkmzSK`S zj;jR{8qX>x|CM7euDc+Py;8mcG_L@;D?ok)hywtf20*tAU`Ph!H-M*CfZ`lL77vhL z0SZ5WJ0AeGZ-9scP&W^-rUQy8z@w`x?J7We0x&8Bp11 zkp;XM1Y|}4Nk2fy5kTK>8ivaCybP0SGn&kO;u<^2&P#F!cj+ zdI5^Zr#6Lv>qkJn1CT=lzLkJo17P$Qc(4n^9|AU!fP5CiDG?+`Ffy-KJDEZc#)Ng!bxSlk0le*h+Pz}>_Xfz%79#$}z9<_ob2 znQN2q@X^!KC%R2ad4rsUnCuoEJHvghM@&yd1k~h|g?OQSPi-E{vnor1&D}M$9bX#Q zK%VQpg_%M_JQ2as0dJ%-;V#K~nlq(&3Cq=^jlG)@+nxPoZE-bugN+|E(c^glrt4zy z!1w2s_3^^g#MR*L&tF;UH8GWM6^Mu)5WQ4Zc=`j@MEnbZ`!D|{Uj-qp8318|1-S)DAmty% zTeolB`lEn0Q-AhPZ?$B(WPTu!oN)fxy=?vK^Ydz#TBt0Xth4G@#KFw*PhR4Cq_-P! z+E2(Qj)Pdx>x-}L1jGsSissy0qQo3Ql!GW=BSped2*zz{%+ILMeYBw-`U;$1^{XgM zZ@UyHeEdEkH3p+lW_bCXjdxnc(Cv}J%AW>|gK8J^v8=H9Ee<~^`D3kslGfkHv>jsK z$J*+lqu@eX4Vb$-a()8ZsipnVq*dW>igy*elA20lOu_4t@aKFv| zp>H7SBkSgSW<(4rIcg%L);`NPL$r)V<+7I(!$FNB;cm)QAcNM= zW(?Ska{cSn%-H)-bx@sPMaCY%HuThwJzDXBV}*rHFrcc(p93{B-T z%8rYandSUz-1L5kn(=*CatMO&grF3MY!J_?FM`DVh}LIDSOz4a2_0SuiVI}>j}CL3 zNm2G9cL@@*TD&Cp->=aB{>UhL;-=FU$FvXjFuJuj)>02mTsMK$&BC{%oR7geQsDk$ zf-*R!p8F97{}9_VV=*t;RA-i9j)sI~e!CTT6ko&pU4=%JfaF#DI-OOIYO=SpnHxs+ zd65RdLZo>77tvLDXTF_U4QwFzOu`dyr{|%x>EWs+yX+66@=@E_@jdrx33Pr!Il(1{ z2c4K2$g=05NZ=@oHmhm#_4s4qUMr9Ct1#{^E5=fK)ntVH8CA8-MMgH1JW}YJ+4tr7 z#mR%0vm{3|IvT5*f&`E3ge8#Y0I%*7g#8{%Y@gTnG~8w%n(+2r%d`F3a@UM zoDUa_#jH!l`dWWk)n5-!ZC3^^VYO;MiG9rudof>5+>||~SX!pHa4AYwtpe%Z19EaBQBA^l*A~bH$JyO%`%T@Lok1n9tVDEV`*zZ zhG4uF%3p|JRep?3iU=a*BCi!Q7yx^3Qc2xj^*ZO2wc2R+E@@SchltR0gaJ4MMhSNF zzSr8?Dg$`@vq_N|?vqL2NG|D$Yl|B&Hfh)JN;hv!PFJdpxSkReI`ch7Q9rsxT%N5N zIZJ}zR*5QmyYRMG1~9@GN%Pui-2#%GmT@s}Z;{DB)c*n>#$6j9D!Agh7 zacYMl-!U}{g(4r1AvWElCkY<~k&~PZ=K)r=?8B5zeB?1dTwom%(rZqVfsN%YPTMXd zzu4h$ZJ3>tz%!dCT@E`s=$1)Ol+sTY>9OW3o)9E=K1Xso(R(V60N!IQPAH?Qm#8h?uZm7nnt5QO$3C!O-oTpMoan zUPTnaM;cw{Gc03Bp!#`tRpgLl_1H5gc!K^g1<5>wN#5=`165yJ2J;dMZ7n%kH;H2j zAmH7j`sy4e-g35!c=}}kJNG%~zepYrm+_yx<)16lg#KOJ zcbBBVyjjT0_~bRGsZf-a`zcpT2N^#;{p~K?$(L-0Yr9wacJ}xW4}JG3UgHiy^!K86 z8^RbBa+U%4gLXjY?hh?zhifGUSZn5aOw3J^W(lg!r4Co#v?SNB*rLtIde6M;Qy!-Q zdb#ajM22c;rB2Ui`OxQK@%ldPuNYsWyXz{et)7yv>a8-4O&P5ULQn1Lhf&kCMg)(K zjmC4;f%O5r-aCEkw-S~W--+!Xqhq}F`v$CBBWnZ{r7#}FyvbiQK~>& z^BZl2NdHd>%OFu~DhYDOtHgXeG4Vexd4!_@EovVw%%i%n;n`F*TOnB@=R18>Y;Gj? z5?hf1ZhsXumE?{7Cd}ofk>X5f^4Yt$vJ>-+enJ0xM4q7^@3F|M`XPjx26D$((wN}C zWpuOVYX#GYgGsWl%~t@R9tIoW)Gxnfvd-c^px^jPU{UWvlrt>0^wRlJyWSJaz0#wn zSE{G`)rT}nge1QQ9MxO3NLpFwSL$Qh663GFyemUfFvC_{DQxn3GQHcUM(%b43+TML zS2mHcAZzlary=MHYHq!mp(n6g*nH`M?w3^EpXfI3Dqm8LTMf|tcM~h9_bvJwNdL5i zQ~oCnf=fj!eZo!AqLp;XYjm}Lh;ET#i zVfa76%2_-8k|Ip}6tT}b`;+F;{=jYT*|pc}BqAeMV+8&q$I@>H*DtCYyEA<_g)-Ai zlC^SKG8O`I1(3ndM&ptM-U8f9MNjP9@G-ld?jK$4 z?27g{1aZ3?508ig)MbwnJL&feR}UqqP3!ApF-``M{$e_S?Hpso_T5XIBqE5a`kTPV0FLR`EqYwFc-3G(3&czZ@ zSp;Ny%=#a@(}flN!I%XrZdJO|yd(`5nMG|43oGj>p@fG+_ce&%hkDoud6+ub%PsJ9 zZB@EtdHFtYcy?T%mX(#o_Dt%s=Q$R0xo((py1&-)1!?1&&^x^zf@ED$aYjmy*rz}O zDSnV-)%{1AY!x9krg9hg_s*}xXY%GJy-?XaIl*q*CLn#Jned!$<;Z6dVUQ#^!_?D~ z`)8ZCR%;e(>**!|`yBi3V7;png{lM%RxUTMef2-R_(#KQj`_9@KN9g^wqF!?I35OB zh?r`%-HioXWaptg9cteSAmsYO{8bY0=0*ZiudAIu`*h40JlxgdE)Bz3h?h;5`)GmJ zw%_J?Pqa#I$6&m-6K!03kZ8@=0Ov5%)&Bmi^V1-7pz}zd@peuV5|M*nL7yKbusP4q zFUd~DUN2(9n;^GuXO*!nE2w-WWSzDyZGlS~0&4=nq;*^WOX<`D2`&CO0myRv>nd?y z#)XIw37P$e{mKQipPi&Oa%P9X4xqLhVy|laD@BB6b<2{q)Fx`Z-%EO$6+QM=%pUvs zb1UFtrs9>4AZ`Li{AW;S2aHE;klLufzq{&T8B;M7s&LPIDF5H44zz`W$GRoliEL*)xb};O?yd?6ueLl*x%+|4q8pDZ#-Vr zY`_WbO@J62kmZ6e4F=l8xWG7SJ9L+Vk4I0^n4%vD=@L5R-_9|=pHH?GCk8E{cyeMV zQ8W12te^hpvrJ`0>V zwV2ITCw5Ra?Z?M=VIk-?k9Q0z=H?AcrpwJqKG*h3(e~{sVSg<+w_P4F*9R(vI4O&n zHz}@o8qDED5zcjWYa$n%Q51)?bt19Wk85-*z9#uU&i*M+c0cnMJ8b}-;d3G!#DkY# z;@531o!D-mV>2#U)_g^lrt9zo&K&I{dV1412NRkYZW0+SuH#ILe}BNHAb)N_O}ff6 zr4>T!QS$8K;OE!dtVp~)D{^*|)}L_kMTm#c#>DU*VMVSUeP@3VJOLN~{&1Q~E?wXz z&k%#L!^u4B?SMAIAlQ+I$BMi{TpX~s4z&eApsHA@irw&_h$%saM=o8nAGGRkJ&^oB zXdIfQnxDII{N0_eDH{H_(EMx{x4MCgN^da{qebeZ!~0M*bwzzd&-R?!%H$fBBPb4= zq8~1Ck|X20@2!Y?a_wioP7=v9fv6Aj82-5%GyxSC<~5uRsoL_v*1eIYtJC^SIE_X> z_b&t6S!4~j4>N&Lfi@~p=x>iJ-avGHD4TW~ilLy=cjKT08T!D#=*yo~ID0yz*6tW1 z(kvAYCh$069!+w`5@GIe@@$VV3|Jr4mgylLGqBBDn^1k&&CN7Fs^YSULiGyamC=by z(c^;x6^%7p>=mjYW{dhZyg96ADSH9VwXFTgkfvLsRx=!TBDIqTUv2COO3IqGXA-FI zXqnE0@Oo}D*lFzbF*Z>9UH7%hgF!Bd=l6v(FwKV_e()@H)duA^(1zyo zSP!h*SCVvpvURuu;j$)R9+CfmP)cJLnp)J0g-nYu@GFLAP-H3B(#F+FNQKnbdg?tJ zu~SDTJ+A!(FVRF394c@=-BrYJD5gOF)}=mMtxpZ@u4P-$gQ!j-hZ%LeWQuf+X?3h@ z^+^LeIotB(yRXYTh?&`aAMkGASL(!*>So0KciY8;!j^hC)pJiHH-#iN!r#AmOIxLI z-?%FMl&*K?6Gl%P6vf~4i3IbbBn(Y_tids@sV!D#xO>)pFN-1!D#E(M;Wg3d5@xHT z#yi6BOrwvMnrckxZ+Iz?`ceK4wmHIP(tTz{gsVEr5Bd$bi8%Dx=RaqcrkHe*?CPUB zY0;6*DlX5{p=C$jM%dqSTDp7z3vw9m?wgypf5uIhGCd4dN4~nJvf~3~)|eB{HZwPE ztZ=4)IgG#jBUF(F5ApyB@!~{Q*POe}j7G{|`hFgc)W$uv(F?{M~KE)*JSx+lR~+-!J(w#~udF*ax=+ix2dz^7y|57HgG5=ujOX6Wz}lYZ zl(NxAxfbl!+4~U$1^tZ9nNmRqiF^3ttUtzz<5PrztAv2&bCFFB2-p5WBSFct4_1^q zJI#>V0Fg8mQ>RNDyYBQOd?KH$Ip$37OkJ0pKb;Ox)NrC@-liL10kJ~kBOqw;juOpLeQt95iq3_mBQM!hzvUnjuv z_?Q2xQecULC$Ao|szs2Skb%fo@51((V_?u}4z6C7nO-{3aJu4eO7>@TeXn!nc~evm zU%zxPLdkO@IwVgQkG@!e_A3jfd0x3H*3fFuPj#o+VJ{uR*k6M>31l_2Pn<}ChDXRz zlW!;>)jR>o^nNCrFy@8=i)l2CrBX*3QRi{=+xAr(|Fs<#^X2x}{Df%4b~NHEyBY+U zRg#A?ro0o&z^T|mr^Ya0O`aWAwMnWbAJ&cQmmX`CnQ+vz3S5JqMZSC zB@!|uGVkI4{35MO1jl5;rb+ofZuv?sKfgpS&dw+f!!81vBjI(gs!yS-F-FRF4_D<7M|72!>) z{r8-fPVsv6d(BN3-xcaj_WRzvhs&$if2Q6b=)K|AS3WjyOb6~d$6E~8(FpQ3Y=oi-%*l_G6 zrZ4Y>CehI4_uk8hjyMA`S#Ox(DL}%N&oW=G^;~>qjY(kAs-nYDz#VkvXLaYwMkG)* zRRvbi%qn`O6Io+u&icI@zgXY`OPXc_ul@M&8GMZ!l-d{@p~~f$x{l-0tqIrrIclGL zn2_W9RD#ZTk*N))W}oCC5UEHu;?M?aIz?Zoqjbz>i{$NzINf!Hp$D3Imyb|Q=!dYV zz1~Uuc9TS2GW@s*Y!(TZeb`^wyFK;>wq2uTx^O;T2baSbG(=P=y*b?k>|i2>C>>Gt z4g%vF#v_E#eyMqFeZosfaJKczi@*)RXUaYnxzG1ajEk{Gp$Ll<=ekdbf#HU@?hh`} zcO!XBP|831$RwD@kDT9`~xV(#LUd24HG z)U31$<;6-4e7$XS^8?~Y@QUF&r6Ek_cZeNrXWNN6m{>|I$rJh( zURR7(>V)p-*3%l@R-U|2b9jYLd}+ixB`j=gWz+|L4pXbss;lyNiGcu%G&qetc32NaiJ?!a z5wAF(nuO;>L@4!~Jc)uU;jF8_5G@$Qw#J8)XvZ_Y8husPeNZJo$7qP%8|-?rWw+1g zT4EifJl?)~$|>0?J1;2igA0z5|8^M7HmTxF$q;Y($p3DJWU-hc$#A2Ul}X51cU~`7?XNO@!eIv zDPp|j5(kdOt+%<&`e86%-)@komwvL{rd5~qfIdYglLs$8V%c;|d;>$dGK6v8Zjvk zPm;1pkTDKAL?1l7MzQ1{IOy<>QinVJyI&lu>OY&x#B~!$sWHv-`sN3rC1Ap`%J*80?2} zT1wb|PWenyc>?}mx3j~^j(Mk6j3th2_4=UqLyv~TiX1}C=Tp8LBopvmtVz@@Q7G{N zG4S4_#l3Rfoaex`B>BD{>)J?te(dPD!hHtJizz{~l1Su^b%Jz7Y@RA6`1K z-mrax6npyFAGp^4h~I`(~p`{1^lsPkS%2MH~WuC?MUo20orofq4voMn{c40&QD zb?(OxYp&}q6|WJegy5{ma4omapP!7aH7ERz-#R&L-g=!h15LB#Jkgm6P9TEw7A3|@ z{Ic{N%w12+@w1Mc?o1^iFSy}=fBPaJI2r37OKQ~v`S$m!a~%L`wGoDq*xX3IEZ?{ z5vAMQbx(Az^?V<4%Qi}0CLg5x{;^a8Et5Kz1GV@S%0j%bDLM7MV78*2VdrCRJR#cUp*_kx|~vC|bK3&JFLOZ@jpE9;>1 z15OlOoF6onr{j5{9Zo`>o#i+DLvIc-zO%R3cp;Yr^CfTZ(Ln=R{DcJGj9}Z49Amnz zyFnAx`%Dk1#KXb5@>S=Rl`|wmglFkeYtqf-h1K5kA;>EM zKdg-=#?s#9J*+zj5(x4NpgD)%gAbyA%>Gzhv{PCzt~hTW5)oolg$$L z?9LMF>AtPtQvD}4degzwe`;SJ*GdN|Vzh&O?V>0KY6ZK;HX^a4B`lryug|p6TDUe-Ll?%PL;y-pz(lR#kt=2U5z=pNPYT3+#Q5(tCqM3 z&Ua8`65%cUwsprdBEZcpPhe?+1UdC>I0fxw-v8+fV|y_A-z#`9<8krjo}=wK=ZH)(EPwTWaz7i#ec8B4+HpN73E$TaPZFir z-%E(hh7l(;8)8$JFU7l6oz@%rOl3owJKE9Hv&0NKD$esSrdp29%YABb^$+WeHSK;-r<%!-Z@~{_J(x76#*!*A zvas}T!&MUhuJ;}F0sr?c0Ag$u`+DwMl{_boFXx3Fwz(-vWiIeGn(*PGoFDgM!EZHF zt>(8QO|Tx^t?f&H|H-cOy75{n8;Ldb`(u$5IEN@9mmKmD;o_3#N50j^wAb;}G5r>Q z|7h-DJXaXZaC#8@!RIsXFO?sw&i5Sxbmh(V`W*ezrm>=@)TCNb@E&-vddoA}&Kir) zjs$&zX4Hs>F8Iu~tDLeX{G}D^0(W?X$_kl6zo6gq%nk_WC@3$DRrY&0L@|izvnj3n zuMDfZG8=EYiL#ToPPYt_tX$$e!F#rkoSXJi8Z?IZem#r5g)r7M?Wxk)7#^y%CQsD* z;3v$mei2kgsz4^$n2Ag_Kokw{ezL}1p6kt4>U_&UuK|)n-J|W(i;WU4A?hX=%6Z3Phki^uku~qLZ=`9WoF&q|WSFD7D z_Vn#=Mt~nVV;^PxTc{{bhugiSibg~{(SWoFF{Ke%j?-4L2zI6}GIXb@CpXm;jk}4O zOp-dHe=vI02vU1{i&;5jK;YIi3juK?GSEB$+qKWa2ugKK>)rVMI11gG3+3itn@b?dm$(ffX zb9k3t-|`cBzYNR7N0w}sAerG9S~#d2-P#&K)&(ZRJi>KIM8@pIv*6GIyJ&fn1qD3* zd2!8f-^~}*pZTFi%ZJlq+p2WEEC+qJF9w}fRl<6S;_|VrQ*P}6mKk%BZA(AN<9vVF z`treH4^pp(DK6t}T|;Y%m@RG84|7VSgHM__ z@1MVOE}7oJBhZ&GddAjvBCOn-?-xN9XAZ5+FJ`Dxt+Y;i_}R|lMBLe;{bczZva zs-2{&Q$SwXxLP4`b3IWf!Jv8qe;w_~d;)a#jn%*w+<{{;Z^Jl(&f&dE%!9T3j4$D|Bf@YeU%_x;V62qmM=e=^o=4aQ580 zKT{J@cfw_P>?QD{#E==dyP06tV|%R`M^p&-*Hp~&jsi=mV!Q2J31Xjw8oHCjHihqJ zbgM(Zjun-$I9JYn|4V8`<7CA7`T12+iqBtM15HHf!_C~H>7n<=i79?klq{XeHV~pHut0iy3;ZMy=phnJys?iwmLL`XCO0adtSB_5qLlkyxEnt3+o%%z2`X^czyf zlYR3ZS_Wq#?nc-27q;}OlF8RDfCiTx`}h34s4wXqgk4~JMkmKjq;E5)c}tI^p(}CA zTa4S!q&~EPJ*d--QoYknfeJJJ+=1TtDgN~xjf#NdlW!YCgSS=ZIw@UouSpjrlCN6-#QW z(3nb)a^~8Ss|(dcL?}ES()NSVaz0v*cBr3rc#7ZntLWPUbHNlLHd=#bEw3@#oI@I2 zpA^LihB+6{Pf41oh|KrjoJ?N15YCRv=RY82E<&`o5TuwJZy?#cJP$RHv6C#ZD}Q`s z=H4L!lHed!darfmJVJ3eiWBca7w{3jwsH3)=)#xp1@kT2CsuI$XaS%%msD5Y<;uWE z2sIRx*8QN5OMs}Ewy(OCEK_}mHf>jJX=S~!m*GPMIjB)wKF+6RCJD}W_G`aW^_jvH z``F5mrlxVLFu5V%M4Ce{Bg(e&l2=@OD_%&O(QaJP@P2@FNTH3Vnixncmx1amQcRjA zZoa>}ueRm0^!xR&#bgchI{k~;hu^>(R$Fy$s-1*lL~UeDpB$c_22)Z^TaZ*!{g4uq zb{FAbvf!sa=a>EB&fmADV<2?f8gzze3`$DJQE`CuJg}t);WcO=GYG{cS6B^M-#!AO zCie)mjH;8y}>!301?+W`}Oqh!zOCxirsYGHlc54GoW{Zt4%@QZY_(g)U zpr~H29)k98ZwL6#<)r1E(gHx%0?A{FSY;*Nzhwa8t-b3fd=tz0b+Ugv`juuP%zS{Jm^;r|HQf{2gQjdsLlQ46i^Nmr9Mt+Z#p<%!lB9usR+Ew~U8(;jIh)T71mJ|g%=w$MrKZ`#OO-^|U_5rlu$ znzjx0`j#rSp%!r1_cvcwuXYq3se5NC+%P>9PWOtRX4EN-MCsGi<2y(9SwfrZIjls( z6?{q}nC_alb$m-!;A14=_EV&`dX%g$N!IHPg+Q289{6nZ=7rB4=Ze_onmmUN8 zhB0vA4%<=P|C=2!*3SF{@%8G$TZZ@Kz)E%wWG*9*&o|WyUbJef(h|X&U>C!m zwR-%2_)iR;p97|cDI&6ZP_<;hs#GvtxJH~nN?$9%Q#{ps0~gwPXdl8Ht_Bp*Yn~|BROZ z^)}q^@Y`u`W9fT>Mqq?>wL6w>o>S~OrkA)ZFaArOh<7P<9ynRzo)IY%9zbKA4Bir~ zm~U>va_V@e@*h}}*E8(g>cd;A+%y~3%G2Pa!>z_YIA0eVK#RIo2rBVAkz4Y{VBJ3~ zyrdIUm&q^o2o6v15qX4=-qe_2!%_dYJ|E(uOGJPL-H8~cDAOYoG$i1Wt+H=i&yEr#577Frk$%uT|M zKTGICli$P*$1d?(J7DPP=+5z%L%o!;$>2AK8$o-$0%XqdP$j&XKdQT2ii3V>av`XG zF>65`F~^=KbC~D?EwX0rcn))2SWYy=pTgxf-v<%3t()YBw>EW)|1+X=bA7qXaK_qz zd3PzRHMknH#Qn}i&(u}xyI`t%WBy0p$0Cv1M0;{{;|gO9dwQkjRzggowhw7O#A#n& zmRf$yu)@yY#8K?3b5rA^7bb5I{p`J3jD}L6D%dUIAr8?nBaH>PEj1>7fz=v0{CNy+ znju5nz@|lqU+Isv!M60-_rA>?fi^eo5LiO79vz$t1Mw7W-8nb$odJy}%vEfRsj|8cwJ!52M3 zDMS4R6A%WA{n#tbWnV+<2MPmIB0?7~hwnh-c$pqryFY&AB2z0|Wi{^<2Sf4V&(y!& zDDSby@^4}pI%b{KhjJpi+}K<0ZD&L?LYogT&ElAxF_h4SM_V}YvL(r=XInuo=4yja zz*hMECN+<(u#zEJ96cy&=ENG7_rMs|;TYN{Lj-x1BmYI3M1aYjPrhgKNIc`yXJhrl z%Z}URJTaAnF-bL~@CZfXuBz9DmIE=MKCKjpLIM2^oCvV>>kZUT+_OKbcQ9vtOu4~= z{6!R${AQnrFP(gyudMm8)#S@**x|oe<7qG67u_O6xVP?Gc2p~569#S-L1yk>d{Jq| zlAVixnZuBOdw`~Jmze^JKJCp?*pnw|GilaXbCtKhDEAOYUebn7FznBWeEHaNN%HKc zLuX=67wYM2)JE`+&ELKU_Osv2%RSMj|2X%uG}$rAasYy$vvyF%pBK@8ibP$1L_@Tf z5zqZ^iFtG^QkJi!dHRz~@;zgQt))Bj;HNH(kSFq|v>j&jEB~%q!_@pXWvNIZghiZmuaZ26{l#Lp@4knA%ja<);ifz=&iTFIobA$X4 z4(Uq>V{j(TEp3kc&Is+@gE5pP6FJZ`ryM;eRspGrM&+O^#cTPj->%rX`5L?pIL;82hl7jDbA6cf)%o*@l z2)`&5WcubIBwdSkC~1-{%d2J3jJBJm@RdIzIwPFDE!0RtU>yX^N@}W1RB8mTH^{Gr z9wHbZVuWy}+9;#MJa}3pOuV98N>QN6!hQ6{lB*(|JJv&Mj!KA%(}rxpZ>yBqIex*p z8^URuMbCve+)0kf{!E0}_FwgxkUWCd5RZJAgl`UkF2Apt%Q{S2b3FeiaPN7H4yHH% zg_DLLiu|n57j+Z!)(xo!(_iV^L$RXEGV&E-8TM=jU__k+OD}w)3?%K5*C@YA+Q?ks=uAJ0jL;9u2}9viHIdK2R=3t8uE?W^$S~PVovjrR!WT zfaBSGLx~_SOe|B~?oZmtSfn?G)y#=piJo7&J$bRD1>{?o$>6krk-IPSQYiiI^4%tR zDDOm7tYQ=U?t+RnFOSwOt~qL9aBU>8=wvN-+ti)xsVa_iP&i#97_5Z1)N3NM$1g7W zNdAnmR>80C2#G0Tt;LSI(;Ip$=U*3x2W|XupDm?uA~|6`{z}2}&Nt9DVy{r`@L*%V zduHqxl&Mj}(3ht%q0v-@*1$jp6Fan+^gOS>4^c0_zb=osH@8@173d(DAcF6Z6-6-r zsLutSJhJ-w^KR4Uyu?9}sKfSL4R$6Xm{PuA z%S{tx=!)Gb*#u<+y@hbfxns-n{7dVO@5L_F9KzeZ^NhjR9~&Frj$-LBbIbhZJ#GlLgQq$H zIferQ$jz@d$<}dw{~D@34tNTrb&j#UVO{=>Zl^nHZS6#_w)TTB^*+yYHYlZBpA_PJ zZmrNP1qFeQ`JTltl^wjSJ9|_nEY9nwn)mr>#a~A4M3-mU={5>&!#_}E4FBjog1I8Z z8Cp;v?SWzPt%srl1eK4yR^~=e(O_kK$9ivK1vF~`w$t8QUn*q<>NPJ928C<0H>PD| zV7>gVQ=J5XR9Wlb!AT}0ld?vfo&j4waS)zO4|$IGFwSak6z7BINv^8y=l(eW3qgEq z^$mK~*78gAR=th9y>fZDbcD|z=5K^|D=+qZ=e3HoH{mWv;g1@h{_NFi)w%n1QRALz ztm7b497HBC^2RybOJB?q1R7!r7m3itSOjZ_P%`k_Jl~`o%0!?#I91!U!1M-@aK;83 z*P8Qo^F7<6-Ug!e#kk~Dso%10T7w3>eAe@mm0Dip!7GclCI}73MO`$l^Alxrq@?St z{pD4Jxe{5!=x(-aVCs_CBrdBjG?=tSL>Nvfq~ zHcrXrblZ=O2edvmH$_U#Rc+1MvsY^d$hsFHzQ*felm~bkBT_3l*i-t-NnbX7$z&lB zPkT4A|J`GQl2-RuPwkEnzj4cbLG`hI^=SQPefBxcr|Y##+l5o`rB4qk^qXq>(O=va z7Y*_E9Z7--3xVf*$pt||B+uv}^~Jqc1FN%bd}$Vwb9EP`0_CycuefLVZ`JQ|j^4jR zF-deB?(H?^PNW~XQYSz|g$|v%L`*WylKWwWo(QQter~yOw2Cqa~DBDdynYoClZxtgd=sH5H{rG ztF2!he!2JdU^xwZC>76>L;FXznr&=@kbgS&``g{VUWwY%@#@_Iw}jMH=T=uD9~zF6O1od;9fB5b(6Wo=ozHC^jx z!aUN2Rfa}C&87Ig^I{)e2KBw?Y~^Ym1>Ablnf-W)HLyF)daIZAvbP#&d@|YFwA1Tk zwdaY7fs~yXHpvrzcblbd`*hm>yVy54UpDG>a4U@u!@$)Xn7-WH*Ff-spI!UX@gw?(83L#jFL7MT7brQ%c`rcDiM6QP zU-oAPtRt}heAqB+C0X==vU2wRX16Q*M*6(Oc6Vv#ZbG!R@r7yAT@PMcSOqPl6weTh zgR+-vrHcNIyPJ1J>qDHC=6Z=!tOH%Zd5D*R0-L@6$X54m$sa{PsS zq`{w{U*0lx(%$CZtYcXzd=S4ivY5FtmI=zab3P23mus(e1mU{O?%^E+Zp2`>$5K(1 zy|iDLpQ2tc1YkGdF0~JxkkuHdBE~<;7xm>!ApbJBpwxWk+jRwu~u4Z`d}q4Nq#jyrWtzf@Pi3fzL~}KA@W(6MrW} zPC6UASlGCC?t*{#eW1}sOKKZ&3V6k_FNcs*!c?nfs6#9M%LP@ z1Z;%?j{>vDyTnwS)o!gIw7s-XgyTVLZ&ifx-k$TUykdg%hTX5#!$4w?kG#*;p4Xml zeOXShLp_+-39z08sab#PV6noY_4Rvc)fHCFGEg+$fAYnrD2Xl~cv;Y;ED&5OO0m*y zqNXS4+3)WXp!0iYmwdI(BE?oLw8csqmag}*mx+2er2f4zZ@5QTciWyBTV)mZ$5WEa zWjZfXE^)(urM9;fhk#aco|W9<{%AAK;;ECNfns22imA<*p`ngc^$12Zq_EATxldpw=-n>$lBO%57V;~uZw7RD%2VQzf78YS12HBsQICmvCuY; z{1ALz+m|_~939N!l7e(eXp%&2;3?_2upfR{5ExUMMhR=Jh&Sp#HSP7%lqExKxV#zl z_5?L4+zyvfMyo1)9&@tK+DGa_T2$#$`Pk){Zs+k{?=QOI=pE)>&c$&N!(QHU^NG7r zMX>bcoq{ybJ3MX?M-6Qy+~D!a%r@jZ34A>9Qb>cIj@MJSW(@42x-vG!TN;;e@A{up zr|KHD9m$$zrwI~P+u)b8ztqMzU2n_X;dSzQh-hTo`G7N!sjYt=o6Tlp&x=T|V_m2u z#?57iyX;+2uhX0pA@cGO-&%6r9?FwbG!us_xy?IflJ|f%i#swa%Rm_Ij`$XB_Hs~` z_T3td-GjauXFNw)@(;LJ1b>nK|D)-<M&K9BF?ckknI|3CMh^E%J-d_AAfONM@=+#M8G z>h$vYgKvw^ldC8wL&9D=9M&q0Ri_z< zJ9ul`Q2U-zUO|eyX>~ zY;9;B21g^g-Ji*o-I63k<%F#&3+FJGj_Eb(U9KI9-@AyU`BB-&`?}7bI%@C_gc#S| z0#XJh#0;>`xnSZbXHz4^L8`kbc7gAqs=+42kR*U)(;d>@hk`S2(qIA9p`!u4ruWAq z*t#0tf)dxJIu8qPFMQdIo_=+o)_?^!{f7d8o;+uT&^vQ3q#z8g)RlaZL5(0dcxq5t z@imAxHvGlMrNZ!A`^Yk%j^z!D?e_g_W=Lm-DgsDC0_|uXhS@T}KHhY)ALMWO|Bx?` zM0(hM9e7Aho-JHYDb=8Nr*gU^GZk&61}R=_D^d^oFQV=V%W{SG`x5`N|5Y8AfQsNK z7;Mm;%3&lHu0e)oT^-d76TrAz3TGdGqxJy4g6uk8tTM`=3O^4N*t(%hhVr(~UV}0J zUkmVUNB~x}n8H!l4b2TvJE2H|KVu<5vXAjW7J@6xaRob1vJV>v@1sw8_$WwM)7U*W z4PU~Mf7i~!ttnGaR!oiL7#EBy(Xc@SW&&FSn#zs}8Zrs39&Lhy=VNP0I{p>kOAO{8 zU@E0QXk{JqAymE1VGL*z@Vj?QwzBNK^4Txt^OWKZk3Y~Z{$l!r{|#vKO8qByy@=K_;P=Ay+ThH&-rDP@=zg1nE$W}>iZDp4}=wNf)6 z7dQ$CP)7;CAJ{vtq`(2^=|@8$@dgE0O73Ge+`M@jc+cD`lpSp`dkZq45I)fnkFNyt z{etAm&sjm`MA9;#8$f!ZcF~<0g6dp~TDQL}9Tk!l{eunn;enAP*S<=cyL&)|V>h&x z_N|S^r@QV8-ceU#M(D@hIl{c2oO{k$0MGon4NF=p;7KhV?;?U6tsb@cigQ>cRpt`a z-H^kc8Y!F1MRk}Jj*^fXJTRkM-lM>u97Nelfkhhz+hx|n4mxqQgOCP)NQj*lkVt{_rQ8O)z!HLO zRl8T;HSwts^3n>`dZwM~e+xO_2T+AS?que8&puSDuYj{h8t2#-UbS(LKR>nha-%mm z!m%czmVAz!a}!aOjGdW{H|#X(Jb@SU5{w(HeH_w?&H=}EIPVn+GZEKmjN!ML1syEK zM|PZotaym@aS~PU*wsP-{suAvc*_FiHf;W2DPDT@z z$Z+Ev4KJ9JT8^cul_^@z;&OZk4aqt!-Z|QtB`h508X4ZZYl8mtDfM<5VrcXCXaW1J z>=;{5e`uRgO^-O=^!|RJ{6l#9b~vm3FW>q^SX|_i z4sY&#oQ_TRYayYot&p#C6J!vr(%*r_ilj-K>}tRmTPFVD5Z3o_#+G}5)xD1Y_NB0} z$`qk`@~=F^xbEebxws7b>O>mM{-Gsxu$N8AkNE5ugEnN=$Nqa#XsmxaBg6rox*+pR zKwFzXZB75^nPW{I^+=tWEPJ>!`O9R7i@)DAOEGi}hfAY6G<2f=n4Jr+dL8r); znB`yZ_a%EEvQ((XkQ<>jQ;dnwf`wT5eswTv{y5uraNT11!Owgr_>b2~98-ta_Py1! z5c_Cgu5kMN;f_<=>~^k;lI({;4v=qoieRg0K~QWgoXhenHuseRkZ#PWhdtu!7m$Cz z4%Z2yIs5b{g(f^E1g*@pEBF`V>)}M#kQQ%?`iw$Y>%(6iRCYtNopQu}F&~l~-?GR? z1ceG9dEpOR$gRkFNl~X&d@*>Q0R>N|+eF`xLEZ!HCx`ld$jqoA2l*Tr&bLDjD5<$s zeqK|6f0qSKuKU-3MK%@T9PXcjrHZA!%TgTSFMVDHQ26siCd>1~TN|(=|9UR%jL{O- zY|HgvG2ZZnk^TTy9*+U5s}7W;A@@s4cs?0iGws?*i}Nxw3KWzCc4ubUyLE+= zYDQo3M_2-uG{chs*I`beb`e+o5?Gsrc;ygtc(4_`Mf&|~-tf=kIyzYq;_WRO?RWm8 zu8n6jVR-XIg_YjhXh@cp7BH zmHX*LM!CkusWZ95j^C1o1LwS65kRc39{M?O%+~{!qnXMX`k67yWpd2t7MF--xRy$S zoUqd-;G4Mb@O{=l?4s9*yP%YS?#z>g1;u*ZOCj;@=%flbEAjPXmQj+vyJ-6te=q!e z@tW+H?~PEu?oo8#O8~RB-0<^Iw_BXf609hzA0VVHh(eqh9cM`;?+PkJ45EbeHrqRb zaddV%ukqan%9Gd}2~t@i%gvhh!*EdVhu1i=92Bux=q(x?^$@Fi>|7TcoyTjLv#j`7 zlp=@tj|YEGZ@18JU<4n_c0N%B%Z6_~k%`zI+P@VGJSX}~&E<7%n^w zycI<%#au{-7|xuc?!g^q|090UahfbzhvREGKS^etf1y#JD{lAnpL%7YOUC z5f6$F zaQ|M(3{4CLskA;B(5`X{P8v85;ksh@DyR%3#Iec_JB&OhTKO)1=+7-HsdD=C_`^GP zI{A^O9CYOr{h2ww3j@p~X~E)9z-A(z=B{&{f$9U&z#Gxg)8VA7Pk%TETCG$5%i?ke zisHZrdc4PdI@7nENYLX@Eq^zwiK#LF3Jqu%W!0{&W^NPr*S$BE-4<6gBaVOZtKxwVzs{P8JK~%C@Vz&mhyM<< zk`vW^kOJHNb-dCxygd%mDqQY7!hq&hvF+~wt7gri@M=*6Y2-2^f8QZs0uee6ri5WC zjOryi8%XvKSo{)v-cPU^JUcItK6+;Wug<9V8)G`GLnsC_n)EMx47t7<<0x+V3Z94WgHF08} z$A)bpb4c>}&}OK%RNhmg0h-LE+r74^mT>gpJ^g`w1;l@6i1vpYJ{X657v!uN(Zi0Y zZ-#o)T?u1o@=K0@N{`%9%PT;9Pa`FHp8tM6x(a4a30Lm%;$JCF@LmQO*q_5HO=s_O zV-IAL2OC@OC5>|*gO`2UU8az!(jrvTnf<=0FEm!h0KdQg*EO^IW%+We>F;ZF^o}$T z;`3W@>FrBT4zVr)%eI@gJOu6$kV0Pi@FO4}wChf|3cH1tFU`t0eI_p0GD5Mt;pr_7 zOA;Vq73qGg8m=I*3@f8V`kFi9uX^BZOxs=M?2Yk=Pp&(_vLAKOA5QO6U7bcEe)Z(X zxXS8Tk%Vx3fTpdapyh*KX#~Y35olv#4c?YJ+jd0xBbV30qkpxHIYt^+S5%Zx6T_c} z;5=d8v-OU5&{NHglypu@lJ)t9)$b6}psHr@Z`X-Sc;j4Gv?5>hFTu4xb>;94GjvSm zFS&zPIY6HI^dbOG-dQfMf<$nk;^zb<_@SmT=Jd~VWnME1hBUO(t*@nAF>onv)H`NK z;jy5<-*HRbs|DLq7yVsUi*`caFI@~S%UlBez11&jb1Mr}gIktsvi;X!gj%&z8nn$} zW;!PW~*X8IP==O1F!5J($HI?u(+fE+@;<(5Uf-0 z%zeCA?xtR#Q`aTd=lev5*ic0`hP+1!^VE;aP5!>}@Ud#}80o;q<3~SbRBa_K4_p5| z;`^i^x$~)0Dyk7$T2WqwZ^pK!^-0zi)^)Y^b^rRs&fI^r_;&w?4WY*Yy z;gEKyFRoI)Ykd?$Pq!WV2mAVZg$)%O?nUQ zVf&2|bdHxmm#1RtoS2tWM9=;Je>kzvFbu`3wM%i)hsW#8O*EU0WWqtTZM8c?MU|G9 z8EBcgX4{x<-vthbkT^tl_k%Rth(a1hk{9EfA>MLlGWE?EZ~<0a%23R- z%vxw#6bxPpn`8WI75*!K>$v#%;q0I4pUxhSEw&Y}PVq&OS)secbaP!|i6oDjmEmlGO;ckdW##M}GF?9hE#4|0_I8TZ?`k>N7DL=L+;*G@qZ-Y0wUB zpM6YRY>Yv}(Rgo;a8@GokM*F4|JEbowk;G z$!+%y{mqECi;fJtfw8aVGP^_GTaIiU|F$nYc>?6=L<3@N&HgcM4}qm8*>HC|oC7<8 zHwHEb$W)Dsrf$>~r3u=BpG!y;FzbZ_9}rOb9vanSR+>K1V><^qSy=2cEKprf;%`Lm z(2h)vX>~!s4n5X;(1Fu?@#0acABr>0L90e!w`3h21B>q`^b;0Xf9Q4j`o*WWMSg9( zV^_;NC^=e75~V(R|0l}By3uR#xhn_?p7BwS$t8o}M^KiN~&1NcAQ62O0%J?f1J7rX$PqHTGQjH#ML zUYpXIu&p5~%Ie0uORE!n^i)R&dcvfEkRnijFNHFUGUW3k^K+zI#bc>J?`0Txu9>{P zrASDBcw3?Mi7QqgZm%RNcl*SF_WjlV?=;ZG*Rn2;a|o~U#eC~VZeO@K!;;3)+nVP? zC7nn{@w6T?5_Cq}8yOA4t}l_SbR|au64t=ILIbY)?38Ee;+#Sup|~Cc6tN-=InJm9 zV{MPy?{f^wic7u>1dxy1JOx$VFQqUOFA1tqj68Ni|98g;lzY=_h6vsqsT8*vQBh#^$FoNQ9P$-acDFDO}Pr^~jsoTTIcIATJ3LqvlVeoEkM^6a>;W(^plB?Nns{k9R%ppx+aJ;VeLv?RKDT6zzxf4N5i ziG@Aj^Ie#J%?$gwy|@rP^yd#s4Wezrx{~<;m`I{hpF7YU#S3aQZ@-$${DfPM=lsL$ z-tkUi1A2JK^@glH#dtovzjI14i`#{B6!H zGTOWAV5@fw;_)+V{wJlhenF4>&uN9`ZW->0n)>GnCG3IWFUc?H z(zk9MQpeLio%B6Nc`Pt1jg z9P+=uod~UFV7}$v?tL|`1Uc3M9y(`b%`&92h&lmKtu}9CK9M}U&zc2d?NlYDeIR>p z8Rk7`*Rl-54yB|A{r40Ha0D41M6$Gt!Fe1)$Zr{Xx;h~$@mWpM1d4;di&0`Z_n_wz zlK7*~q16XZ6XOt1Ps(w#?em3W!igZ^j^ZzOwuYG4VCb4XZtX5j^%{jL36ZaPd$;0? z?qON|&#$}f7pbhY>c>1hJ4HI=B zY#ZAtC~EjL1oQx&(-RkFlm0#rFBG;)5Re=>f!CYBi3Ne|5KXyeh4CBv3s!dBAVyAg zfbo7x{;dgz;=7>uX34uuyvX`@jrJwiG|xB&*9{1b1-;6SW+*Q?3;WF z0N&V)6CsTBFXD`-Jzx|U`t%2*j!ZL&HGor)ptc0dRTmOpKz;=$*qRLbuEZcpn(2HN z8yfJTf2%C&nvwBRYntbnx?jJTm)a-}V&t{ak99oGxipD72yCUPp zT}I1yYr@%^8Fek!Z8}tIyxeCNTgkt+3GTUrks?_Ri#}>?QZn{4_HIFuVprGSbRb(6XLECRhyoDV@STB@0xhh>#O#kR zGF=anTJLe`8%`osr7+cO8@DjxO@?=lG)FY&qlgqQ{1@-uu1>^$BVNr=E+>6&NGaTa z1M`v1wwwzzY9D?hZ$og+E!9M1sxA8R(7tV-hXMWdu=p;v;ScK!La+6B2)@H*^eICZs{Q}4am(WoVBAlH&oJUi=$G2Icj%Cw}jG@T9ngJN418OQ1D4aW@+JX&;4Q0P{@E1zHSw$tUmH|e0z z<24ujcV@4Fbo(6!lwb!zkeDuo-pJXE#o*ZW6ua8mlT6{0jD;{VI8WV!g+X_%L}Fy>F%QKcR$LK%8&K*UW@I8+D5&)*#!uBz%NtZTG@w zh2JsV@jV9Yvlps|Ndfq0^j8OrREBT%?d(JLQen^~hCurEft)*tKHH|k{8j*RY=`}T zcZdHxc=ph%sP*z=uyCix2xH>Snd-Ie&xtQQ9~BL+-iliML3JvrCi}UFLx^d%g$0xm zWcLjuXwW*k{RTwx zMc~T%;!$E|nAK-!RCxITO^uTre*%)xV`1Q_Mdp%{4~>+?t*;w=pCTP0tm%*%`(;H_ zs)F3gcMC>U33my7apoD|0$J4vHKUuf4^as0(X@eGe&?8sQdz|&|6>mS(;|}wc4oM* z{gkiRk#v>~GrW;-Ya%a(pX3Kr7_FG!WicDEHh&h@-D>SD^04E z6NJO}wi*xuItM)$p7wo~REmUs1I)$K+4y+{4q{;E zmJ5dANB^Fw=c=(4e*ao!6aA*r*|J;@TfK96K?{rmALu_{r#UNZaVmQxx=XEMy>~Y^s}(@o->(zGWpOG2vSI89yaf7 zJJx>RK`A3omhBL}i)19rvKu+4I7}yjV1qH`hcZFypJ<7{C?}r=Z+~T=mG?3sF5ESl zdy`d*vuo3cA%INzff&G%cPN)AY zvlbJJQJ=Q2X-+^Et&fizm);-H$ghSMA>Tc1UtafV(qB%YeuvoK^4Ya^6eg-nn+G&D zj`DlX-){Cj^0*0j{pJgtTtFdn>j~5=l$1gH33h91XM}43rg`?_>lf=`FiUrIdCaMh z29ua&est1#<<3(a5Hi;_JJa}k@Z-NPnHi(F-Kfp&AtAfxc54Xzc(gWI3W1L(nt zkh~ywL*i$Fc5-{*aZj8+c*FzSaqZJ*n>oDe?<;9ZUv3QI5*E%o)pNBJm`nK&o(2s` z|Cikwzji_e7yj#i!l>agqIq;x2nbr(_>$tZ%3-~3ni?KYmTdTAJ5z-Yhib{?GNWfq zRQ#tA3-44T1;Du?1pbnxKajlEtS%8xYMzj<8>}}SgvBp#8YUvz9>2X#u~u7b;Ij}R zC#l2DCL)Ag%G|y!fAuas#&u3DmRxbWX2Z=%Zw#i|-Q1x5#~2yta-j+j)&j>J8q;hueV$orCR~=4^BQ? zBt0ly0eUYLA}}R_-W8yX6{8EaHC;^Am%=Cgb8mBr?B?x zjLm82RZtmMhLF^`AV+a0ZN2q#9(aJmw*)J>Rv8xll182c{O_}dE=cZVnehMEFBFroeyAH{=iJU zy6*yLsU<{|uYr3bDLR~FiwS6d26C^3@Q*jqFB8|`;{g1f4$9F9h3CbaxG!P>=huj0 zIm1|dj5-S@Cp&Xhz(My54{5D~jz`H1P!pTYpxf5RWf?R|=aeJ(pG%#ku|@Y=}v4`JJI zE~b*6=K&yP_Id{~;Pn%r*c(KRgU)QfrH8e{WC$j|&sI|G#W*JGfr2H-cx2|V1}S8x zES8Fs=n|}RIG;sKC8F%3el434anx}BH$fk6p65c1s-O%-6Uh2r(F$koBg~^vZz{7n zHv96vI#l=s<0)c>_b}ld2o)Mgu>><4d}a;E&Z=I5{drd5y&z<00;5Ocz93B<;lWUE z*Y8>0!>eH-M)B#J@$rLH$;ovOCKK>=mrMukw%d=12zz(ybM}$zPtCl`U#KaxxF{`> zau{Af(TgHC4Rjy>&uT4|%i+3C1KK)#nxYWE=b}PBJ!%{o&0xBncP!_<@0hlLehORGl;|CM!X?`Wwd2vidOUALO*La& zft5g=x14luf?+QgN8NG+Q-U-63~&uFR}D9Bfo%99CyI0p-% zi$HMqy%;b{@{w-n&m+g3#~=pZjw^ofFTH}sMqEX%Fka*!hjE^pmneiRAH<4h+Umc*X!QCmvs-HVF$OpR+mct7rW$^$2=G$+e=3tHbSlV zJLm_ZxHsJMaeJ+yjWQFGhXkkp{py7S{`u2QpTW;c2G#ZMmP@>*t?2PzK!u|?oq};1 zrp^r$<9Zt^ucdc`cjbKSaGiG&fChUu%;SzT{;bxvvDP(UIEQ07LVNMM@#x+_=B5Sm ztqa#0#}<*}LpO{M(>(GgT;NH2u0PjJdS0I-88YHu)G9Eds)yIB%OFP#QVgg17acYi zak!leGL%ir;eewAZr9<)oZ?^}FV$~kb7l&XRXLmnbjbYh=-g99GJ zo$qxT{{EaB&Nn(Bs-dOt6Ct+`79R|U)JSgxn!k1&TY)WUZ|1u^N@!lLKBaJ=!a$?L zCC06)-+-DGhTo6m8T$mJXIiaHb2;_dJWLsUneQ=`@wsUI5hL6OF=q}K znxDO@343q8)2yiWbBdTyCB^_LN_Ry$BG|Hw;yItTc&0mr6%NEA>kf(A-I;MNEQ6(U zw~!DCtK`gFC6f*(;mcJd0E0{Wq>i)IWxpn730(qD2;WmO1nQ-EzgkU8vP;i0GNpgl zW8GJoq4fCir*hF=PDY59&Fkrsj@5`x$3xS@#@wiXDD!S-DPl<{n)57+dYVb#-($8Y@v{=;^@dT0Rn z3Vu-3>h+#4b^G}=4*|{}kcuKoe@m&M>;mty2Zb>2FMY-=2suH0j6fkJyLcqSjP})2 zT;UspaxVUxVprcy^{+H3-uro<&sBIrIZ1H2T<9QunVUEq6Y#lfUGPOef_`%f1~+we zvTDA`L2&f+3G~dUfWCq~dWtM#E6kElCq2VHAZ6z_r2Js)yct(il!FkanDl)~XN>l(i0 zxvyH2UHA#lB6myMai90U9ogdcp9I-w=`93A*t$J1Q1I~akb2|DBQtlLfVZo!i$N3* z9tu4&RPISf5~y$&vwNVuVDQdD06#Y}3 zYIujV-kGOK0vkmYRM5rjxeReqdUV_7_yr*JUI`p14|DR~J5Z zooqe%_r=!mwOBe_Ajq3du3xgP@IT*P{ESgsH8XP34ntFm85PdkN7+UOf)3NoMIgLC z<|-rM@>N_MB5_D^ITY^?Gy4cKb*OSLrK5-Ub&HFlk7oSMlmyTep=xeFOehbU)k6dia8$+2zc8Knnw$*?5T93n zvnqaOO+Gbl)SStA|8tN@ro#AB-g_R&2Add)wPImQr&BA?jMuIVyoy63>?yhkynP6YNxyXTUbo5I?h$zS-(gl)y_@hka@NL$d~yVmjsv z0vS!FgkgnKOMYIIISa(JoJ<;bcUirjmvs_LB#!i(QeGB66OzA%-GHY+k>Mc z5DsOkUCl60k`|a=AH?1|#;Ai88qvlH#cx7BFdi@gI*Ti0rPxBUn~UuLH)#zTNMSF7!v;EOmJuI|0gis^1Te1v`TR4CO+D#)!e^@X^F zH%HTTdk6N#U{hF0H3gwZZSn2_7}7O8F;8HPhiH&fj=N}KW!{cYh$NLx4FKW1nM=>K zGFrxF?|B03JUd)b%S%#&*>sYUI;D8zGmcZf`x=ppJ%}-H%=4$Z9P(VT%hF;4{jQvtkla^% zCt~a?Go^D>6bkEZBlG-yFv zwx0bWgH2x!3!&z$g24a2sN+1QU|USX{CyHeT3y|atF&Uy&=Zy|Hx2m90Gx)_FJ=yh z{FiBm4`0KC_qQF>f$Ty!p%+7UWcj(K4WPSVZ^TQNliM7h<0umcB~J#{8#i0r-q-!E zp7~$BpTZ=cV-F7>7t~)er8&65Du#^nSq<2R@L`6z*RRf_J#DDFWqn14pNY#F{Xh|2 zMBr_8jHwnzaQ(w74yo6+--zzaByAD>soq*qUd7eP?-~t#i+2SlX%>I3Xdw*VG}g%} z7%L8UMlMTZNbR=(wibwP>yWbEKy*s(B+i7k@@Q^%Oa#aGG*lqCB(1=Qd?$u}pqsZ~5$*l90 z;=pXCZ|d?(-O$5ge}X8P;i9SI+gy8bWH6&>ZL2|FA|q!=$gB7+r_mVu0h=ElCmyfx zEy3e6eBLGV3h&Pi^d1)^-N_?mns}&zIv-Vs)(j3)!*;>BYapk_u!lGZ9@k*C?``{x+`}Ero`9MNO(U=@yxPy;$OHz zkHLgsW4IDdLBauaCj6K9lgf<+uC_=@obgI*O?Q$riJzJig;MD#%m!*W8__~E6;$vz zlbnSwy*T09e@DJl$DkLh3XAZ6zrD9QZdc(GWR>9i9{O`cl4s?BvIDYVPQ@7%{l+zEOMfdzFOKGE zww#*KlOX+-fZNznDMry^DowEb(8Iy&d!Ye~l}ajC27trMu-||AhHsahoqbxf6vMgy zbI0c&O%7gOhrLTkp7%=C*@SQwJL}^bxZzjxC9V(&?U1N%jjTQ|m)&jxv30u*YP*x3 ztjzPFd?%*Fo}9klWqSQ^|JxxA2F~GyTs9ugA@0k|CT|i0Wm$IDKXf4~4`OTy&9!1r zQH~=ZK-%7_tSi_!9JsbR+j1D6{_{;6frCNV#M;@A7Yq&JB46A<7E}>`niIWSKPboi zwJ1Tw1*T>4JSy#U-eD=@`?O=T!hHLoGE2I^QRH6_k)y~^ZM{jb@t|E7W$lE-8^q;b zpK8^GFsW^d9>?{o^YE^O_mf!afyUgYP$+Ra%KrD4mBHkDqo*L#U$GzDS0sB8 zqaxm9gr9BR2gcU$DjM|nBml%aEQ}@sH1Fx}=IVMo3`$2ghj3D@t>g(rD0^&dv#5j7i z$T+80^fWukAZHUD`4K&m&U1kA4Sp80C)^SjLb{HZj?J{%L91SaL)CC!ULbg?0S2`W zZHp8=%XXs;TX^s~Z?i#f3rR?LTHP&a3O4M1mgn{foT|QNqt+kPxPuh7tqW zb3=K%W9m>{8WauQ<6LPBXkxQTT`gfQH^uc$q*RV30Qs^f2Zm2#*cX*5aK%!fy+06@ z%^W+tA}(Ud%V?M8=XM<|>FmLV_KHQ}wT<~-HOZCjISYIJ^*hJ?xQx=}TEm94c(Nz9 zXednGcqR>STc)II(rpLp%EXNR;;&#;w6*6T<2BOY+B;4~D@zlz^{wJSR^Ze|v|hd8l0GNjUqB?yjmTRqPj)>8jUt$78&1WR~8(`1e;l z6Sd8^9W6~>304*SM?(8O(YvRjAeVd?r?1gv6=_BP;PA70YTnALR9YaxUE_8-S0a>h z$L07+R>J3+}U@7_=nO>~CO~S`Vk408JgHG8(_|tFRQCxsC#unD-;jbx!XTVvcO>` z>s{POU+)kp4xKU~sFb$8iD0ApNJZ4@HnkYzNF9OcP_(bRQ7h8FOM;l#6mDhZ8S**vo9=-QUee-@%daUF;AFMkmc=X~IynZq9&&i>Jx zV&ug8tybPjeT`{EX8Aywl~;&^^WQ1(zlT-pCsK!7fMZDxxH9R9JT-4zxc6(O$*(feSdit`%+kek<|AH!aXehH4+{8 zonHmgv7AvK1csGgo>@Pb@r_|ctiNMdHlK2o510h1AiNh~@F(GaGrJ8wcW+~mfN!25 zItg7i6!Igt=0St|AC9^j**%KDuqo6ZOA$UNz=nM=q&mu11$zJ7pT#kz5S=B zZx=Exo9q-c8$U)~tmQ8>;&}|LK5Oy_=NW@PD^J?{iJ~R=&LY_-??nK}EfuSAWU}LDpcKZ~YpP9eAQT%x#p< z3%4ePKVEE$-kCtqL&||N2hy%u$YS$4C_8+HaoW#)Y;jpp5%ELIZL)qhgWxXrxtp;F z?i*c`pBE?bSsVHCW?}@_k3sRga1q?~?z^mx zBS?VS45Pz2On^Fe2U)u`n0SZzi#aUoMkMv zjt3F}Wt{wWW+8U6`VOQ7pG2k~dfLWvG5YADA=0qxZvaT`qdVc)pPp-5zn?8+BY>>xK+X^3*&IXau88nL8D2ShAb_bZguJ3Af^9bm=K z%bq~O_e8+8joLk1{(tTtmuRb-_WE6)*fbH$qv{Vdu{!V%D&!$tdB_j^ldf;8e8Nr* z%ut!pvLl(r$V=r!eCdxB8+ykbYO!N^lyB(z8s`JMt18{wU4j9ehOdLC1>;j+1ZNjo zes0az@8}JlX?*s$EPD&89inzlv6enwT?#B^-+H}iWBh4*d*&jgRYJBO>?Ja2pp-bi zbN^&7%K@C)_pTIn`@@5m4XkkM=x(pezt?Ee5Kv_+V3cuESb%8o7cPubLFP_Ck%n_r zR~7qaZ}44<3A+YYBYgj67axg5Z>iT4ZY_#ep%~pm>=g6hc1J#q_IoY!b-(s*xv(sQ zj21$Ei6#Y`+=76I{Ifb0!mexY0bh2w)C38!hY243z30wpeS;FNd&jG{Z!Y!d>zfS5 zNiLWwdglC0pl7cUG%8dHtUyVofvlILeJ3Z4=W1o!fPcvV>WE1ihjotCI z@8s>CTIo3{MJSX*)wM((5aE+_xQ~3dt7tjg-IyK`CGCD@iH2zLr6#vpe{qoIuD{mz z$h)xA9i+j+Qp7Ox-MOgU!-_Gd#DC65S58Yk6Av?`xZ-`(=A`GC@94(H>O7hHOx9Ii zGzWW%t*m=}O=GgOEnp0vcgaWK!2W7UiidkU)* z>T?&+tQqCvp$#7RiRO$KTsc0_9-^Y!UI1Dbb zxoYM*6Rs7Pp}I{7|29Q)`11H84cL~{@;OkSK6T=$*yB!+qBrJj^JfM!!eg@T^6WZW zsT>Hl+HfvxjkvtQc!q(v{^ZR}gT<)N4jmJfwt)f{dqYFX;2MeHuEtB@(g`|v{+|V` z#*L{b81xxa(MT7jell&2xao|FF+dZ8@BJjt>}OID5T%Q)3g{i4x zS1$nGmq+nm&_J}mYz=}ciNmio#YG_RX~rM`)!timn~Yap{!bLQ?aJ(!rha}gx?{~F zTP%lZ_{X`eJrYZ!Z*?~2$AI1}!86*;CZbSvt01P*NS&H@M}Pg;ac}bx{6N>vV(A^% zfOsM&=0`Pc{_@~yJOnn-$S`8C)Y{?%{1`c!d1TmUC;kNtE%kSVV82*m_7(B?xtDLE zaI!rR>owim#Q7o3>e6`l*~!W%c+47v2!!M9j&q9pSH zq@PQC8?@m``6W*VO8DN_Dc-3Cu41e5n=uh(al2-=q~#JA_MfPqXipInzT|D$RR{RT z?MgHH)q8aICGWa5@!a!+<6VD05w`lm51;ai?(A1RyU)6_yfAcr-tSfR5|>#$jc)wc zPDHiQJca)nGln0PZ3VQcHNg1K%iGc}m5`pAP|Zq8CNzb6LNKkfu& z7?o>zM`)6B-sfEd<%zbv%x|g?A+@at&pT}`5ySWjdmvTBaKA{JPvW|t;m?Upr6k$k z|59~d%nyGazG@V>t;z^Fs+{UcBoA~o zhIV8W0t=l9pVObvD_6bFav54+rf<=@z%h|JnrNxHf?W~LuOb6Ba!M`Y9gdjTLPHZ=r)JlgnmN{S)AVna1Zp|>1zbmOutAZB=bK=Gu*#-UZ% znb-0;B-G}VQ%srh<77_t}Gt_kkVHZLY``6(7#zCS<~Zt`95y2))0LN)vqjqo3|ocKllYM?sW;Y zCFE9~ zfjwYOi0P@M3U!B{W!pg)7(0`jc>P=qj(^i1n%6LMkC*wy`BV<$&%ZZ?mbt7^%zJ$n zYd00KVYa-C-_^0F^rR>OAp0-|axvY{oA$wBy!p(x9ARjzDjj**BJM94#y| zg~Y$l+MG`1-5VLYcp-sP1L2hJ_sGrQ6Nws+W$!(TP2q_HN%u9-s?F+=#76ki=ty)&@BmXdF&T#wlk{A;3pTQP#X2#Ha z(CrAxo4tCeBXe>TC%TVK4hr5b!1I+sV#t?ln-Al!FT|})3Vul|NX9f4UsJu3%bd1! zIsQjUf~6C0F@Ixjd3ZDqb18e`!{sFol2|K0*KneW_{AqotqaC3CG6jg%ieI;;pGsd z%f*d1W`2fV$l*na8Q%=e<=F$DYnYwLXke zJb=9h1phd4RG3ONi2r?m{vuc81slI)=zdkXUz>!=4<2wa$qetzj+-vpk8L{K;FX&@ z;Woc}C}we+kmuCGlJ@<6-E;xpbZ@F>M>NR=B^NM0=Q%!;iMW3AT&;(k(w;a!*v*1h zDsDQ|`c|Q?X4!d#M9=WSCg)njNU$V@VDfOJ8W%f9b9UE`Fjv+yNvsY+o+$u>U)%V& z_HL%3Yo5w7Q~UMwq9v^zRdzZA9srE6ZV{`-?T$RjoW|OLZ*k~qby$nbjmnue@SLTM zgV2}O7R(QxbD4vNE|y&&FHO`n_)(o1JBd8-Kt1TML!Ie&-2xs959Dx)9kAFVcA#+dq+)>(GKQasqia1kG?MUKXYsG%5Jep1TF&~UR19iUL|p=*SyYvI0~wO z3X}B|0XM=ba;7=zeViOeTJuxKCyEj6Ty`k|c>Vo=Qg5?Ay?v!Au8CwoJjBjaojXX_ zS%Y=Vp0uAu`Ut1LwN_wgP$eXXAN>xFB2Eoc#FpjLD;B_T>g`c-m!KhDiO0FUdEWm< zUnX%jb4bk*&Tp(Y#FFvqMU2#AyAWH!1l;Bqf=cKs|L9xLRS#4e$&}9s@Nl_N#>ZgV z^Wx_{0LzzD3ohQP(O);z`@|6gk%5~%jhWu}5PY+Px{pCpZ(j2-v2p%ZzU14Sy;$Ft z?qiC#tz|;p!)bohTYv}&Ct1R5TflqxKz9oYfWyuRW;Fxs?t10MLcvl8nFsr{V1TMa z72~Uh?PPaDyHAX=^Tc3Kif*;}XR_(5V+lMnvvyI!L+J5)lz(^&lVl+L48Co~fI0IDxXDYlQZ5b%PVw7P zRD4{Z9$e$6JYtXLt2JDJa|B0pYE!c~Tmv5K?e2!ViiM*uQx_l6o;`+vfFi27gp&^V zDar%Ovv@Tm;4`&3+A=4x@7zoDBDiM}yeRNPHn>aL5+dX)_v{{yr(uuFli?#7`X!Yc zli$rfW?LYF8f()adudA6 zU#hLGq@?8bduanZEZ@&B0GBxf=I|FK^H!nm+?wXN)vHBcm*MOkF1mLg?|n24N#@MX zR0p-R12WuXB)8B$+!{+4K9#yRV<~IVGb!X+r zWmb=w!^i$!Ih0trDTqtN#0Cr6LeDU_%h6kX(GXcq_F~{5|^If%Zen{av=3Zq`tHX+hy=BEAFe=8S87ZX=C<{k#H(oJyz% zbi|ooff^5b(rv1VPvl~c27rGCMqZ#6T}R#YhSy!$+wok%^|X|$A5hWx1C?+gC*CCf z(Uz}@9?ieJ6AM30CEc%LL>)Z;L-ST0Nr|(D*mbH&E~Jz+#j?Og)a7P(z86pKT{ee0 z^0~mNg7)t#0=by}hw#8+#e?|mw!sERjY8O@Z}5vIDY-sbLD%C?jJ=c4`y>)6+pZr} zN#;D9Hwc|WUs;kzF|3(#uLR-=FrB&IzeZjo+qKdWcEvhui~nnRlNI~;D{%v*m2=yb z-fST4`f4hVA7e#U;-d7grd(fErCDP7lnf>MgXsG1T^XH~Q0_0ss9>L5&ynw4;M?Ut zKj1p^is{yb*sO`qJwHX2`^r}got8(Qt5impe>w>%JPuvC^6k=l!f5BE4hLzGDD8h+ z=buglC#hCpu*_iHs$M^pw$}IBK^L7h&RJ}W(d{T3=JdMQHK_wLtEwn;NML9e4lM#e zyj6*#Z!OBb)KN9n)l41?J(m^|blcjGW!?a){|W8kvDnY%_Nfz(zd4pG2eRE*@L@zE z!U)Sv;LME$?mLh&xP}@_3k@Jwemfcp{VIK+x=TTr1^C5Zg0g)s2>Bz!1RFcOBN9{U z+wp~_%>;jclES^>z!L(gYA{j^K$?RGy*^(eK0Onnkz{H{jIf=izjD^Zb@8xw1!5g& z9i+7=;+w^Fz`f(zJshDmzelEbZ$go+k;uL)8#l~bizC0a|G z8P?)FxKUHGh*=2?9s3u6J5pM|eBe3QAr=BpsA6)6(qIq9w>8gW1CH+W%3dJ5zqjo& z*L1Y{6~2KacC= hhi%GknKb4I$ZKmFoUDS<|Y?7wXIyJ?G?T?`{x-@*B6BkovlG60nHD-dj!!8;lVJ#h**^R(Cv8BaM*RW9fGj*-rLmH zK2QaM^E@QFvZCVm1ai^gWrtswOqDBCpIAeSPzLipJ+KUi!2V;)gz#YBlD?+m|NQym zGlgo;YUzg)?{~!vw#WZGK`J-N*g|QEU*?XiXeSc36B4{ce;8#l;j)Fl(dd)ga=3vr z%RUQkV6%t(S@7~t(e$6~o%H$+=RS{%I<4w)(@o^Q(OPN`qUE>Xw-+{1VO~LB&9VW7 zcNWHvw=&mx@|uJxQ;T#XFBx@V#UR} z!Sg(I>EO;YAqbf}O!FNOcBd`W9Et2jIzp4kBxM}xOO01m6}3Py0!CS5pdGw#CV;18 zyKp~ulX(4)9w%2NMw=QpCf>u{7w$~;;v7osa?LhOIL-3Qo_M@L(7%pQE^5kmOuwLg zge@ld8Iszhk#u{dDrJG6)o>~$w;)t(iTJ&}5#^;6?sxfb$Z;`50qkVDn~K^=5!*N& zvp};22A1xw)W(cM^udcXMla!LNOt&<&hFdm4frnz^}0%OL(A;PzQ&lhTdedW=^N9tZ( zzE4v^0(*jpvfWMOOEf67yPt}QGQ~l}x}dk-V94EMGn}sHe*#eo?~4Vfa3|LK8K{~Bk{0g!X{dxfu8#V6Y26R;8tjiCQNF@ZNLkvl6( zaRMD8FRI~u*RE$?=ev>#j+#Mn8I#`jy5K>%+{l7^v#;y=9neUGi=K;%@Js`Q zKlrP;AH?`RPAZ#=WN-z@4OzE*QsFcPkMd;=$)0H%Go#C&a9e|jK(y8FhI;&pYa8?! z0a-(fU8@T7%@4-)&53Jv(#p_fmnfVES7i9<&o@(nix%mqg=&1jbD~u*r&WGaK2On; zqux)4$%Kf1GW?_+K43O?UDJ#rF^$pUSf%!uS+BTjJxgwp@~>EzD(GOj_7=n1q<#yT z)v_!9RQw49*6a!$CO!TDHz1tc`0(|Qj$q5~q}MlEB@#XC6>35T`bc%HIm^q2aW|-A zt!l-5BZ`a4Iyc39zV}G6DXO(J^&YfPZ{N7Aau4G(PbAbT?U68-7G#F9-^?kqw30#s zLWuOZiJLne(w3NmLFp{adag<#AwJP^jLhLMaG}}w&#Nw}bSO#a=NE8n@PARLm8HDt zQgr2n1?)5n-xWoSqsMJaBvRu;e~JW0m)4Lz#t|bt&Ejl{SJvr1`t~Pw{3 zdUJ^WJCh?-WP?bxcOTNbO@C|h8BNPU6Mw%T$}fXwz^39q?ub#{-1XZT_yPN@eT%C! z!p03@o%ZNrt02Qo+u^0p;5C;{j$NJ~r7`u@2f8g8^BWR(7jGwnYt)aN2-(-a$bSR% zUixRa1HT9RDXt#=?7Fi`&=sfS+TdsYza#isz$@O=QchnU^x>r+4l+;GS6OT?y@4cj zsCEr3c2Xh!)8cTPd`@Huv^7}(Z%BW~quG_}T3r~@K{w+U$j&5*7wGRLXobR%j;Z@t zA|Ctb6G-dR-LgTbDQG(b+Iq{)`Z>ZgkdqO2Hm@W+S6qTL1Av8z%$D13`5lTRjImcY z>F4*X<}Ae*4`6&0H^hep?u|qIrPxbvdcfUU)O9fbK6|di=-X?@MXdG$Le5n@6kiAZ zqWBjksOA1#X<8q7$8Psn?VxI98%PK)*|ir))9abkN5^nR@;G4{GVbnveaF-UGOM~5 z{7Za2^(z8q2y0HIi;r=R2T1tu3fb5sfAbNsH^C-lv45?X(iyi}NFv)!BWhuy>j`)M zQw>(J%Ef*w!TXcv^a**_zYGqHk?@1>5W!nFu&Ev@)ib`jA0*otTrtRc&v<`9Hgu@ZI(S5W@#HH==KQ@T2#_jK)ZJl#J z7z>M~1iKJBr?g2p>TWH0aSK>%rfXF;)`)PjcuFyEf0;7OUbwhBN4Mz@S;F?55g2xF z)PZ9YAT)qdj>+ZZ!_~`HDLDK*enTVdU3<1MzMs@xfNR9}Vs;k>?sBn?sk?ymw*o+f5(n|gC6OTH;DbHx(aTF1fg=Cju^#L@E% zs3ktDnuJN7ts$G@*n(W;6u5iB$H`ugV$akhwVsF2RHEW?~=T*#e4VW zDfzdn8qqneR6Je^*vMxr1k;5Q-o$czd9k4ene9TYK>kDQ91J{g~l? z;?>~SU%*vA7!lc1l6bND7=6Rxw4#vs3Tdt;d~2*Z?3ar^M9L|6yEKk?&*q3UkddQe z1@Hi#_CMPbKDg7zg81TptQ%7911_^AhamC2HGFE=jjsbg2berz`y%&dCaCwOir-!8 zAUE6U5Rb|w_9{JP0OV6=6=#-aaM4=xr(owHtEZ2%t4W2suF~(ZMN}jQte(7ZEk2ji zO0rO6B!AJDtK&9MT}JaLxVwQ$(|+LSdka6G(=%PVrm;gjuQUPvcK1;S?e~|c08y*S z`GkKzq<`|wfa>=fpbe>~=p>tc7c0cJ?&-Oa2Vk22E~ zCe!Y*1JZlNsk3>MebjoiBx)jpk7u;5lGP>Dm0~E0JO>fhll^_;>60#v9@@3JAdxpaM3nJ2x)R(v zw8WgZ;&L+{eP0gIhwR4PmyiF`eo1w!_ppafV7S-9eU)9hjEuQMtOk_4V~P(0bHwQr znHNq{11Oy#&tCzYK*SBtVH(m96~L}_{OqVfwx#~?@_gIlQEh*&r)Yi_Q^KqETiW2| zOuR!!D%YHWIU({0%7}n^EP|tKi{Rnii(0ij>x*w&2d*< zhAwbd&Kcvqe*QVggOI+`CY7hYqVxem;t%6A25UZo6tUKZNnK%^^zThxE>_GafQ%_V zb~~p=r2-ym&p0;UXcwACcoO-N?GP~pjng|l^LI>`Fj~~`S@1(lhHi!!MXk0+P_)Eyc}15 z-FlKiOBUiKi)mC|t}+Y>*e?>cvQH8&uo+;=1T965UU{m2PTl?W`$zA`BUwk5kc3|N z(rs7ho^S8|!jkMhm+<)By*-NcjcVr$?b&_Y&CkXysXx?_`Ql5J&kd~__V`Gt>AKD_ zL^UK}d%){`hsFFL!j;cpRQ$~~kM`D<$*h9)Suw0EM=eg)lc;c@9@3N=3wwA;2U+(E zU99D=uYi;KHUx(^QlY#>;r`d=O3m;u@OND9g1urPwtIX#=?SJ<+0OFtA%TC<8!db= zQySt&w|J-d$H4Dc;zvQQW?^Lf6K_`w4hmJCeRn4oH}Oe;_d^aEUGJEKc=mYU(>0Hh z@zaK+hwtcPOh|RIIK2fdBvjA*1|*w{?a$rq20k5xqlbn9_49FID7XtN9EAYJQ_79@zRiMuO4l$lT3D&Y98aC->Nv59JY??U>3&- zS{-t&JWnCsZAUo-_ut}hLDGZ}g_4_82bP807!*lCPx7ODVG_CMLay-C7W@o^EK#cA zpYkQ)YwN#9R=|-LOyP>x)8;^xLA*f^yciON#y&nZZDPBib+WFWng$ii3Kz>NTKo`^ zD*OrdRRBarl53#661TqvXUKSG|NSlDuR93Uy)lOJ){s&*@F9Kk_g2-6PJuo&TUz-c zn48mc1F%OTUb)#kfbLtoTLIUQ|3k~*x8uwhKr?O1PjSY{O#<(@&g!;@W)2osr(Tik zEb8F%qSrrR?;;_AuxKFMu%f3GvP*$d8btmYTXUfy3Ayu;)(>oMCE^V{taV`siH^;l z4iuecfiR=8J8BKddf}7qHYfwK6k>s}cY|&e2^{FA>HR49Jk#kH(u}@;_LxLf4_(9z> zD$L|BJcQBodLt}>!1O0QF8{o`1;gLd2JciC>!##zlpZnM-0}~l+SI6Tm_tvXe8NBc zK7ExnYpaBJ7*dyXihfqYm%paM7<}`iQA$iUAU^zpnsl#(s*WOP(U2Z;`!l6quxFYn z=y$);f7CuSSm0WFHUE>t-OP+8$`Z?Rr3UOM)0lRRcO~5;4J>l&lcqh}xSCr*B(bFS zfTZ?|*faHm4%S(?y~{8RSMHkVgJD?ptmh|?B*YxDP0G6yEk{i?EQ&l9NLTq?tgW%7 zm!vJBQC~0n)CY4I65jo{V`#_3SvOXF`&O@;CJm0Ndux{HU;r~Rr>xZ;_(!au3490{ z1tc4t)K_q7?N+otkbi{dG<8l`LcMFSi>0c&54?O+F>^K3%h(>$H@;nk^;~mieY8*Tp(&3ylY-O-#cK^P;j3xM07=$= zovPBhpK}MZe+Pg)za?{29C?j@@P&Ddx-RZjF^Un`NJe#HSe%()Uf>YJy7(t(kGMTr zqxhCdqFB`A1`0Rj)ZUNqPJ>-9h7;~T{^}UWkwMR577vzQD_~;-81|n8XB+I9r9rM~ z7#-Nq<7T!dO}pc5cr6sYx}D67Xo?-AqLN5l?) zQ-0*r^ddeRB5Nhe(+N-=ltc+J49I4mC0z%qeXau%N<(m$2jj~$`@es|3>}7@@=VUj z)8VOEME=xE1f%4v(>q|&Rcc(ml#~h`p1NY}CuC4s0e2q>NcZ)9l#TshE(gEgC%h`_aFaLaBxW^e9hD;AAnmY^8MxY{Ug!6y z-QA^ehSVXwV#&CBZ4&&8%h>I1HWYJ-$`(zl_GrGMqFt83Bin`lKl;x9_X5ltK3QmL z>OY>7#CbV_=R_aNzENzadHBz=_2)og5oQN`ALI4?fCYo=qN!Y$_9e0J&{A|Z<6e?k zanDj*F_1fM|KcY$rI+abwIi065G&aLE6bA&-*%6uDMXY$3<)!6x$%aZS&@rBeTSB} zFsA|Br|v-bEqEoB9Una?Ti33haKgLsFFx(hcE_ZEFY3v`$s-dCrISpSR~#GjIkby> zl;^aya}K()JHCLz5zDg!Fm=Y)a}PP@5~|Lh;vZF+ z|19_wbkY$vAbx2c!?e5ft}!+Sc~Jl3sIMrmy_H@GK#|Xur(TKjSCYgudW`txr#p0b ze_wZzPAP;Rd@H*RGzJx2Z|$!@`2E>ym$lajw<<=5ATzKbCfN?$3)Iz8+1CHC9gp4! z?z?EeddH2(zf1jQC5NDvn09y>pxL!@dfD}i8#HL-Od|m?fO{ zvbhIIRPzc~!rb$cw%kOPW;Z%4y7m&6t|&3nh~Um<2b_;(od9uo9BO0qMt!Pgi{q8V zVR#M^9`7nSr=4h~!k))|k73-mz=%YpRdNp2&P7YIx9~-Y=<)ha&^GTnjWNNozNS)t z;0s+eB&y?@e_L(;xlB=`-b}0C&$(V##?;2YdwcCRolPl%Eeg>{1Ct-_J~%r2;qcDS zyqFlOgHv@vgMgPnvQI6|cdIq|*B}Lq{#gifC3g?ZDagA8`~!l9o{+j&ZxM(*+8>@S ze=v22P8k)51tPA7?M}Db#7!7J$_{_|sLoBZByrs{To4wvag`2NHCfK52(x6C>>-!9 zCe9)Nw@t$zUTzfSG}-6;ioP3k$Yz1y+RVN)Dv7CmsFN~r&p{>o#v>?X=*|y?-y$Q0 zelg>b&U2VBGm`-K$J&g?IESNI^f4n6{b+%3)ilICm?q6-tQ-6WIm^EILolI$LxHHsN=m>N>bO^V?>=<#t{vM0LLYVE%`d}XM_juag-;5ug(g_n zdSX+oh*sSdW&elyZk3+ErK=K?UIzU*Ya!Kwe}>P+Jd?|gYTk0gF& z%ojO)z@$v0A~`HpaPn`AiPEN}{MWMc+s8?A6N`;CK-qzGirGDeUFseYT;;aMK{tIt zSl`qmK*6l!B?987V(mcTMlT;RyeONQdEari+u^@`t&!}oFs#IWt<9Sh^QIZ8F|h9M z{qkU}9~}RF1Y`$01I5$L>5DY>H-MGVQ*A%42e%)ut;wsxIrdvuXYX7^+9q@B)!mJP zJS=?YlrrNhC2`cUN9{uVTz1qMN3@07BC_Muktms;)GZsW5OZ9_8Rqos_v5sGf2y8# ziB6GeiXqoV3zLVV7i}+rIrq5cBF6oo7tuPnlYrm-8p*+9V{yP?s=(BL{;|q{4#{_U zD?fqjuj9jfby{_|pr4R>(ygt8&J-V3Gml5!>-s%#1f`VQSPWbwZK58FRk8x2E42=U zo=D%sNt-q?29J=GJ>0W%H|8k^lDFg2x4aZ^aT(V=?mL0(xkQHd}6P7~$+&{UJfYHgX|nLJnL!d6$Y@hC+i z#L0q<7{`<#LT8_|QHykP-jx&D@gT*Cky5uGLL261 z$1DXB#VR+SwO~ZKTfqEM%+K|DVC4{18$WEX8Al^u_yv?P%V*jB5$2Zf16M>2KF)dI z(dzRE)RELy7-d3IP0S@O&xiKIsyHl%d_O;Eyf<5T`JdQp@Gd!Br*FU8+4 z((pE{u&bKpQDbkVKi#fgO=uZA*Lii2#E=ZjLS+YXAtv)t@pnVR7V`C4%&}L;f@jZ6 zRk?8)l+Rqy0;YCLQ(m?wTFSDop-EF9%~+Uc7uAs9Q(s-xrJ$v0&9`zWjxb5H62rX=%$pQXhG|M{N$qD=##q-87?(bysx#Lj$X%M%)Cd`a%mjw5&( z{@heNF@%s$%)WVUn3hs6_I2Rne;De(9(` zn*5N=5r|E_0{wma1w@VTdnt{`^cEI*(K*#{IMG(*A`Y8rpmZN~!O~1Jnnya;KCeYP zyCJT2No$Gk@qBpL+nG=LIXVEw8xE3%pO~5z&iEKlk2;}6cZMq0USFmPR+^zoI%@0n`3oy=;cZ?4Ad&*lo2$hpc$fSZdIMJc;h2ijH5oIOOR%(3s1uE4y)bsf#Tef-(>R)5|R z?)SaxVxiGs<}Qd|^TI#LdFRZ(NxQ#H+f9&q{7rV8YzqG*$E#;DLY5vZb{P)b71@9V$cdP7+W^|0N^YpMa~sxxHiY05~(tX~4+d#?b* z$pcnT73sJ9_l|q-hDGHy_jKTl1#%GQT7>VJoInRsWuRXyOpyc@-R0V048Hc*eT(Ye zbsR4sG|n80ko=(bS^?89mw9erPm&uL+(F2%NWj3Mx?9onPS&rQ&MtCuY$CUPrB{97 zr%>B zMx!f8B)GS0tz}cK=fgl~P&s&$Arl*)N4cyN2$w}ZA;UQ0S)b$ud zaooSKv^z13Fbyz&IVAC=Xp}JD#^bWo%KM$R17WOlHZ}rvWs`9x?`%3PSP87KW9prK zHF&4RLaImb_KS4jMAsV`@Lx!dXEr_lHzh<}x@YiX*>0zYw2t97oBE^4DXy~zxqCmXqmg-Le0yH>LX z`sL>$&67&l0ITtFylp!)WIyg+JkD`KX0NB?-A@0sQ5_uPy#XQX@7(2FtyJ6uDd(G2d@1kpqaT6N|Z~85b(?vfD7;KQ!jJu z81VXx$o>s7US^tt?{=)PRS$Gv<6RssCB4lb;_Z`l&bL7{1s)++>JDX zu#B{t6fwKEi$o*jceUEeNOMl@_NeJ%AL-k&*%LJ=`8bFq?!C#-C7i=wg@LhmBV*E` z=qo0Ct;dX@uT%XS|8*TU!Ttg(Tw=@YkTHLpI)b)KSRqU;YEXXE;;is_cX`P zpyBnYVJ&)BiaVE5<2q{C+`{jW@ERR*G{Om#?^0ZN_N1JTDp=3>g0fO#L=@53)#c3b ziD&z({TEbqQ2>iV(Xbk+^Q@=^;a%!z1f1}?Ab7oaT;`qH6_MFgn;1 zEpa>+y@#r=^R)Ywr(A!&{O=^>yqDq0Wzk*1pzOd?3W8W}aqg;}=E|H+#hChMlw_IB zT`m?<-7VlFEIMr=M593x_)p}`RqTHOx-to$N0{i^vY)=KoK4UvMn>o-55p5h@>Q8* zWUqpFGDfQ&LnZwT#>9sb>@bsrn=>)Q@-xn`?G+P_q)I&oyK;}aJuI|F?cI^zFKgUg zQ%mV_LB+3=B|UH@8lYRidAeXqBC@uZuD!><#G!t?@$uqohuOp5do^rB7@c=muB zme+v$Yj&BWSNm|dDYfvdi7(t31*HWN{=;)Gm$7U8>|r`zSosu9^WS1jH~uffd}Q07 zQ`!!jqIC2x=$w$OzxLR+|T}yxV!}>34>Kx^i&1<(CDAzVU0L;}gQoZu}J-qIt)A8;y{@|v( z&%c>dZPwab%5%4p>MkX{FUd0k%HJWW>lWzt<%0j44~!S$OkTf_^%EcYml$$uJkdXF zLeX~EcED&hR~&lKC`#?(6e^tZf$y)ypy?55ndNOiFGt!hND>Uw#0bj-o+8%sVGmmJ z$F+`^rb92k69zA4NB+{1#I=w|Wr?qUa{!14iyGJg>@pRff-{^=Q^w{9vlHulHy`_; z*@7E)XMOnE$Pq1m`#>GVWA`3^XJ>mp5)z12GUOF-j{zkKCiIgJP{53cN-FIYq8A137w$l>A+~;dci}7D$Haa)Sxu-w~{HB zT>s!g{NS9JDYF%@?}6XWBMMRh7AtV)c->Yc%FgPvDOnSXjZM}l+gG^v?sgX2AU;qi zl_6aFf;7nRk4`asD>Wh$8a(rl@d}9e_v~Bim{JL9u<<|Fw$3*}L?-uAyhG7VMWDW( zlluAre8R~&pABefC5TxOIlXE&2bn^iymgwN8p#K*%N?XR=wkE~ucQp^=kUSN#ks8U zv7gzm%jput!hh25){LhN++P-`+cA0gFt9U0&AUX#`=uwZ)Z*_M;^JCK(LTrO4RQ{! z)mW&%fUr^trto7oI}9|#_D-aS-j-2((b4gP)tqd$ai8x*j+b8 zQgzMz6~tYPaZdGmRh-(K_@)2v>3|7Nb1{Ov*&{<%M!LP^2sFTF zWqybZY5z<0D<>?Ro1AJ-p-8gtWwu;$o!38Zh}R7B3HWue5j`xlclzrc1im=IYXIY< z@UfsxFnN(avhrp=zJt8oLRmdqEHRKbB;V>Xtb=Rz#~)@Q+1t(*EYrmpK;C;!^udaT zj&nAzYdcZVTw1p63n}rS34fLJrk3U z$r%b5$!54?_p*m$x3a|m;MnKcAK=U$^{8XJRSy>|Sh{udg7yRN*OH#Hb;xu>7lPkb zW>4&`@WFdy+?K8E!taU`hKMOY`N(AVY+^-D&N&4c3vJ@|`ZOQWHw{0rZdH=6d_62r z#gy@|&bIxPAnhw@infT^FH~1~4c#3XjHD==lX5M4yNMfFK7=>tD%cHG5Cvan1sN8T z$71!8nzc|&<03bR=3JzVLFmBd*D0-`M(#(mu zrl$-PVduf;=x@k zFJM3Dn}yxA2Iv7b2EfToIvtXaJ_zB$=8CTnx9^%ve-hL6T$fv=HG7pilD zsk09&OWDaoaKX2g>Of%R+Y=al;y4pO&FU?#wC_Gxq0*4A1K%a=`U_Q^hyTTU-j%*} zV`iF3AJ^8=rp$bUI-`X?U9mbjdQ6?~C0+0H38*XI_CxdKg{FT~k*4nw>~HoObK1@x zOnn$tS=&s$8bod0^FkdL<@Yz}=n$rm7ERg7*lk5>3(tKf4KQe&$%Wm&kTtSNYr^Sk4t6BAwt(peo3AvD7~G?A znhte^L?(GX2pdXH4&?0h#f6=V7VDHb0Q`yhnik^ zML0r%B#wlJkPVoM82M%LTapI1^lzz|!X)kgzS9*XFM5j$^#!SObrY@vr~wxm|8+?RIEv zUX5m|bBb+>RO)DGtL{n@kB54*or|u5LK#$oZyFqWhba#{(r4wXN=Hwo(WV=3C$u7sTB@^4i_P*U#>E#1DQ>dGSu zEIT<|t7f#Ut*eA<1rSwP=+j8+i^DtNeH(*e{HAT{rpiF-`4eWMjU4IXCC08?LHX#H zqVTGF?4<(uV@;3B!&Eg^w(q=Mh4zs;QX^Vi_k#_-bSW~V6?DBHLqm>y9!ToE9S*{K z`a?5FCfu;~KVb2_Ft(t}l;ebH!aMkan!MAxx_lq}GsxQg$5QpHJCGYnDZNpqjZ>E{ zTb7l^8Wbq_LC;gd`~Mcwv_0Pv4dVB=$Qa%Fx~Ptp(VE$Wpz|bQ_-5fwQdIG{uRxG z3LMvNlb9qg7Hl?kBVL&x8%Xp6J4{a(M`cTPzkl@osye0)aSr~LyscisqMauLTfp%n z@g*;OY8$H?hbJ7&hUkk+RHIy7LtC}k-@XXPDZF(XtMP*MlX~)o)FB)kwRyXF*sN=)c1y{UUlI6bUk2-eOT_|!n++LJA(`yO-!_ou77 zWsJu>Rp8XeXK?FwarXeOK!5+ruF&b`&#g|UIeE9%hQlDftx49W^m{e17ney#?<1gY zpHiJ6ZCw2L-#`xv@|pbGVlH9Rrph@zE@&{=NkvSjcbaM6SufLRtKZY6Am0pATX%H! zU5oW~<o`8(`Bxpq=ZRsp{l_Es)jIH)_2{w1>XlPeT_if z0Ze}iu@`YuWRAre2&WuOW~^4|AwY`KjqmCugnt3Ikbl|D?QIj#&{<3F=*n+;@n1do z#yU9p#jalUrt_kAj>X#z8%kCtywhmu()8AuAHOdhRP57;)&}oW1?%9lZ|(JzMIC`< z_Lf6->z_Y0U!dN5Fn6zY8g%<^T8L0C)spDm?PcyP%wevH*nup0u-o%Fx@=b8-8uv(wXhw(Y4IX7Vu={yOaS-q!*2uOoo1I*ZSuE3ZS zn~~#(b^UL_6+2h;N(>6EpFgI2)717RT9!*m#kv=l=&NF!y?Q@}GTHSSx%7F@lyJ^L z-Na(*!*Sl*3w`I0KkikyykzL4xS-m?U5hdS?-AW&!zceo(|LHa`Nn-%Yt{Z~jH-rC zQLEHW(5j+VP-+K>JxYzlYE^5dYSpR{vucyr1hr@F6(e>j2}*0PH}8ASd-5OTJkNdK z&-ea(u4|h&2~~+I9(?wZ>+_wzqfE4+Snp>K@p6x&P4Z)tR_Xsl4-(#*6*m0yq_g%} zx>~L)1p{Sja!;zl_=E{(xeD)yp2?9qshTz3nV6;+$$rkL*LMJ5P`)B-$?x(NCcFk0 z>dCvUYGW;1syZFiOvW&pY~GIG4I}0~oXUFPLTDXHMin_nsj_UW_99eyBflKAJ4gw9 z<%X?19`di7dnXIPXPB&+9pG}@qHQtFW%pndTz;hc+WY4GO}{oV@yAkff+3jn$gPKN zf@;OtY0F_g7!r&!O8j>Sc)c2+vjn?SBq^hiYL5*oY*cloOMRnMZ#+V~Yqe@9k$Y+Xfnsn^;C~DDG&?& z6VYOaRBIm%3~vT6e&^pU*O6Yyt;NQe4Gj0dqzUMnbwxoTL5`EX(y%RH{0R;W8+U)K z=+wv%LR_tW55ePFGXyg-Vsj~@RRj}?x4AMcwc+0rC%wfL_qPuD{RELBa1Fjs$(Lc?1?Fe@hcPgk>i<{8V$XEfvR`D*jl;q4&ti9iVEBXKqblE0a8k9b})sVHE!l;>oX3CK1RV>fDB z41NHkx>nl@8VZDWD)KloB2+F`{jYyE{m+NdXEa5;jn(P!fV+2AMLCD)b)@^s&(<+E zfk?Gal@`66!G|-@CICdp;0Ag%X_8v!YR6(04_dM36e+l|+7gFRxX#O{Uj%@5ew2h$ z>bAlYYzKM{R+o3u;n$LTN@gcF3ax=`T*#~63vO_8>Aoh-+c$jk(lp+lAlW+9#%G9a z2cd?8a=u{>1RF=ir1QT4Uq@Cm0hsEXMcA!Afw@Dqk(F(Op~mBX9{IsJ?|)m6SDWw6@R&qk=LDr?VzqnVVg9tPw_vtP~5 zGJXDyC*lhQx6VO5pQlIf@I`uypeb`_`11JZebZlPN7b(Lyte(M-6JmM9}4S~ALzr3 zSVr&r(KBhb^3dWIuqyt7iCe7Y;!>(Boe+!^*!kzB*oFhe8ZyH>TZ4Y-))@;bOr(z# zju{I;>8X#~#9FYCCO`-mYEMRH08=&TXN>FYBqTn>M>6B9QXg{lMeF4!U^q}$k!X)i zc2N8*A%P~i)uf-^s(KmW?L%v^XpXTrCIB|IR!!Ew^d%?X&Nr}Y#?lpIr=8I1< zwm(dpOl8O=tpL1L{uYBh=06u0kf^w<#Ry@5)UijFth7hMVE=Jbxgf4Vz}L;k?poR@ zeLXJS?WMYY*4d1Dt@?Tv(%3}XR+b@blDBE-2=v2#L8@*0o2E!x7{7dJW-z6V_3xRMI~Ya~v<2s!ZP5iUvmV6aRerL`a2=MpQGI0=I1)P$&&hjfcCBu5L^!IGY{P zB%i$v_lb)inbx4kUD2Kpht9n;RSB?o@IYxH_2sTUqd~_TeverQHqZGvJIM*~?l_gQ zw!X@1KuU*6z@Mil96dkZbQ|rJR~$xbH-PuWk0VeGv7KeGD^9XLhwS`4883v5r4ye% zK?u0DLn9#HU<#Su{}|l={<%sxkAs68eX{xC;f;Wyg(YF3+p^1CsGI)v5n-4bK@q1~ zW-^_8m|Wez)rbnl8iXh z{HwQ{@C7yM4Gg<^fVxP;8=f$2%-r2L;_(vs$$nPxuiRUu{J-5WhmZz?s9@hY|-d+qdxpo>_Io7m$tRBp9TMVyZI?qnI1Kf%cde5bB$# zfse=uy^c?rJ4KpOHZITW8CH$QILRq;s50p~jE)b@x$18=+eoZ;;GiBX^#htP{A1`)S}!G z3h3uaNl6(Ny}#O)-iWQ5eMC-UJ%8W-@0R>gvpE6S2SA|~HE?!^KLjCRu+K#VDL)R} zdn?%h)X;xOM;+TW1s{OBmuRu0Y}djj|6Z$X*E_}T{t88(L5eX@HZJs zG(DOs8#R84m-h5}MxQ~(ZZTML2Bal}o)TJJy0_V~tw6jePo2--(Z{!Cz_=>AJ4hH3 z5CTYUA1peGDr2qnbj1!?vEd7<5*`%~`q$IC@Bjjv=*^u5Cky2%)*|Baf^F4?MW;(? zl6T;ft*KACY8mD;=*voXa~BS4{j)ihnGGgHug6miH@PDwDGR2cVkbOR<~GSrj^=$u zzWWfb5d>5U!50ksJXJXH@7r14;4HVef8Qg&Librx{o;p)R_otqoVP9Hl1WT|E5G@) zLjzhV%N4}k9&&DoBF`tZxxv~6Ak?>?&}0dcjlPqaAJshN5q2hYOBP-wp*>obt6fpI zNhWpuePEe>a{o+YymaYQMBktGk!;>CD77nE1=Mh#&7Xgk-IXlQt=t!W&lQFcS;3!k z^I!pZ6zFDR)huHPY4f+hBywkdixsh>M~(X0-ki7-jg(pWfN>_IwKxM}tG*|+LB+Q_ zjA26(TINzD88<1m@ahINl2H9e(e-I?%A1dzt{P6Jep}$&x32&KZlfB5#|*htN;(h~ z3MFW3rWx~b%exGdgyIo2(~-R!>Vn;Ueo;PpUz=qp;AOOyi5`WMiR`xH#0$PS407tO zi-fA>j%*aX<>kpvKK_?eQ^_=~ce}vp0e#3mfH__LA*eYZfWiA+{hNg^uUbY+GDwu;#Qb&h}t5Z@Le22{UUjO%+fv4NskB$yT6#|F1 z)MfrvL>I@c*PWXk&uM&9SDpjjcml_5-@&n3%f3PFIvy8YCd$6v{;l`7BdlWfK}bph zQcVe$F6cHlOTE?LCa=THf^SMiHnR772_@KQ3*)c?VWrZx@w(8HXLL^=vP`}XeqJy0 zx>d%c`FW{+YmqQo)Oj&P57bQ(Dw5?<5kRFyyIvE6rFfEdd6o1Q9ulO#VgtPX9;lRo zCu32IjnLm(1P%Re0efq!yO%x>xhpoF>3$9&K1i)FZ>;N~qixx)_tk+e3xnlZPQ<`b zSn7FIX|n$kp4*Z8F%tIQXHx(wIDg`cNJ4qsX+3uT-$*hoWb>4k{;xInPh!Br3mczq zoVgb$3@f?wiL;oHC=78VbdFkgRq#UWm}g+YcVz;7Q?wNIAXW|h^~Gvurl6`48P&M;W$e8a|n*`qT%TIgy5-@2}k&=%DmpB`w? z^gC;KhKqj3V5J39Y0?ZH_0!WFE#$*k8Fg;L2@@$ukycg`fq(0)nXg(hk60pTt31PV zid(6=o}}S7H`=8E*8O?q_c9ZcxUJ7d-5yBn-(jx&?$afx!ccBYLvKN#$g8PYt*YRS z_^cXRm4iPFocXq@GK7(p8N~-(lQzn%c4WuNlD-VY|Ub~$)9gS>J{O183jT$ zbvN(`SsAW)BasB$TxA*}{MlO=wGU$25XZ3hknXeD(ypz-$c~HUDe1SJU}3f2uWsLKYwJkb*`y7RI!S^S^OZ`X zF8a5G(RSc(@tae~eZL&&%St1kjmgtd{2K6&~tz_dH?DSai|;Df`= zp8HuE4jiQ`b@Ze>@|QwpjtVh}Zui~Lq1#s%H(%|6>g?#<$aXJL@jVo)kifd@q;fl} z9(@&rzajx97`v`-ElRqMeAgl020IGu(Uij(z5X?d9xXP(|)X1f^l(!?_hq; zmnI+5?=FxpPuh*S@RRG5+4FZoos3PK53E<8a z4X`#aoR#eD=ICz|EQlzUAceR+8@_Q%icFKj{Hiuo_SPG2D}}V1pIyM;OX3Ueu^ENQ zx5hpGV@28uNzf^MEdSK3Txff2McHY+T+(`K?IV$mg7SLNzMcGoYt(If8_j&g9u_^D z*{VqR85qNhuDcH}lD~+^JeO360N+xqFA(^||K~Tu6qZntCei}tK!xDhWw(+PZcVpZ zM?A-fS+>J9Aih8NZ%8FyP*YYaLo9zbU&443>?<{Dw2Hy0Fv-z5~} zk>8N`Q5}PPfvwZ=Rq4vguR-_u^xbY-EoQ$a({=gy{B@OA?apv@WwP^m=BR!%tuomi ziC_-!ivQ16H1QXwv}mFf|3@Cw1L-%6T%sl^oS(n^G+7-T0B^Le^@62g#$o%yvS+qE z4#oJluU>;wjy1-5Czi(qZ3~^c-CWI;$Vb)(^abSGXZNMk=w>#4VHfhPb??S$Q0%Ac z2p-d}uGP~I=Y``ROzf8=HSk(ko&f!akWO%!b$)*CQ0J6qMRYansNtq)SY(+{SN!B% z?AvNpim37Q2qqI zK)%}o+uUvkoL0o5iczsw2N-UBlDtPH*HcE6MvnHVx}GW}An8a48@Et?R`lMm75p>8 zhq3@BAA|(y8~W;3XR{)+zP`n_=p&v-f2YQp(nK4x*-EH2U(@hbUD-Fm+)sG-Uy?oP z=gmoDGkuM}?fXXC7-`+^yy8iZpY=sTA7RpM&_gnXr}%>K`pI+d%ER;JdzI0lc@oNa zUj=xUPK%hBCEB}>301JAH_-WJIJ)fR{q>iHUkhw?Et~r2LSLe)p5BoI6`2&l_2(21 zzaZ~56@AKkDRZ5Xu38z6-HvZ<8TAXMY@_1$SZ}Mx9E~LgnSH&Ha9E>b8V+^c?ow_N zOahT9(N>E#=)9gt+jd?ealPns9>pGTX}9xMCAWD;E;^yC*kOCG+wpqu6CER7*;EtE zcW<82?bcE4n}wB(5)-&rT_SCa^}k!_R_}hL_fUGF$nW|^X7{P`Hu^u2`%`l36ns*f z;-6N+8y^u51BInG3=F4%7jJ(o3?2ITydGaQ4HS@g@S2bCH}UZF@5NclxQB?Sw`N|> z#!lnEe~urWxasCc9@SK3zp+dKaS}_8ecK%sR2$QDV}Jo?M+5^vo7v`pBPxiCzukvc1HiKv9D~0Olyd8 zD&5L*10?`o;Mw=JTFYD*=oQ9(2i&_8Rj((PbA|hHmyBVAzpN{$g5kA8`zKTk|C5Z= zRU#V=}H{c4^x8BV0rkr-Q0Am@ONOW{trDh#gx;uTi7` z&S9^s=g87hZ&`M47_K~`&EUbtB!bODQWSQ{E^pG(M^`jHQCA$HC9D7d$kF#hzznG7 zQBVBOuQ7(MlcfR=axjDb#%&HkZ`5-JuR$nEJL*qU9Au@L`3oCbd@ZXp|LIaWTYQ}A z4Em3>L~CTaXCANxI&JdxV-#CDA0OYrMURelzOH5C?{V_aBbro`wKWQE0N1;Gu$hgDXDn zsQiw5_s-$8q@sNgz$hr7KeJISu!zVV(~>t!C*{GqLE>?ia#dG#Hz zXzC8{ZmNh3z}TIcrsz!k;zx81h9eibJ5ePi`#01`3b$zW!QjaFnb2c=U#|e5^Q~_9 zdi&&Y;`@mL;pDCM2VxuF-iPiKD4htO1rGaI-~cexoWaj4w6Gjs^H%6F!RJ8O$!Ay- z->#l@PE)ojI5Ukwh5tMGo?Zk)Tn#%<%rVG{6q;mN)o{hHRaKnMm0fwtUwq4zYm}iA zPnP!e3P1y?`-e&NkBWWsZHaAHf8#=x)2T_j-YI&FyoMEJRyNTQhn;N>^6l^@)NLb0 zE1gyn&?*&3R0%TFq#<2$xU&Gg0fROzjf0QBppl z0!t%#HAHx* zQzs<`Pv6Jt)^l~M8Rz5keE|Dosz(5^do-uv`|#>!Snlpy>PQ#}lL`dk61qN_0UzQ= z+6q)g_gCmU@vW_o=;$S4#J@y;xn|^Xo{}Xx#emLt1j*tL5Bx)k329J5Afg<*f+R;r z!d@%*1@Z=D@3bi>v!;tAe6WEr<9m1XP%+l>C}^CWKUF^}%tIz5@s+}CS_MZTx1V@9 z{0ctZG8 zTWMZwZZ7-E*!cLs94lng-$;f&|&3>4|6Nf5DF5kt0lqQE>; z22tkzuQUb&O&bu%FgCmk?96P~?7R`h^$~Wr%cF$F2A65#lWw~URF>V`Eoc4RQ`oTT z1-gIo8YkAibJII1pq5)fu|tf+wcrIO3-Do=1ZHBfO2aLSMVVN4WQ}y4<^AYu_u3x!2c~ z2D%%(Dn~L_T#_zz^L-DY-JN}0cp^)EGKixQg}l1SOzvRCAIe8`x(|1hCF{5%rM)q( z%;Rm7-uyFz(&a+ni63QeS;!taAJARG|Kzty zEoz0=0>}QFCNr;oM*~pUyk}EOwj$U;CWsNLEQa=8^z%tHTzT|YTeL?v|H;nb;oJ^8P(X9ZPSBmjc||j%pQdSZaCC55diB``xq}WDpowdcx5%ap#Q*DmDl5<`-QyZ z{G&i7x~LLKl+4#){A^P_7NXL&eKvl%mr9uW_YpB-Q!7MH`eC1J&&|Y-FsxjeO0tIu zb$3C#CZE1BY#HM=YQ-{RF<}>;CuoMdVO*Gy6 z{ZYnHKt*Pq@;2(D?=lNzY)6VZr;kA9qKYs3`avYej{a)y)&+2A^wLY z9=Kbbh^Fo6!YQ=jveJOo>IBLpJ9iXO3Ul{bU$xcv8Lz{Zmw(C>!C zsCv?%s|SQ>9N`uGaE{j3#>S?tt1D~B&2v;{X6dNoYccscZV5MD_wwxu<(kxE%HR9E zcT|ilq+m=6Z7(^x3y-rl(G}JVxaadolyYcoK=uWL$5CU4IR!j`Kj=2$z7F*LPBt08c((C?R|&6+FZ1nlf@dQ(&`tgDiQ`%K>z5lM)VF~WYP%MY<2;zA}0jJ zfcI)xgDME~Xc@1SI2u67u_R9pbkKKoX(R8mM|yTfXI`~YK$9r5@8)=jEnIr7f}s31 z)3pSc_FtA{I%z$+A#3oyHl@BZ?DpgnK$BMM)b9s6*QNe*;@ceOe=eMi9JrVxKRuoU7>)q1kuqr0*tn|`l zo(+Lu=I*gQNvU${!`kC)gZiO*uS% zH^ohXu9U}fll3e$fOcm@BShbjMy14#S3D_e5Pa)oeMJ1{gUx;t+Pkfdu3)9MM|AR` zzH!%~S33N?`hw6JE0pVBVrxxA@6JuzQYY z2okmVFPVWWP|S)o!zY+wprN%e^GBKu;z@;o(IJ4F1GQikg~*)kI^eP69{V6NYmx=~ z;;7!Y-R~sp1>`qYNQ-*TvDIa;-F%?FS$ywL_3)R>H$4X5<5jr-nA$FmR(E!e)Elht z4U0WK8~O#K*P9(5j0gBxg9JLY-FM0K&tfv)l86{0H%g4Sf;|(Hp8YY_zZ&2=$s8Fg8qz?q{vRaHMK+Jc@ zsSUSAX&did+9B=)u#)N!>TMKc_xB(m=a$I~&}gPC?L;@76E5lTVB?QW!1Wzpf$$Z{ zjiWcCe>p-hwSQ^2F}CVksZc|2I-mLflAMt=jp5ArADbM{pds&sf0<0}2Uo>cg9jQU zr!G$2_yDJCvt{UN5>%nh4f%RKvSmAl3L-eZ@%d_Id18g+71~#11r&7j=x1$-hPpU4&+5|5jn7dg2j#ebBKB z)_+*wOviRL3^WJNZ)b=`w6?pI25-D)Miie_8@dV?J55*6Qm%AiT9GifkE~qYu%^a* zgH6|a-zUK+Z1t6c^1DfbKDF~#@O^!Z-B?$+^yYIheMH%(59rSOTq_J)2rp}gJrh;P z7Zg!!tATED-TCF{VD5%o?D>;`bzZ>!yY5}@5jPg<)$*(?#Z{)0FI?9xma3CuI#{c$cgwVL)$>~? zeO1jJ8qoCN-}Td-L%f#|ndb*}#}BrfC(Gv-jZfZ#J=>v)WJg5_WIA(Y6bt)yShz~| zQb}He(3Uc{S@MKN)n+2UMf#U{C+5C z$kB%0+V1lD9jKUHI0IvA!>*Ha74^7X$Du_GCE&du!Mz9_cIy)taM+2j3W$|H+WY@3 zz)kpioS$br)tK1Hh$7rJya5dR)~-LTo4+7md&Rm|VBv3|;W{JY&}0_sJP?eCg^pkB z4a3!7cxM5bXy{Y$hf7xEx^xc`bDKm%W^CzjTS!RGG&T_kiSxZV(wbAQ4-(LRW#no| zdZSRHrxUKvm}dNP?U0vUEO*eM2>kckbEBEWx=-F+$`Vmv81^1PgPZgcHD~<*^{%8;-KX{31YOTFCrlFd=sGvnV8 z49;Fg+jEnJg4J2QWR~`^2_1Z)yYKBWN08Hlo#ObC;(0Ss@03NNz=sAHRfC#1uC7W< zPiSZ{q38p{OQhVNr^eTDwPnK=7PSm#E>DRK^G4eAU10LfF)}Pr!%?LQNs4nML0kE=A>6I^sKQ74r0wM}DeMXu>?N zKfjkr>|uqxdz@5Gw*C(vk;-aIL^#R;+kA>c38{2UDI__Kv3;{OY0q977|p6h$a9$Q z3qMmzDy+od7kItmZz((U4Fp#`t?|{wc$Q)QaqZ1dQt=(MON&o?plKH)ES4MmUeu~ zSz|e@@sy1YHf*EL2@#v-!BT8csk+eKA|UHalZUiN1QVeo)qYW*r($X8>3Iu3o5k_% z(l-L!^$*jp&E-|HwjP+t$k>+geK!J^P3=36J5lp0OVDRARo2B9v1Sa#d7pSR;4l64 z!T^*9sTRG@2$|MvoVTrI=SEkLP;vPTkM+&AwiVo)lO?UcnV;8!7f&?I8A|11D`ObxG%o-R zzQJwV3dZwT##0`a^tatSc6o$k|0d{u3iXLpWqy-K%Vk*lk_ zYU1sYw)xoy5%w^1b0a|iAyP0R^vy>*jPH^(Zh+SO(^=H3Kj*+mjGB~eocjn5MCwt{w#KaF(B8yGN?Q^fRRN?O~?|^lL7% zrMyiU=sOog6ZAO{fk)JCBaE}jbY-jx>0jQVM!NzhA9(}0wrI$=KM&Z30)=F6L8HD@ zMe=eRfR{6JfDW~a1nlFZA58w+hG&079|aFAwP+d zyijAqy{eWZA*aJy+g{X*AEt%0lWoI0)U!z)_!f-s-B_p`qfhkM`C{;t7Z-W80#J)a zGC|w~ExA!c%y>$n{`jX80sfR7ki(h)Q=lj3Eb52Gb7l@O&G1-#WvV1NtMesAiM({n zup0Rt|GnD351@SjLjF*I` ziU!~9HgqwRRcp<8h&)##~&c^a4ih+dp}@P~XVVs&u(4`&!K{ z_o{(%rWdIxh|x}q8MlYK^lV#*O1*B$5aQ_X-$(-jF}IhJ)dD!xhO#WF0hH@_e7ezz zZ8O+Nsjw+3CwX9?S5ZdS%|&!XtTKEDH9?c1 z#3EO6&}L;?np6VusO}q3NgfWe1&XSx^Fv6xIKy$$y<(>42}5W3%#FXte&fY{R|oiJ zKK1z!iBga_i1B)268R=)BI9!1^{%6@iIQN=>Cv5Fr2Q1yRe+8F{MC-GKB^TuoMM=4 ziYTBqHHI0}7Em)W(ZfT-Mfayp&nNw_Q?jXYDY3@zzovv=$>T>49%v<&2^+Yi|BMFM zytzt3b#5CqAd-<2OT|V=vQ<`(pnYfS739y6v@0xJ7;uGer|98|GlrGh`|=T6#Oonpkr}Rn z{7|33Atb}A5@Df90|3|3i(!q!&C*c~4P$#PFi+df1}JVYj5uWVZUY4Alnq~7o3S=< zA$jBwCE`&`wl*K&ncom?xnlR%lWa^K1cK0i*0b3DE$?yP1NPc>_i(o~Kzx)gW~p`{ z*%)a1%b`3ZwZ}CJ*YaLG&$Ibh1-|2twG%RTYtWLCF8~wDDb?R1{W2hp;9#?W=gV;NUzx zf3e24j1QbF%?pP zgx%@N5`PVcJG_KvR!&W77^_d^Su;6by(M6p}ZA$bW*>TP>o z7o0UXYXrXsl@|XgaD6s70+Ns z$J0I3{tZ^!J9pXldcMR7l1CKfGz#;OF|@fP!pd#AZ?84xo^mCb4e^ zKd|O~`-WLlGFyuDLCh=b4ckXWgExwMZfw!9!~c5$d3kPMHOzOC<~dta1G{0>ypTC` z`uA8|+-d>yN|)KXwtlQgmT+HH2aY)%%=G;2{xa#cH%B**C>* zHZfSk*6IC2ft=*%F-Ayp#QFBf8zK)D8;+G^FXOhSb6k&W$9ihyYaLh%&z%f*1kPC) z8!fSfNRh^ON~-?~xE&AtDigG|kWh9o+-OYtHr5I*CHdYr!j;8^5^-Y=Hx;PnH9z=X zfvDmKug=(2PSZXGI@ZiLGUs};Z=c*6b-QIEE0D?V&&Ob=KD5xB8;p2gugrre+!A#E z*ubisKlPvF?w>^Qn*avA*KGa~j)dg@_%|jN+=HR=CLE}5zh6wC*L{A1C(>hSA(|My zbd!of990V!YIX}fV7&4kK0&@EgJOm!3zd=g-W@(5-#`j({ZyG5GnKc^cJ@!nbLC^| zzi}OAp~+hp&<@%=-rSto1PV?>MMtwTjjIm-$95_Ar1}-%ys;OKNF(C`pkEre70{ET zSW}QJz^cx=b#NT%tX;y%h{B@J$MN~BjN?E7LhEJ&9pe=3IQrTOs@ghk-Xo_=Kr{4x zX$feYR@>A8^D&PaEkd4qIafcvr^CZzl-0gq|L15+D3ajCnz4#>FOpmVEn0Xa_G(rHQO?27h4I095Mn zhlMrAA+R8xmylR@B}D7Lwd<=0K&?AOcl}K>&7FKE?-Ei;zPdEROR?m)AWZhCljt6( zI(8Yo)={jG(@i2xgNpQ0uhMIVz>)paH_Vj)KeCN5c`-1< zEOMMaDKYw%t9JRJ$DzQ|%2FB?K5gg!vQmLV#nl@uN-BI`!8aelgmOG%r zfQw}NyW<}gv$pj71|}9lcJyBEY0b@#mPu!K-){g*;oj)eH)>;*Zm(M_v+b$^zjLPV zwW+z9@|0SY@A*CZ+E*-_idBpFDpC>#WK+4lasTWyDkL(s=HhN@^qzMj-MYzyem$!> zz>L5!EgYU ziLfiFRhJtD^*Xxil#KsS%Amj}(C=LQb zM+^s*LA*C@0rz(pl{g@LURQ2!5eNa7zJ*>O4iS4o6xzq?ldEgd)9Jixb}FQHvG)#^ z2jXtoWoh1nZ*(6yfTXtNvKx2*PGbU!U0g;Y{*=e{{P_AeEvVhE7~C4iZNM#)IEgCAmKt!l50P zMlHs#YTT_JQXF#hYBe{bN=8uvTQu7hk;1*ZyWg^4?#ameyhbykmN;wrk~E$V$zz>Y zH|_K7t!|SHzkJ<9)+!U=)2y#upB-2O#v`%CNGYz7+@{UoXY%=a`Dp;O1tRt?=+8C4 zX{T(m;qT|VXihG&ufU|4uElLK%AL~1!bWm!bOj3Yau`-=<*(81yaie@=5#CMp|X-)>=$rVH3~ z`uI!dgr=mN*}=^|z!?u!_VJjIW|xtQf~t7l#IHDUf0jE}nB&-?L4A zl>13j))ct<1wmqm6@o%#gnPp9Mo9lRZFAP@rpxv=rGQefya{Yd&>MsGkCk(}X0!H9 zsmC0wc&d{EZ7)}snF122R4RGljbk-C3+812`dT4}_>~s}T6f9j-0o%2? z`CsYxf1FgIcP5tLduk6#sLM_N6zofFe~LKL{fKla#$7wFzly?{C~(yWp8M}@y34Xb z($ljb?4lKlt>+9{Q^2s+OBAd$o*VRen4t_C9~qqn9c`l$oCeDKe`{bbl8*X=Ja4*r z1x=^SEQnXG5nIfL3fV)y8Y=-~0CNR1R!e0iY`HrRsCm6v+bElMG=@W5*G2v{>^yL+ zp)W!sj~I8&`Ld7B=*zaA+@i2BcP6F<97O!^E-x=nWb0d8UIo#AX4~dFrUlk#Ib5&J z(3luncDCnvVdv?pEh|uGDT^>7Kb>i!`ne>lTzBhTY@1ki5%Q?DZ|tGnR7~Z?PZ|29 z472x#BH4DBAQ&qHa2nF{{-h)H>89-u5kevdBczY;y4~$Bk>EI9HjUm{ z_)@{EJflD2_KsJpKr4R z2nNUj0&4b|X&!g*0)Ld*w6K!gTG+pa=U^0!F+HlSb8wh(o?5r7EkZXpa!c4uhW>bP3e5p`zQdi?Dk6mJa zEnd;^pg5r7Dc&l+CQW-e4`e=rv@&hw*!A`NwIiGv-QR=Wuz5hDU{0XyB#sTkda>|` z_^<6{&)Zlh3rls*aUZ%2?R?9$cK74i?Gxe`8)Zp1Z3eurrlz$Wc<{7<{6en zs#NKhTJID0#P;KZh{f~1VNM{u!?R=1R2gjcD%rZ!APor~k-yX}acLf&FFU<{9R(C5 z&+k~XF>ylV@2l}~nHs-q+^+J?3=S?VhL{@M8``{G{V@dGkZhtgzz{;gFxJQR^18}} z{0Kk1!3z>dDEoV2%Q~GuP%^Z$=cEeyl|IOrL`FE zIRp(NvVOpS*`ua{$W*vJ+>(2b7>lkV$_Ei2Xh2$})%|LPHBf4{PxqMfdAk4-qE+bj4Qq_yF_djZMT zt1Sh52jJ2H^%X(|_%DkR2n9TfSK#U+0LnXk4W9r&`LW==-(E=;R;rKJt)%#Z3qITYH*E?25nAOIuxf-bUVLp1Q1eF51&qse7<2qc$)Xw_qOkg;Ch%+ zz4zeOVF-;y+glwYX{nL`aFP6yFK5_KNZBANIhTlMG-$g zjmF0Jxji~HQJFb@`=97RIx&b6bc;!>sSMyC_Q(H;_vKb4fnSwMDv<9@f9A}h9v%3F z)daa9vq+vV37pzL?r#2zZn((EL4zIcad{IZ^GFx+;n?=+%LS%!kEE}r4&l&e$^rK$ zKLa4z~y8Le7KcyT=dE&6)LwWEtI%z@CjLD*_5pwfBNcv@uQ@p_p+kUYBA3_84L@|X!Lx3uZZM8|+iQI!3;S+Zmlk`6 zAKA|J4)IYuUyxkq^)OU0+r=h+(hX3-p4ZuMB)SU#TCmDJ@OEu{Q z&vtB&@>12nYo+bDqXP4-!*da?HWD}uX~%cJ7T%@h=+nxQxD8Kh}1LeBgY`Rl?w#{v6Sj z(O%;do~+u^!IDrxr+S|NBL^tWA1dy=x4$lHzq2>gQGWhh;7R6sI>jp0+R^=7$-S?4 ztni_OYG^32LsmF}EVbo(y!)zBz^=fGQGr+t4E#eZejl^&bP}d)nx7%7kHSv&>R_@; zT-bdOt=%&GGv$h8eHmO?*P#1)<1{S(r!L8ntnyHz^!K&S*jKo4oNTjck@R%i#_PINW+kYK0XETofC4w2u~;+|c>v=VZ{kUU`>KMzkM!X^1s{YU;DwyRZOL zo_ZzIWh_d`t>bZ5tk10v^UI?uc+GFExRFJZP>d)xoW0MFh^Pky47YJ-lKrPy0{ZOJ z1Blu$Y_sZYIYwmG)XC+vT;XV&-zw?}`28=&9x#G(Bpr~3)XD36+{khm8z*8DOSvqnr96D&4LqN#;-(>BgJ6y5vE(A|#kdZ9-0 zV@?^njS$Y{rz9Vjln%?1jbe%9UP*MA4Pi-OWAALPk#-l zM>M#YP3wqJ{c8F79W>H98Y$7eU~uqU?9C|O{?JtqRXuusFSW@5YLZ%Jc4X5*TryHh zPWcKXpB)b-6+m!T@cWgxehS!YSu}@u?pjk=yXsV)G>aZ$v|PE&oEGik$-pI>Q1p6ln^vf!I!X) zRV3agFJ`ihIWIT7$+0=y9(C)a_UefpGHHcr1%EU-#b)dEjG!#iYz$l#T?+p5 z%D4_J=j+nFxAC7h?tf@H?|(Mmw~ZS`t*BCphN=-;)yGOysa>^K)ZWynm7rE_s;I4q ztv0bo?7jC$5G`s)P&HaDPo5vX|HApY@9VnG^E{6C;pDWMK4~on*y??s~h4`PG7M z7eH311V>(`p@5)#tNn1Qq`DoVI=*Oppz-u)?zHZv(sVz`0WY)8ay-DS<0P6|`?7ih zTy*&HYb;hq47K7MZv5_N$5CIx0nDHwWeji4Z=Vs(=PBp?u02(h3~gTM;CdiorD|Fr zCrPaknf71;^^3DttI+*uEP)r4vl@GG60r`O3oILN=JoOIOFsmlz0B#}L#q!D-lPDZ z1A$nb3SB1nJAF4nD7rlh46bIdjb|#ZGK-XcU)FW^WfE&@s84LPp?S~(gp-NJu(h=^ z9UHBqEH*_(P34mrBF0s_f+Df1du7Z~zOIYE2xqNNXd_&ai>QT=PJqq*w{U9iqn`dvigB419?p4~ z*Mj9eym-M;ZbLMgy;O7ogFNt@S4CAd&#d(d_wE=kUUi*Tw(ReNkV6(R*JW2S;7N$U z4^YG?sg}AtL3GfDkL_?y76Pwhry+l3G6W6ZdSb5m}on zSpR_?>zBkGVOJU5-x{{p08kYRQG?@PUEf-U=pOYIF&Vi}aqATZ(P%F_Nm$#z;XM*)p97xq% zXGx6v;Jub74GV}LtErH1jzD10n7$$gS6c|(&+pxu%fO~p@T9zJi}Qe_{u>_5?E3sV z;Wt$hP3Uh9MfL-6bq{+I=DcQ!fXnp6$%w_Ip_Mmwz+RD>Yorj4gkJOrr-&?qX)7e< z3f?b>Ye&SCeK#}v&qC@qrQ^FV$1$Good5EZ#$hS*_%Uhv3tF#7Y#KA`2VbLBJoMj@ z&edS&@V-??37Y4`hYP#gm~Df%zr8ll5q}=eBKu>olOR?f?qm6P&YPk2Y>&+y)poHv zK0_w%$&w%OZ5xk@+yQb*E;jeaVIN^2t&SdN=v&R18}M@};wo|LO`A!|B;?uXY-~J! z0NjUnSC%5H@75A3)1^j>ZAq`i=@fu~*;~7mg^JQ!-q05^61W`X%7?v9j;`Qf+ q) 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 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@2x.png b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6f133967da3f2472bc365f109e739f0fa539047f GIT binary patch literal 48223 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೐*&esCW8ThO{IiSA#`nIIA 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 Ia6RdN0Jnb=)c^nh literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@3x.png b/assets/multiplatform/resources/MR/images/card_create_your_public_address_alpha_light@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..38970844b7a9bbe6b78cf243ee3cf1401cb723d0 GIT binary patch literal 101981 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\( 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 z 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 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@2x.png b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3d54b2c50743484181ffdd3e1ebd1e1ba08b95c5 GIT binary patch literal 28671 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

j>VG2_kewrrJ7^#DpGs#XS>E!C;YKFw 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 F{{S21Gn4=T literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@2x.png b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..329fc8d6c451036f2c8e3035436d22ee028c8db2 GIT binary patch literal 27441 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 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@3x.png b/assets/multiplatform/resources/MR/images/card_invite_someone_privately_alpha_light@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..99fb7a45d6e07d34dcbe0b7eb28e1c2b3d5b2891 GIT binary patch literal 53719 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

-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 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/banner_paste_link_light@2x.png b/assets/multiplatform/resources/MR/images/banner_paste_link_light@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c34e98888636d6f0270f3d70cb5d33a01d37a438 GIT binary patch literal 20450 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 literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/banner_paste_link_light@3x.png b/assets/multiplatform/resources/MR/images/banner_paste_link_light@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9d813e2c8eb3bfc38a281f1976a18a77b7a847f5 GIT binary patch literal 49027 zcmV(|K+(U6P);|{#{C;|4MGY9smDQd&Qss{~uWHRDQ?*noB~G|47mQOUnQM zyG|_r|NqERMx6ius7zXh%S77$T#L>B(O3W5RYBkX|E@|`gUT&({wHhyO~wB$Ka2mL zPDa)LPp|(n_y0PF|NfCn|IJbVv`zo_TvfCG|KL?hrvFl@|087mKaKx4g8xvv{~b`~ z|Np^1<^LaE_A`3_|M6Q^fXM%&N=BFe|KwT!)KdTIS6`6N|H4f=?*F7?S(#f`DR;mB z*H|co!ZPUpr+9X=uCfy=eW;3!tDT|$-d>$*Y%6NS{=Zo**Z(-e{~9}(CRMiBluqHT zRTeUfAxp0wL!>F+|2I;n*@j3l&;P!_%PU^P-Sz)HxBnSQ-zSm9C-DEg(CXKsSBg+n z@A>}!??`1?bnA>(?YvsmdPMZdXh{739Zk&s{i-lczh#!t{_1%C(~ST1-7IPAivIp$ zb)na}Xe3YPn;W_wUxlKbMbhI)o%XN+7+XCGyy4jfxZREaxDis9w#x!?T%`|zL6 z`~LC7zViRt(7nfoW0U6l{pOD)FJPqb|2>}9!E#G#?f(Anrt_;+dH(-$!{>*${CtY8 zF+O@UuGlQa~Q*dPjfJMpP<0!JqdhAe`V$JE6>Fz&&wjui!v{YqAZGO^%C#uOV)g9e&_N-YDQ~Oy-BUTT9MkU zHg1hKW4BJKH&bV%mS;<*wtih~FHoI|Qs27HncDHSuijoA_6JfoH{zq~`sUiJOTMle z>$b5ziC1HFqF7Xf%4aJTFY%Ti375nx(?6L+OTd)@Sn8A8DyvT+Ubv=H0L;|fXKUUF zm*9&MwpvrTst+b?yz$u@^K1cKCzh@=A1#h90+;6Nw1uzTIYw$Pv4X#ta_B;A@pNIl zuH;+&>*~rQ>{hB^OOO{9R8)vJ(S8n9`hWUPs=nWtSfbS)ax%agD1Tluo;wG2|Pi|sq;I`>3N5+=Ue z;5r-}x^4ocOOWO37oY{CIfNO!G+G{C60AY2WedV+y?yz2w$o> z;?9^OxRiWVJo#*icY)xVy6QD=oZH-=Etifx{u|@deTjP?^uZzE`UOS^)HGJ)x z_>zn@s~7&`ijyz>X4LD}K;?*6M^H5YvMRP>tzb@bj9l9V#tZr)_Ff=f8FF4ZD3>G* z;3|~=DxBUZO8w5|$HmL@U5gndi}7A7w|wgrs9tBj^*Zy|V(m@8Wnut@gC)P>71mS(s$Dzu2R*VbMF zR!Zw7?0voUnzOxe-h>xxyt|&a0iScD!nO3@ey5UXrd- z<`YFe)#62sJrhdIJ<~Pw#B0$!IR^H|ahr5;fNj;Q8Lo@=wkXJ|>5Z)M+SmIo#@M@n z*~#&epBtM21F)blT>}D^)-63Xs<(1p5!Nn67VeOWuZiuAnRZ{9O1xaUL}yfEV{V<7 zq6<5FBwyaxxZrF(g=^({ufHZOfp zaWUhJm+$RqZ(R9vW1aPCCQO+65V|;P?*Vpx8k(twB@?Z9`NqDnJKp-KL}%FVOSV9> z6kez|#?T#&yN^J=Vv^S`|s#$(D@06kItbOgBXh7$M9} zC0gn78Y7oX1P_ihmk@9T#Htld+s58l!4(jDDfwa(dtbh8T0&Lda2a>+;dP6{RS8zV z0!y&fACi)yg?gi=4B(11VaRwvW*=eel|uTZAw0H@=_e9kza&_? zC|z2!ta@o{Z>L5>mK#mjxiPgrR>9njP?ljwpQBJUhw!U_qF*`_*FXYgR(<%Dn2^Bhwh0VlTcoUNhe7>WXcR0A_3AOd9!JE5jnb;E`B zA!8%Q7Vqp^)Qhq9=UjL62gf~cytquYQNJ#E^{VNPosq0Q!Aq%^R50Ydl*dP7BZ|GS zyC<#D$?R())eO^>P0-CmPOVp==_+YxuN=J_tG*d7Hhq+jvCO(oPqND~$(wlhXp zznoYi^9x%q!k2aUrPvA?PmeTRw zYhRy$cu8NM>JB-Xz&CPWHjb!%q;Bcd3k9n9{mYG&NpzdQ};?lysrS2z)8F`p9?b)`-l#LTg}z3!8g%=tZ2D zL<`*!X0X!Mc|;1AUKu&XA(@1p9VK2R-8X`O3FGC4s!Z<1U*a^};)F%2HP;e;s2XCt zBw%gM{XKk7wh&p8E@{1}^O7Rwg%XOe;UFIB*h_C7LAdBf z$h%W@qIx4k-_9yRi_S@z19j9LB zthHxPCD>HL0qo|`s9l7vPPg0DqSZ5HJ7eE`9O-g=y)s}Xv^Tn?9azN7jY7h7SB&7e zULV;f_zc>~B~&VrSx=wS%)Zo(F~KUZx`CH#%hx;?b_S3*!5bz9%S*ICG+YRj#} zDc>ADymoGT+&48xCX_fF8dFu#9R*mjdR@$w>5n}SEjv4o{8T~{rrf-ns19%_5y9N3 zC&$EO5|K6b5PMl$uaBU;!4df`uQ8S~?^3R-ge%p;X^rg6=pWh78e3xvUgOPrb8@m- zuesD~+~&Q`YUakqbBWU+@jBntMD}5as?ArbQn9+WDxA9VEmcdsWpC_3SA<+*G(ytl zZ-j<6mC%_4-8GtviI@rOBb<39{xaK27hjozO8*`^Ox|d#4-geF35PH=|_Uq;A z-P6rhE5>@hu(4>BPEWmrI+0on)7UWEI>HKv%-8GPmmeQL{`2FHm)Ff|T)Po!@m|Yi-JR;vfh*t+y8YN|Uz6gF^)(lriDdo&i2O{3BDGK)_wL0-9fL{+xw@N=l^{8{`F&354*)`+>lGK zt#`)XMB`U0{ zF8K2N{U0BG{{H<}LDrAE<)StB3b=!=J0gz2_N@cRfJcbYxHY5?~3o1XD)_EW;NPubvqj6<5Z4 zjYhn7jZ9}8>glm>x7jGR*KpBGFTD^lc*WRuMNX`W0@c)a_oOkJ=4iK$@=moZO0e~K z=?D#ST62ywulHWB_m34xO7+9jIlFDF%~V@fLdzPuwtv&p*>#aUYrBB2 z&e2atvLsdtGAmtLxQ>x}X=4wO1mqIBH};fbF{9(qTzk<_V(6H9r#I?{&&Wj%>5O`x zjn_e`+ZnO-%DvpHl+LS^Q={EyWBXNb|Fy)-O})_EIDNf)eEsA1r-v6&-zZw&?#DBU zmRbj`-u?XS8SRdN|8dE-?F^b*H~PlDDY8FwfU6^em*UFM1@6mQ`>wY3J>P)Qwm5=t z0>h+9&`v5rkL=Ymw59v>EP9e-PGYYa ze=xC_ojg^7^~cNe^XJbm4-XID{!7t%d0ovmB$x<$0xjV2>?M0l*mkDMrV@N_Jm=Th zl&{WDvW|}A7Y0nRg}P%G%okWN4VcNjx-c`U!BPFuH^C}hWRvJ?fKq#VtQuRy1~pG- z>&G~v2wg#^Ns>5ss;T3?CiW?z91V?RmtC5jklHkQfHD6!4gzcX`t;}H%eUv}=ih$$ z<+lp07Z;3sNO(Y=n+CAEke_>q}IlgjF2n| zC6XZ-#|$RVFs~qHnS};|_hvK?5DebUBAY-!WB9zu%xd@uhHvFfR^Cnb{i$13w{Caa zBmX|Yq3YaIr%qLqB|et=R4XwrpL-c#<*-ul;^0b5hs3-KG}She?PE3*0@>b;vpd-A zs2r)0=fIS)!@fxCW5R8OIu;PdUaj3F7b!IOdyM#9!*E6%fp;l*SFcb3j z|LN1wLhJti-?w)et{!%~-S+aJpv9@y+vRE_>^HuvWF(X|uNOL1JuA+~|Houcfn_U~ z0T^J)F#99I?Au4AHL9Lyn)rF4a7+>SKb+u7NuXNEhe^nwb9d&a4${n z+lX`_<`rw=MVwxJL%b}uI*!yE-ZjAc+1)PM`v={ANau|``f>zeVmxBeacDi85-&Q^ zUh5r4vGnJSXGKdw)tJ|XFwL$c zO1v+ZRK?gOUcG8{0apZK9=ehvk$JVTULWI$qm@*RSt16fSBETd^qn0CxuF4L1MHR{ z6Gv|%)bo~S#hJD**;`^i!;yqjYf+t=)keeZ=WANL)^3Gk@$}nY?jQbno8bz8l|hS9 zE!7Wi|D5&1VvBv)jl&}^%X4vFLcE@XFAS`Dj;(sjM;I$&4qW=t$QvXRRe{#G+uq9& zT=uZX!3aGWX`@k)FwI7}I3B*3j}T9UtOuIzXOC@T91}SLDPKHxX)dhKE|)p5a#&^G zTL9LjrdmHa)w-DFCPLqj!-#z~mujVSiI;7lRWeIh=B0s^fl47uL#$Pokd=eW{v@_5 zCQMbe??u}o5$lTaAX}4rp!ddygBql7#GzwHY*qC{Rft_0lIGZRD~>c!)vBtak&BEQ zlf+&urp|0;@6F5Vwm8n<&EtzVa&54b$$V%vgOF-DE?1H|yDDy$TfB&{T z7pGFRgauX|0H!eJgDZhHNklKx7m=#Uc)gi7>e}THmf~?^rz4pN(E=?nur6~Fx-AX^ zFf+&|E>&Zdz{5slgeIE3CH5ooc4XrqG7?fQ95CA3XQKv|f))iWgV*@Tvg(NCfQ!V>_JvFS*k8w!j80U^~!&F73&g1OOPJEUw@oQ zR>IY)@_u7cplWIHDiFQi27{MAd??rvs%5xp=06NvE%T=(GcOFS7E~{B;7FKPOg+#s z@jyds)TEwj_L|UlyrB?ET>^sN^|m+$Jo9o0mM{HcM2V_n=YyUy6ZDv-;*j#CtT>j} z+lrL~>xxvY5Y_szpUlJ!q4G3)G!vSHG}Y58iK-VBsaN^H@k0iz>{D+lV0pC*P?iHs ztX>#fXy$mtAclQ7j(NotWOnT6T+pe!fyV1s7jJNwp6CYKbmYj)ggQw**nRe_$A0RyTi;lgm>X&RX51S7-Q|x zrQ(cHrYcUxncD3}6Wcp;s)QQ|@ruQ9mG#3P;*@7PUzH@|6fbdXDym+Smzi-&*JoY* z%77I`k%E*0med#B!?9JSNZ2Q%$Gn`ZYDPk6*w~KgK!`GXo-L8G(uH@=y2zG;WQi_X z&>r-pI~;BW&4qf6XqLU32${SRHxB~K1O!R|u=dwi23Q`n1giD(`eMASaw_3Dk%WqR zc^H#zggi4&qV}a`91W}}$5qs1pL(b(B55Lku2$k1CM>kD8;4-sa=W_L{P2iOpr&lV?gLG@`AY$M30()^bPM4VMX>aWHxCt71M6b!D%8jb>{aFgE28Nh{Rpff3 z=J+APqDmC|uG-Eb5t7Yi=wV#7pmhqfo(dhv8sR=il6Y6h%t zycMcmD1l3!@~obgOWzA!C6aK(G6~NL((EVUV4TT|QV9WGwML^6H5wAIfO&cF(z+Md zCQRW9z{@p7s@WeUo?cIkgl+4@k#yBK^_;b)Lsx{< zB_WolI>zB8`G)~(XK*FgtiY7#*Lb=VKmNG{sW+~kb4MtUd3h=cUls0y=J2YWHyRpS zjYbswG|e~WU!tViLes`pklRDeN)lvIO(?TJ;)<$ik!@&-WT4%_)XNJlVX?P>JY4&y&G-Ma5vM&X!eGV(!3IokBK(!uk-#*P2VxnrL1CGM>f6>5kQsBlZ zHbKi~iQ0MgQP|SIJ$yA?Ym6|gtn$@t1{Kvp9}Y7?+Z`chQZ=nHmPSHtG7xeeoRsGE zrpSy#*Ms`JEjmH9Yh;NY3YQqTR4*_OBEJN-s!lbq_E52Qwq%9MmT0KiZ=RMISyDO< z1J~jisa}@~8&vRAlxd73H|FO4!Y4DzX0n^mvWCthhs+;8l3a0P?X)hAXjbczEC*8pvCup3pb zcv)aw?=xV1&MTIoSplh3p7Lba`gFaXF4r{HYb{=ItdKT-c2=D0RdIi|(xrrptYSH6 z8F)3!AKM){x|;6QC@A*LAi!FM@Je`tOt49nOoWcG!=4r|MCuvDI@C$%moKZ;_oYxA zwthAUy%{ya#-nnb;jcQMfmoX|#;Xft+C(OBvO=%f9k{m?3MvhU_?oFd1FTWtm_v z7`=+`gNps}w4gcGEUI!0acC)UAz(Gc)`+H!5j9FQi(6v>0b`QHk;NBbUd&Dyx5<8X zo7la~@;^&0Nzo36-fMKg5>hX9hwO>T5~x4+hNCdBiWDtFwH`8P-MMafYp&3e18D2A zD?$rz`+v1~p3#flK$}b?be)E-R2g8|s&%d)Rs^s}>Wxh*t2QJ0c&z)Pmwhz?vOJ7= z%h@Jcf{7ADQH?>Gh=~iDM#g3TLgrFR*RyT)>WXof{015gmq7eW18Z-9^?A25(As_d zx^ptTdLzP9aZ9Kc=J3}~b9>$RhF);wdC<#w*>U#rGeVm(>J?DE*hr}M`Z)V?xYB+b{d2>XdSgrh$!LmOYq`9cj92h%XP&xFl+4c zJt5ejwf7sl`1q`@U#e=2fMfmr`m?QA+3%~vkdB<(YGP?svq8gp7r?nD?=7G zs;W&6RKIxfDiiFbNa(Xwk?K|Xf+IjHuUN;&u4Ww@vgH>o&FKMU4JX+*1TmEfUog%V zy@b}LS`QE~R=X0udX}K}NLiP>{IB$gn8ZOl$*WH!`;NaN)OThHc1Xx^dtI^eg0=sw zk)>dDck=4w_q#_V#XEURqe}nI`{7T;-7{nz{`lAF@CNyyjZ-y`B-GJfb95hc9lEdQ z=RwO(uT{|N^{(3XzY^?EYsdD3OPA}724k;<)_5LNRhzgma!XFVSd<7CutZv*P51(> zH*k#OwvdYxr^rtj3nu73bjLpKj$N|+kBs&NT*EKA6=oGHZ-rL|SHHhHdG+e#>e5n99WTG%C|L>5LbnpkvymaJ z_U6qS3oZMPgf3{3P`w)F-B|Kyj9g=ky#5#{T?Ixj=EUKmeIhRjxkSPky9rao>#0*B zW!xU?ULZ+mDYcI_glqvDeZdv$$^h$U4yx>vfeVTjX7pQ(tsfb(46F`^fBbs7f!#Ax zy6lqBdhK$TAatA@XV-9|hpK$qRc2K!-f#4L(xzgUjGcAuA(VkKZ2;kJ}tr_luEDjl)}LkVkj}hlJeWY@$pog?Xc(*jI`h z53L9hgyj`0{~RN9X?$sH9Ru&O>|W+yc-j~tzXbE)L`}~JT`zD#SJ3RqhL9s)2}9SB zXH=z+$5iq_6PtaX6t0dF>|u|?(XQT?{}^EXkFv98Z8OW_xW9p(LSJBoB&w2Q5=wPa4pmzY%w!ndR(m zHz*n!M;07sy&PY@{G0~I3|nHQq>g~81XyYDl3y8L=wh@Nq&8N=3TEDo39a(VraPe9T6?)<85lxlYCS!5XhETX0;c&$gWC^h3HCVh~E*}(M z#U&<7J6*&`{c?7Cx*(E8mk0%BdfAsFXqOAU>|8IhKKm)p>oe}g2wes&4VU^Tfov(V zQXJM-;pN!0cp|iSvI{~Xoqceasb9fip#}EF~20;mAxmdilnBjn;bMfxUH9iR0V#*t1_ZX~*F9Y}$h6 zE7nLWR&lO=7(PIl1uWU`3}*_glhgBS`~^W*001BWNkl8PZu zoFozT%EMol%hnfTJh+d0_BC9MQ?J+1wB8!4B(&8Mw0*Wh4KGi+m}!q4)eQmGHwLVU zQmoO*5WtczL`~LmgptBW{hO97IKOSRX_-A+me59sjXvqDYE7xC+>E~dBjgpo4U za^5Nzw(C(f4wbM~6ny2Pz5m`g)mj~u1+r`!wtUiOt{-Edi5>J*vN7lSp zzE&Cqd?H-ws}bRqx@Ti*Jyn(zI;uv(E_gPYC!@(XCM+M6x79dhPrq_eQ#A@#z>|P$ zu@U9^?la!FjXRpDqOSjn=zOh%~nF4Dv^c2hFcOD>%h|h`sP-uyVpKWpp*2NC?6=($lZ#&a3RiQa{bemn+CnP@3wH5v8sIRQpe3y zg}AGYN7;AK*xsHF@cCx;L;E#byF%~Am4-Y5E=Cu~R}d}nsR?h#Cb}IVC1eXFsHbXu zFH9GYy&|1w0l18EJ;u8Y~4@L(#?M%Sl*3x=d)FvNvPZ<%OzMFSxH(!_P)ksj6*> z#4odVh0wSbCo3Ve`t&U5y4`=~l8rSs*^9I|R$yjah1Yw*%(!n2SW>b2@+ur+#RA8n z_-vsZuB9>K@0SCNmiWCl+$u~@mPaJcD>jILM<|r5T?epTF8??>eYn>jwB-l zOC0eMAsc%2;-S}z@a^cpLZ=Wo0Y?IR_7He!0fYFfYEnWhvag0JaX`QrVbOec`Kg<6 z)j1Nmz;uZkd@$#$ZUZnfk+2@_5en_7T3ZufeeD#hKNMUI2gt{oi66D$Ab*tMmZ^J~ zPpD{}OwR7Ej<3X#U_l?p4jUP_$%ikY23j&~oZ0qWjMo$rLh4@ZnsKTJJ%8MI0I(Mx zVIEioqh5tDT_FK8Y?WN5F}0Ut)fM94mm^qSRqER77NNvEA5GhTlEgbW%Klid>}s6P zeK!VgN0b<<+t7=)pqYs!3@j;G2&~!teCfc_4@6V-a6cOiC5Lc^XLq-ftK;$U&HBDA zhh{C>bp7l;nJ&?Gvn8a2p;6;)Sd@Uaph4Kx1LMo4gvkEJNSQr?Yh_O!4i*L%f;p$-o0*XOcJaaR4Agm^5WSBL{+aTH(MPO;7g5-f5RYTI39 zq95dsa|4zMv}XGEC0RqOSQ4!DO)~J34qb9vILzP@t6yYCm0g&iCwsM6VBf{-XVvXE z)pa=Fs7}2q`@?XH;fUX*;s+41na!dz#6K%&s1eC#l>&y=A$<%@(|Hv5p4sjlqq?(WHt zJs&Rl$wN#s6^y?0lhK1Lg^HXeB8-=-R-3FECsgQ_5NCq)H!7-f`FuwIY=*14`b6Mm z*y00B{Bh&6K#83wZk+1|ZLPzC%lK$_n|9SlH zUZ+VuP-pLtF2Q8wea}8vP#EgrAm*xjIBsLbGGGlS4*-@7xD-w5@BK5UQiHcD{u1&f z{*RC_rC66zv%b2zS+fQ9W^~*R+JUyghnIxL*<0mm*~<}?Ou%mTmaCT_OJ1NBr^6nO zy-&m|pN;OkFzLmpqbf(b+6FAW)8`G7R;<%P32$+|Vl4)s;}JEw(SgjR4raRtJ3<{?n^?YZl^0#a(RS%< zFRY)EC40ENQINIrTpkRUg=|lK3IG;%B|;fTRp=FaI@;S2M_nnAFTYxXxBjQ;hu_}6 z|M<^~i;G`gefsp{<4u~D%e)OOk4BA`ah*gy!}D7Gyo3;KaMVXA*5&tFu+GMB#}jlH zJ}8@NH0nQ?o*n(AxOgjnX`m#lct6o(X|OIYZ{=NhQ^7NrHgJa{pGmlBxPqZ^6smr7_m#E9ty zTs1vQ!d#1K>F3{0-~au)i!Z*oxcKguR|>5crE0!hjuw06V|zIAPh=c(vBv*Z`(&)t z1�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 LNkvXXu0mjfi&L^s literal 0 HcmV?d00001 diff --git a/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@2x.png b/assets/multiplatform/resources/MR/images/card_connect_via_link_alpha@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3f53f89dd6321c37335ad34a5855b4acfde9a6eb GIT binary patch literal 29205 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 z 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