mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-08 06:56:10 +00:00
Merge 72560f56f6010040c4c9c3d01bd224b387e1268c into 1f59e814f90c3f489f48d68262cb1bf640bf6181
This commit is contained in:
commit
30e88a7a02
File diff suppressed because it is too large
Load Diff
63
bitchat/Services/Gateway/BridgeStatusSummary.swift
Normal file
63
bitchat/Services/Gateway/BridgeStatusSummary.swift
Normal file
@ -0,0 +1,63 @@
|
||||
//
|
||||
// BridgeStatusSummary.swift
|
||||
// bitchat
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// One-line mesh bridge status for the settings pane.
|
||||
enum BridgeStatusSummary {
|
||||
static func formatted(
|
||||
enabled: Bool,
|
||||
cell: String?,
|
||||
bridgedCount: Int,
|
||||
nearbyOnly: Bool
|
||||
) -> String {
|
||||
if !enabled {
|
||||
return String(
|
||||
localized: "app_info.settings.bridge.status.off",
|
||||
defaultValue: "bridge off — only radio-range mesh traffic",
|
||||
comment: "Bridge status line when the mesh bridge toggle is off"
|
||||
)
|
||||
}
|
||||
let cellPart = cell.map {
|
||||
String(
|
||||
format: String(
|
||||
localized: "app_info.settings.bridge.status.cell",
|
||||
defaultValue: "rendezvous #%@",
|
||||
comment: "Bridge status fragment showing rendezvous cell; %@ is geohash"
|
||||
),
|
||||
locale: .current,
|
||||
$0
|
||||
)
|
||||
} ?? String(
|
||||
localized: "app_info.settings.bridge.status.no_cell",
|
||||
defaultValue: "no rendezvous cell",
|
||||
comment: "Bridge status fragment when no cell is active"
|
||||
)
|
||||
let peoplePart = String(
|
||||
format: String(
|
||||
localized: "app_info.settings.bridge.status.people",
|
||||
defaultValue: "%lld people via bridge",
|
||||
comment: "Bridge status fragment counting bridged participants; %lld is count"
|
||||
),
|
||||
locale: .current,
|
||||
bridgedCount
|
||||
)
|
||||
let composePart = nearbyOnly
|
||||
? String(
|
||||
localized: "app_info.settings.bridge.status.compose_nearby",
|
||||
defaultValue: "compose: nearby only",
|
||||
comment: "Bridge status fragment when outgoing mesh messages stay on radio"
|
||||
)
|
||||
: String(
|
||||
localized: "app_info.settings.bridge.status.compose_bridged",
|
||||
defaultValue: "compose: bridged",
|
||||
comment: "Bridge status fragment when outgoing mesh messages cross the bridge"
|
||||
)
|
||||
return [cellPart, peoplePart, composePart].joined(separator: " · ")
|
||||
}
|
||||
}
|
||||
@ -78,6 +78,14 @@ struct AppInfoView: View {
|
||||
)
|
||||
}
|
||||
static let bridgeNoCell = String(localized: "app_info.settings.bridge.no_cell", defaultValue: "no rendezvous cell yet — needs location access or a nearby bridge peer", comment: "Caption under the mesh bridge toggle when the bridge is on but has no geohash cell to meet on")
|
||||
static func bridgeStatusSummary(enabled: Bool, cell: String?, bridgedCount: Int, nearbyOnly: Bool) -> String {
|
||||
BridgeStatusSummary.formatted(
|
||||
enabled: enabled,
|
||||
cell: cell,
|
||||
bridgedCount: bridgedCount,
|
||||
nearbyOnly: nearbyOnly
|
||||
)
|
||||
}
|
||||
|
||||
// Moved from LocationChannelsSheet; keys unchanged. (The former
|
||||
// internet-gateway toggle is gone: the bridge switch drives all
|
||||
@ -440,6 +448,17 @@ struct AppInfoView: View {
|
||||
.bitchatFont(size: 11)
|
||||
.foregroundColor(secondaryTextColor)
|
||||
}
|
||||
Text(
|
||||
verbatim: Strings.Settings.bridgeStatusSummary(
|
||||
enabled: bridgeService.isEnabled,
|
||||
cell: bridgeService.activeCell,
|
||||
bridgedCount: bridgeService.bridgedPeerCount,
|
||||
nearbyOnly: bridgeService.nearbyOnly
|
||||
)
|
||||
)
|
||||
.bitchatFont(size: 11)
|
||||
.foregroundColor(secondaryTextColor)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
settingsCard {
|
||||
|
||||
28
bitchatTests/Services/BridgeStatusSummaryTests.swift
Normal file
28
bitchatTests/Services/BridgeStatusSummaryTests.swift
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// BridgeStatusSummaryTests.swift
|
||||
// bitchatTests
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct BridgeStatusSummaryTests {
|
||||
@Test func offStateDoesNotMentionBridgePeople() {
|
||||
let text = BridgeStatusSummary.formatted(enabled: false, cell: "u4pruy", bridgedCount: 3, nearbyOnly: false)
|
||||
#expect(text.lowercased().contains("bridge off"))
|
||||
}
|
||||
|
||||
@Test func enabledStateIncludesCellAndCount() {
|
||||
let text = BridgeStatusSummary.formatted(enabled: true, cell: "u4pruy", bridgedCount: 2, nearbyOnly: true)
|
||||
#expect(text.contains("u4pruy"))
|
||||
#expect(text.contains("nearby"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test func enabledWithZeroPeersMentionsBridgeCell() {
|
||||
let text = BridgeStatusSummary.formatted(enabled: true, cell: "u4pruy", bridgedCount: 0, nearbyOnly: false)
|
||||
#expect(text.localizedCaseInsensitiveContains("u4pruy") || text.localizedCaseInsensitiveContains("bridge"))
|
||||
}
|
||||
10
docs/BRIDGE-STATUS-APP-INFO.md
Normal file
10
docs/BRIDGE-STATUS-APP-INFO.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Bridge status in settings
|
||||
|
||||
The mesh bridge settings card now includes a one-line **status summary**:
|
||||
|
||||
- Whether the bridge is on or off
|
||||
- Active rendezvous cell (when known)
|
||||
- Count of people visible via the bridge
|
||||
- Current compose scope (nearby-only vs bridged)
|
||||
|
||||
This makes bridge behavior discoverable without opening the people sheet or guessing from header icons.
|
||||
53
scripts/add_localizable_key.py
Normal file
53
scripts/add_localizable_key.py
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add localization keys to Localizable.xcstrings with en + needs_review placeholders."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
LOCALES = [
|
||||
"ar", "bn", "de", "en", "es", "fa", "fil", "fr", "he", "hi", "id", "it",
|
||||
"ja", "ko", "ms", "ne", "nl", "pl", "pt", "pt-BR", "ru", "sv", "ta", "th",
|
||||
"tr", "uk", "ur", "vi", "zh-Hans", "zh-Hant",
|
||||
]
|
||||
|
||||
XCSTRINGS = Path(__file__).resolve().parents[1] / "bitchat" / "Localizable.xcstrings"
|
||||
|
||||
|
||||
def add_key(key: str, en_value: str, comment: str) -> None:
|
||||
with XCSTRINGS.open(encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
if key in data["strings"]:
|
||||
print(f"skip existing: {key}", file=sys.stderr)
|
||||
return
|
||||
|
||||
localizations = {}
|
||||
for locale in LOCALES:
|
||||
state = "translated" if locale == "en" else "needs_review"
|
||||
localizations[locale] = {"stringUnit": {"state": state, "value": en_value}}
|
||||
|
||||
data["strings"][key] = {
|
||||
"comment": comment,
|
||||
"extractionState": "manual",
|
||||
"localizations": localizations,
|
||||
}
|
||||
|
||||
with XCSTRINGS.open("w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
print(f"added: {key}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) < 3:
|
||||
print("usage: add_localizable_key.py <key> <en_value> [comment]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
add_key(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user