mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-24 22:25:18 +00:00
Periphery 3.7.4 audit of both schemes (macOS + iOS, intersected so platform-specific code is never touched), with test targets indexed and the share extension built. 277 dead declarations removed or demoted: dead forwarding wrappers (ChatViewModel+Nostr/+PrivateChat), removed- feature remnants (autocomplete command suggestions, back-swipe tuning, MediaSendError, GeohashParticipantTracker), unused Tor dormancy bindings, assign-only properties, unused parameters (renamed to _), and redundant public accessibility. 13 orphaned localization keys deleted across all 29 locales (old pre-#1392 location-notes UI, app_info warnings). Two real tests were flagged as unused because they never ran: Swift Testing methods missing @Test (NostrProtocolTests. testAckRoundTripNIP44V2_Delivered, NotificationStreamAssemblerTests. testAssemblesCompressedLargeFrame). Re-armed both; they pass. Deliberately kept, now recorded in .periphery.baseline.json: iOS-only code invisible to the CI macOS scan, C FFI signatures, keep-alive NWPathMonitor reference, InboundEventKey.eventID (dedup semantics), wifiBulk capability bit (reserved for Wi-Fi bulk work, used by BitFoundation package tests), and the String secureClear cluster (exercised by package tests). New: .periphery.yml config and an advisory Dead Code CI job (mirrors the SwiftLint precedent from #1361) that fails on findings not in the committed baseline. Verified: full macOS app suite, BitFoundation (119) and BitLogger (13) package tests green; periphery scan --strict exits clean. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
73 lines
3.5 KiB
Swift
73 lines
3.5 KiB
Swift
import Testing
|
|
import Foundation
|
|
|
|
/// Guards against locale gaps in the string catalogs: every translatable key
|
|
/// must have a localization for every supported locale, so no user ever sees
|
|
/// an English fallback (see PR #1391 review).
|
|
struct LocalizationCoverageTests {
|
|
private static let repoRoot = URL(fileURLWithPath: #filePath)
|
|
.deletingLastPathComponent() // bitchatTests
|
|
.deletingLastPathComponent() // repo root
|
|
|
|
private struct Catalog {
|
|
/// key -> set of locales with a localization entry
|
|
let coverage: [String: Set<String>]
|
|
/// all locales appearing anywhere in the catalog
|
|
var allLocales: Set<String> { coverage.values.reduce(into: []) { $0.formUnion($1) } }
|
|
}
|
|
|
|
private static func loadCatalog(_ relativePath: String) throws -> Catalog {
|
|
let url = repoRoot.appendingPathComponent(relativePath)
|
|
let data = try Data(contentsOf: url)
|
|
let root = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any])
|
|
let strings = try #require(root["strings"] as? [String: Any])
|
|
|
|
var coverage: [String: Set<String>] = [:]
|
|
for (key, value) in strings {
|
|
guard let entry = value as? [String: Any] else { continue }
|
|
if entry["shouldTranslate"] as? Bool == false { continue }
|
|
let localizations = entry["localizations"] as? [String: Any] ?? [:]
|
|
var locales: Set<String> = []
|
|
for (locale, loc) in localizations {
|
|
guard let loc = loc as? [String: Any] else { continue }
|
|
// A localization counts if it has a non-empty stringUnit value
|
|
// or uses variations/substitutions (plural forms).
|
|
if let unit = loc["stringUnit"] as? [String: Any],
|
|
let unitValue = unit["value"] as? String, !unitValue.isEmpty {
|
|
locales.insert(locale)
|
|
} else if loc["variations"] != nil || loc["substitutions"] != nil {
|
|
locales.insert(locale)
|
|
}
|
|
}
|
|
coverage[key] = locales
|
|
}
|
|
return Catalog(coverage: coverage)
|
|
}
|
|
|
|
@Test func mainCatalogCoversAllLocalesForEveryKey() throws {
|
|
let catalog = try Self.loadCatalog("bitchat/Localizable.xcstrings")
|
|
let expected = catalog.allLocales
|
|
#expect(expected.count > 1, "catalog should declare more locales than the source language")
|
|
for (key, locales) in catalog.coverage.sorted(by: { $0.key < $1.key }) {
|
|
let missing = expected.subtracting(locales).sorted()
|
|
#expect(missing.isEmpty, "\(key) is missing locales: \(missing.joined(separator: ", "))")
|
|
}
|
|
}
|
|
|
|
@Test func shareExtensionCatalogCoversAllLocalesForEveryKey() throws {
|
|
let catalog = try Self.loadCatalog("bitchatShareExtension/Localization/Localizable.xcstrings")
|
|
let expected = catalog.allLocales
|
|
for (key, locales) in catalog.coverage.sorted(by: { $0.key < $1.key }) {
|
|
let missing = expected.subtracting(locales).sorted()
|
|
#expect(missing.isEmpty, "\(key) is missing locales: \(missing.joined(separator: ", "))")
|
|
}
|
|
}
|
|
|
|
@Test func shareExtensionSupportsSameLocalesAsMainApp() throws {
|
|
let main = try Self.loadCatalog("bitchat/Localizable.xcstrings")
|
|
let shareExt = try Self.loadCatalog("bitchatShareExtension/Localization/Localizable.xcstrings")
|
|
let missing = main.allLocales.subtracting(shareExt.allLocales).sorted()
|
|
#expect(missing.isEmpty, "share extension is missing locales: \(missing.joined(separator: ", "))")
|
|
}
|
|
}
|