@swiftbrowser/swift 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/Package.swift +200 -9
  2. package/README.md +7 -9
  3. package/Sources/AVFoundation/AVFoundation.swift +131 -0
  4. package/Sources/AVFoundation/Asset.swift +487 -0
  5. package/Sources/AVFoundation/AudioSession.swift +144 -0
  6. package/Sources/AVFoundation/CMTime.swift +303 -0
  7. package/Sources/AVFoundation/Capture.swift +688 -0
  8. package/Sources/AVFoundation/Player.swift +749 -0
  9. package/Sources/AVFoundation/PlayerView.swift +117 -0
  10. package/Sources/AVKit/AVKit.swift +132 -0
  11. package/Sources/Accelerate/Accelerate.swift +122 -0
  12. package/Sources/AppIntents/AppIntents.swift +144 -0
  13. package/Sources/AppIntents/Parameters.swift +220 -0
  14. package/Sources/AppIntents/Shortcuts.swift +159 -0
  15. package/Sources/AudioToolbox/AudioToolbox.swift +55 -0
  16. package/Sources/AuthenticationServices/AuthenticationServices.swift +257 -0
  17. package/Sources/AuthenticationServices/Authorization.swift +251 -0
  18. package/Sources/Charts/Charts.swift +0 -4
  19. package/Sources/Combine/AnyCancellable.swift +65 -0
  20. package/Sources/Combine/AnyPublisher.swift +148 -0
  21. package/Sources/Combine/AnySubscriber.swift +230 -0
  22. package/Sources/Combine/Cancellable.swift +35 -0
  23. package/Sources/Combine/Codable.swift +29 -0
  24. package/Sources/Combine/CombineIdentifier.swift +45 -0
  25. package/Sources/Combine/Concurrency/ConcurrencyHelpers.swift +24 -0
  26. package/Sources/Combine/Concurrency/Future+Concurrency.swift +136 -0
  27. package/Sources/Combine/Concurrency/GENERATED-Publisher+Concurrency.swift +397 -0
  28. package/Sources/Combine/CurrentValueSubject.swift +252 -0
  29. package/Sources/Combine/CustomCombineIdentifierConvertible.swift +28 -0
  30. package/Sources/Combine/Future.swift +228 -0
  31. package/Sources/Combine/GENERATED-RootProtocols.swift +426 -0
  32. package/Sources/Combine/Helpers/ConduitBase.swift +40 -0
  33. package/Sources/Combine/Helpers/ConduitList.swift +59 -0
  34. package/Sources/Combine/Helpers/DebugHook.swift +177 -0
  35. package/Sources/Combine/Helpers/FilterProducer.swift +174 -0
  36. package/Sources/Combine/Helpers/Locking.swift +30 -0
  37. package/Sources/Combine/Helpers/PartialCompletion.swift +26 -0
  38. package/Sources/Combine/Helpers/ReduceProducer.swift +249 -0
  39. package/Sources/Combine/Helpers/SubjectSubscriber.swift +96 -0
  40. package/Sources/Combine/Helpers/SubscriberTap.swift +69 -0
  41. package/Sources/Combine/Helpers/SubscriptionStatus.swift +33 -0
  42. package/Sources/Combine/Helpers/SubscriptionTap.swift +33 -0
  43. package/Sources/Combine/Helpers/Utils.swift +30 -0
  44. package/Sources/Combine/Helpers/Violations.swift +33 -0
  45. package/Sources/Combine/ImmediateScheduler.swift +178 -0
  46. package/Sources/Combine/LICENSE +21 -0
  47. package/Sources/Combine/NotificationPublisher.swift +48 -0
  48. package/Sources/Combine/ObjectWillChange.swift +46 -0
  49. package/Sources/Combine/PassthroughSubject.swift +222 -0
  50. package/Sources/Combine/Publisher+Subscribe.swift +53 -0
  51. package/Sources/Combine/Publishers/Deferred.swift +48 -0
  52. package/Sources/Combine/Publishers/Empty.swift +54 -0
  53. package/Sources/Combine/Publishers/Fail.swift +42 -0
  54. package/Sources/Combine/Publishers/GENERATED-Publishers.Catch.swift +673 -0
  55. package/Sources/Combine/Publishers/GENERATED-Publishers.Encode.swift +388 -0
  56. package/Sources/Combine/Publishers/GENERATED-Publishers.MapKeyPath.swift +384 -0
  57. package/Sources/Combine/Publishers/Just.swift +313 -0
  58. package/Sources/Combine/Publishers/Optional.Publisher.swift +315 -0
  59. package/Sources/Combine/Publishers/Publishers.AllSatisfy.swift +217 -0
  60. package/Sources/Combine/Publishers/Publishers.AssertNoFailure.swift +158 -0
  61. package/Sources/Combine/Publishers/Publishers.Autoconnect.swift +193 -0
  62. package/Sources/Combine/Publishers/Publishers.Breakpoint.swift +231 -0
  63. package/Sources/Combine/Publishers/Publishers.Buffer.swift +356 -0
  64. package/Sources/Combine/Publishers/Publishers.Collect.swift +102 -0
  65. package/Sources/Combine/Publishers/Publishers.CollectByCount.swift +195 -0
  66. package/Sources/Combine/Publishers/Publishers.CompactMap.swift +241 -0
  67. package/Sources/Combine/Publishers/Publishers.Comparison.swift +371 -0
  68. package/Sources/Combine/Publishers/Publishers.Concatenate.swift +474 -0
  69. package/Sources/Combine/Publishers/Publishers.Contains.swift +293 -0
  70. package/Sources/Combine/Publishers/Publishers.Count.swift +87 -0
  71. package/Sources/Combine/Publishers/Publishers.Debounce.swift +332 -0
  72. package/Sources/Combine/Publishers/Publishers.Delay.swift +269 -0
  73. package/Sources/Combine/Publishers/Publishers.Drop.swift +155 -0
  74. package/Sources/Combine/Publishers/Publishers.DropUntilOutput.swift +283 -0
  75. package/Sources/Combine/Publishers/Publishers.DropWhile.swift +378 -0
  76. package/Sources/Combine/Publishers/Publishers.Filter.swift +248 -0
  77. package/Sources/Combine/Publishers/Publishers.First.swift +261 -0
  78. package/Sources/Combine/Publishers/Publishers.FlatMap.swift +533 -0
  79. package/Sources/Combine/Publishers/Publishers.HandleEvents.swift +260 -0
  80. package/Sources/Combine/Publishers/Publishers.IgnoreOutput.swift +109 -0
  81. package/Sources/Combine/Publishers/Publishers.Last.swift +252 -0
  82. package/Sources/Combine/Publishers/Publishers.MakeConnectable.swift +82 -0
  83. package/Sources/Combine/Publishers/Publishers.Map.swift +351 -0
  84. package/Sources/Combine/Publishers/Publishers.MapError.swift +140 -0
  85. package/Sources/Combine/Publishers/Publishers.MeasureInterval.swift +187 -0
  86. package/Sources/Combine/Publishers/Publishers.Multicast.swift +284 -0
  87. package/Sources/Combine/Publishers/Publishers.Output.swift +241 -0
  88. package/Sources/Combine/Publishers/Publishers.PrefixUntilOutput.swift +197 -0
  89. package/Sources/Combine/Publishers/Publishers.PrefixWhile.swift +176 -0
  90. package/Sources/Combine/Publishers/Publishers.Print.swift +206 -0
  91. package/Sources/Combine/Publishers/Publishers.ReceiveOn.swift +227 -0
  92. package/Sources/Combine/Publishers/Publishers.Reduce.swift +204 -0
  93. package/Sources/Combine/Publishers/Publishers.RemoveDuplicates.swift +264 -0
  94. package/Sources/Combine/Publishers/Publishers.ReplaceEmpty.swift +201 -0
  95. package/Sources/Combine/Publishers/Publishers.ReplaceError.swift +211 -0
  96. package/Sources/Combine/Publishers/Publishers.Retry.swift +274 -0
  97. package/Sources/Combine/Publishers/Publishers.Scan.swift +331 -0
  98. package/Sources/Combine/Publishers/Publishers.Sequence.swift +437 -0
  99. package/Sources/Combine/Publishers/Publishers.SetFailureType.swift +120 -0
  100. package/Sources/Combine/Publishers/Publishers.Share.swift +104 -0
  101. package/Sources/Combine/Publishers/Publishers.SubscribeOn.swift +197 -0
  102. package/Sources/Combine/Publishers/Publishers.SwitchToLatest.swift +444 -0
  103. package/Sources/Combine/Publishers/Publishers.Throttle.swift +359 -0
  104. package/Sources/Combine/Publishers/Publishers.Timeout.swift +301 -0
  105. package/Sources/Combine/Publishers/Publishers.Zip.swift +720 -0
  106. package/Sources/Combine/Publishers/Publishers.swift +13 -0
  107. package/Sources/Combine/Publishers/Record.swift +237 -0
  108. package/Sources/Combine/Publishers/Result.Publisher.swift +337 -0
  109. package/Sources/Combine/Result.swift +46 -0
  110. package/Sources/Combine/Scheduler.swift +89 -0
  111. package/Sources/Combine/Schedulers.swift +211 -0
  112. package/Sources/Combine/Subject+Void.swift +17 -0
  113. package/Sources/Combine/Subscriber+Void.swift +20 -0
  114. package/Sources/Combine/Subscribers/Subscribers.Assign.swift +175 -0
  115. package/Sources/Combine/Subscribers/Subscribers.Completion.swift +90 -0
  116. package/Sources/Combine/Subscribers/Subscribers.Demand.swift +476 -0
  117. package/Sources/Combine/Subscribers/Subscribers.Sink.swift +206 -0
  118. package/Sources/Combine/Subscribers/Subscribers.swift +9 -0
  119. package/Sources/Combine/Subscription.swift +22 -0
  120. package/Sources/Combine/Subscriptions.swift +42 -0
  121. package/Sources/Combine/SwiftUIObservation.swift +10 -0
  122. package/Sources/Combine/_Introspection.swift +107 -0
  123. package/Sources/CommonCrypto/CommonCrypto.swift +80 -0
  124. package/Sources/Compression/Compression.swift +365 -0
  125. package/Sources/Compression/Deflate.swift +407 -0
  126. package/Sources/Compression/Zlib.swift +179 -0
  127. package/Sources/CoreFoundationTypes/CoreFoundationTypes.swift +101 -0
  128. package/Sources/CoreGraphics/CoreGraphics.swift +411 -0
  129. package/Sources/CoreGraphics/ProjectionTransforms.swift +28 -0
  130. package/Sources/CoreHaptics/CoreHaptics.swift +399 -0
  131. package/Sources/CoreImage/CIColor.swift +41 -0
  132. package/Sources/CoreImage/CIContext.swift +31 -0
  133. package/Sources/CoreImage/CIDetector.swift +57 -0
  134. package/Sources/CoreImage/CIFilter.swift +243 -0
  135. package/Sources/CoreImage/CIFilterBuiltins.swift +136 -0
  136. package/Sources/CoreImage/CIImage.swift +162 -0
  137. package/Sources/CoreImage/QRCode.swift +393 -0
  138. package/Sources/CoreLocation/Coordinate.swift +30 -0
  139. package/Sources/CoreLocation/Location.swift +65 -0
  140. package/Sources/CoreML/Evaluation.swift +414 -0
  141. package/Sources/CoreML/FeatureValue.swift +266 -0
  142. package/Sources/CoreML/Model.swift +269 -0
  143. package/Sources/CoreML/MultiArray.swift +92 -0
  144. package/Sources/CoreML/Specification.swift +233 -0
  145. package/Sources/CoreText/CoreText.swift +64 -0
  146. package/Sources/CoreTransferable/CoreTransferable.swift +606 -0
  147. package/Sources/CryptoKit/ChaChaPoly.swift +258 -0
  148. package/Sources/CryptoKit/CryptoKit.swift +375 -0
  149. package/Sources/CryptoKit/Curve25519.swift +317 -0
  150. package/Sources/CryptoKit/P256.swift +321 -0
  151. package/Sources/FoundationBridge/AppStorage.swift +165 -0
  152. package/Sources/FoundationBridge/AssetResources.swift +90 -0
  153. package/Sources/FoundationBridge/AsyncBytes.swift +128 -0
  154. package/Sources/FoundationBridge/AsyncImage.swift +116 -0
  155. package/Sources/FoundationBridge/AttributedStrings.swift +16 -0
  156. package/Sources/{_FoundationCommon → FoundationBridge}/Bundle.swift +41 -4
  157. package/Sources/FoundationBridge/CombineExports.swift +8 -0
  158. package/Sources/FoundationBridge/CoreFoundation.swift +77 -0
  159. package/Sources/FoundationBridge/Dispatch.swift +458 -0
  160. package/Sources/FoundationBridge/EnvironmentFoundation.swift +41 -0
  161. package/Sources/FoundationBridge/FileImporter.swift +128 -0
  162. package/Sources/FoundationBridge/FileSystem.swift +68 -0
  163. package/Sources/FoundationBridge/FoundationBridge.swift +24 -72
  164. package/Sources/FoundationBridge/HostResolution.swift +31 -0
  165. package/Sources/FoundationBridge/ItemProvider.swift +655 -0
  166. package/Sources/FoundationBridge/KeyValueObserving.swift +138 -0
  167. package/Sources/FoundationBridge/Links.swift +127 -0
  168. package/Sources/FoundationBridge/ListFormatter.swift +32 -0
  169. package/Sources/FoundationBridge/Localization.swift +149 -0
  170. package/Sources/FoundationBridge/Locks.swift +50 -0
  171. package/Sources/FoundationBridge/Markdown.swift +527 -0
  172. package/Sources/FoundationBridge/MeasurementFormat.swift +95 -0
  173. package/Sources/FoundationBridge/NotificationCenter.swift +154 -0
  174. package/Sources/FoundationBridge/OpenURLHandlers.swift +60 -0
  175. package/Sources/FoundationBridge/Operation.swift +195 -0
  176. package/Sources/FoundationBridge/ProcessInfo.swift +32 -0
  177. package/Sources/FoundationBridge/RelativeDateTimeFormatter.swift +136 -0
  178. package/Sources/FoundationBridge/SceneStorage.swift +78 -0
  179. package/Sources/FoundationBridge/SecurityExports.swift +8 -0
  180. package/Sources/FoundationBridge/Selector.swift +226 -0
  181. package/Sources/FoundationBridge/StringComparators.swift +17 -0
  182. package/Sources/FoundationBridge/Table.swift +211 -0
  183. package/Sources/FoundationBridge/TextAttributes.swift +42 -0
  184. package/Sources/FoundationBridge/Timer.swift +6 -1
  185. package/Sources/FoundationBridge/URLSession.swift +718 -0
  186. package/Sources/{_FoundationCommon → FoundationBridge}/UserDefaults.swift +17 -4
  187. package/Sources/FoundationBridge/WebAuthRedirects.swift +227 -0
  188. package/Sources/FoundationBridge/WebSocket.swift +187 -0
  189. package/Sources/FoundationModels/FoundationModels.swift +1098 -0
  190. package/Sources/ImageIO/GIF.swift +72 -0
  191. package/Sources/ImageIO/ImageIO.swift +248 -0
  192. package/Sources/ImageIO/ImageTypes.swift +28 -0
  193. package/Sources/LinkPresentation/LinkPresentation.swift +249 -0
  194. package/Sources/LocalAuthentication/LAContext.swift +86 -0
  195. package/Sources/MapKit/Geometry.swift +223 -0
  196. package/Sources/MapKit/Map.swift +638 -0
  197. package/Sources/MapKit/MapView.swift +229 -0
  198. package/Sources/MobileCoreServices/MobileCoreServices.swift +28 -0
  199. package/Sources/NaturalLanguage/LanguageIdentifier.swift +317 -0
  200. package/Sources/NaturalLanguage/NaturalLanguage.swift +135 -0
  201. package/Sources/OSLog/OSLog.swift +6 -0
  202. package/Sources/Photos/Photos.swift +572 -0
  203. package/Sources/PhotosUI/PhotosPicker.swift +248 -0
  204. package/Sources/PhotosUI/PhotosPickerItem.swift +261 -0
  205. package/Sources/QuickLook/QuickLook.swift +168 -0
  206. package/Sources/SafariServices/SafariServices.swift +106 -0
  207. package/Sources/Security/Security.swift +332 -0
  208. package/Sources/StoreKit/AppStore.swift +207 -0
  209. package/Sources/StoreKit/Products.swift +280 -0
  210. package/Sources/StoreKit/RequestReview.swift +28 -0
  211. package/Sources/StoreKit/StoreKit1.swift +486 -0
  212. package/Sources/StoreKit/StoreViews.swift +428 -0
  213. package/Sources/StoreKit/Transactions.swift +197 -0
  214. package/Sources/SwiftData/Macros.swift +58 -0
  215. package/Sources/SwiftData/ModelActor.swift +38 -0
  216. package/Sources/SwiftData/ModelContainer.swift +129 -0
  217. package/Sources/SwiftData/ModelContext.swift +451 -0
  218. package/Sources/SwiftData/PersistentModel.swift +238 -0
  219. package/Sources/SwiftData/Properties.swift +261 -0
  220. package/Sources/SwiftData/Query.swift +256 -0
  221. package/Sources/SwiftData/Schema.swift +192 -0
  222. package/Sources/SwiftData/Store.swift +146 -0
  223. package/Sources/SwiftData/StringPredicates.swift +125 -0
  224. package/Sources/SwiftDataMacros/EntryMacro.swift +82 -0
  225. package/Sources/SwiftDataMacros/GenerableMacro.swift +183 -0
  226. package/Sources/SwiftDataMacros/ModelActorMacro.swift +46 -0
  227. package/Sources/SwiftDataMacros/ModelMacro.swift +260 -0
  228. package/Sources/SwiftDataMacros/Plugin.swift +18 -0
  229. package/Sources/SwiftDataMacros/PredicateMacro.swift +1244 -0
  230. package/Sources/SwiftUI/API/Accessibility.swift +55 -6
  231. package/Sources/SwiftUI/API/AccessibilityExtras.swift +33 -0
  232. package/Sources/SwiftUI/API/AccessibilityFocus.swift +63 -0
  233. package/Sources/SwiftUI/API/Alert.swift +1 -1
  234. package/Sources/SwiftUI/API/AlignmentGuides.swift +65 -0
  235. package/Sources/SwiftUI/API/Animation.swift +37 -5
  236. package/Sources/SwiftUI/API/App.swift +31 -1
  237. package/Sources/SwiftUI/API/BindingCollections.swift +35 -0
  238. package/Sources/SwiftUI/API/BindingForEach.swift +27 -0
  239. package/Sources/SwiftUI/API/Bitmaps.swift +254 -0
  240. package/Sources/SwiftUI/API/ButtonStyles.swift +96 -0
  241. package/Sources/SwiftUI/API/Canvas.swift +337 -0
  242. package/Sources/SwiftUI/API/Color.swift +104 -28
  243. package/Sources/SwiftUI/API/ColorEffects.swift +52 -0
  244. package/Sources/SwiftUI/API/ColorPickerAndLabeledContent.swift +138 -0
  245. package/Sources/SwiftUI/API/ColorSpaces.swift +161 -0
  246. package/Sources/SwiftUI/API/Commands.swift +106 -0
  247. package/Sources/SwiftUI/API/ContainerBackground.swift +28 -0
  248. package/Sources/SwiftUI/API/ContentShape.swift +33 -0
  249. package/Sources/SwiftUI/API/ContentSizeCategory.swift +35 -0
  250. package/Sources/SwiftUI/API/ContentUnavailable.swift +101 -0
  251. package/Sources/SwiftUI/API/Controls.swift +59 -27
  252. package/Sources/SwiftUI/API/CustomLayout.swift +49 -10
  253. package/Sources/SwiftUI/API/CustomToolbarContent.swift +46 -0
  254. package/Sources/SwiftUI/API/Decorations.swift +1 -1
  255. package/Sources/SwiftUI/API/EditMode.swift +25 -82
  256. package/Sources/SwiftUI/API/Effects.swift +105 -7
  257. package/Sources/SwiftUI/API/Entry.swift +8 -0
  258. package/Sources/SwiftUI/API/Environment.swift +93 -9
  259. package/Sources/SwiftUI/API/EnvironmentSendable.swift +6 -0
  260. package/Sources/SwiftUI/API/Focus.swift +11 -3
  261. package/Sources/SwiftUI/API/Gauge.swift +3 -2
  262. package/Sources/SwiftUI/API/GeometryEffects.swift +232 -0
  263. package/Sources/SwiftUI/API/GeometryReader.swift +13 -2
  264. package/Sources/SwiftUI/API/GestureState.swift +65 -0
  265. package/Sources/SwiftUI/API/Gestures.swift +1 -1
  266. package/Sources/SwiftUI/API/Glass.swift +127 -0
  267. package/Sources/SwiftUI/API/Grid.swift +1 -1
  268. package/Sources/SwiftUI/API/GroupBoxAndControlGroup.swift +130 -0
  269. package/Sources/SwiftUI/API/KeyPress.swift +54 -0
  270. package/Sources/SwiftUI/API/LabelStyle.swift +13 -6
  271. package/Sources/SwiftUI/API/Layout.swift +67 -12
  272. package/Sources/SwiftUI/API/LazyStacks.swift +49 -0
  273. package/Sources/SwiftUI/API/LegacyAlert.swift +97 -0
  274. package/Sources/SwiftUI/API/Lists.swift +61 -6
  275. package/Sources/SwiftUI/API/Menu.swift +8 -1
  276. package/Sources/SwiftUI/API/Modifiers.swift +80 -10
  277. package/Sources/SwiftUI/API/MoreModifiers.swift +100 -0
  278. package/Sources/SwiftUI/API/Namespace.swift +44 -0
  279. package/Sources/SwiftUI/API/Navigation.swift +24 -9
  280. package/Sources/SwiftUI/API/NavigationDestinationBindings.swift +55 -0
  281. package/Sources/SwiftUI/API/ObservableObject.swift +16 -2
  282. package/Sources/SwiftUI/API/Path.swift +543 -0
  283. package/Sources/SwiftUI/API/Phase8Controls.swift +48 -31
  284. package/Sources/SwiftUI/API/PhaseAnimator.swift +130 -0
  285. package/Sources/SwiftUI/API/Picker.swift +11 -2
  286. package/Sources/SwiftUI/API/PointerAndKeyboard.swift +77 -0
  287. package/Sources/SwiftUI/API/Popover.swift +41 -0
  288. package/Sources/SwiftUI/API/Preferences.swift +262 -0
  289. package/Sources/SwiftUI/API/Presentation.swift +17 -4
  290. package/Sources/SwiftUI/API/PresentationExtras.swift +60 -0
  291. package/Sources/SwiftUI/API/Primitives.swift +164 -37
  292. package/Sources/SwiftUI/API/Publishers.swift +4 -4
  293. package/Sources/SwiftUI/API/Redaction.swift +75 -0
  294. package/Sources/SwiftUI/API/RowActions.swift +3 -2
  295. package/Sources/SwiftUI/API/Scenes.swift +131 -0
  296. package/Sources/SwiftUI/API/ScrollModifiers.swift +132 -0
  297. package/Sources/SwiftUI/API/ScrollTargets.swift +3 -3
  298. package/Sources/SwiftUI/API/ScrollViewReader.swift +144 -0
  299. package/Sources/SwiftUI/API/SectionSupplements.swift +96 -0
  300. package/Sources/SwiftUI/API/Shape.swift +214 -0
  301. package/Sources/SwiftUI/API/ShapeStyle.swift +105 -7
  302. package/Sources/SwiftUI/API/ShapeTrim.swift +78 -0
  303. package/Sources/SwiftUI/API/Sharing.swift +193 -0
  304. package/Sources/SwiftUI/API/StackLayouts.swift +199 -0
  305. package/Sources/SwiftUI/API/State.swift +18 -1
  306. package/Sources/SwiftUI/API/StatusGaps.swift +121 -0
  307. package/Sources/SwiftUI/API/SymbolEffects.swift +203 -0
  308. package/Sources/SwiftUI/API/TabContent.swift +841 -0
  309. package/Sources/SwiftUI/API/Table.swift +382 -0
  310. package/Sources/SwiftUI/API/Tabs.swift +148 -4
  311. package/Sources/SwiftUI/API/Tasks.swift +2 -2
  312. package/Sources/SwiftUI/API/Text.swift +386 -0
  313. package/Sources/SwiftUI/API/TextDates.swift +61 -0
  314. package/Sources/SwiftUI/API/TextDecorations.swift +52 -0
  315. package/Sources/SwiftUI/API/TextFieldAxis.swift +103 -0
  316. package/Sources/SwiftUI/API/TextFieldCommit.swift +52 -0
  317. package/Sources/SwiftUI/API/Toolbar.swift +52 -16
  318. package/Sources/SwiftUI/API/Transfer.swift +145 -0
  319. package/Sources/SwiftUI/API/UnevenRoundedRectangle.swift +66 -0
  320. package/Sources/SwiftUI/API/Video.swift +64 -0
  321. package/Sources/SwiftUI/API/View.swift +10 -2
  322. package/Sources/SwiftUI/API/ViewModifier.swift +4 -1
  323. package/Sources/SwiftUI/API/VisualEffect.swift +74 -1
  324. package/Sources/SwiftUI/API/Windows.swift +25 -0
  325. package/Sources/SwiftUI/Charts/Axis.swift +7 -1
  326. package/Sources/SwiftUI/Charts/AxisFormatsAndRanges.swift +68 -0
  327. package/Sources/SwiftUI/Charts/Chart.swift +17 -3
  328. package/Sources/SwiftUI/Charts/ChartProxy.swift +57 -0
  329. package/Sources/SwiftUI/Localization/Localization.swift +498 -0
  330. package/Sources/SwiftUI/Localization/PluralRules.swift +127 -0
  331. package/Sources/SwiftUI/Runtime/Executor.swift +2 -0
  332. package/Sources/SwiftUI/Runtime/Exports.swift +37 -1
  333. package/Sources/SwiftUI/Runtime/ImageSource.swift +310 -0
  334. package/Sources/SwiftUI/Runtime/JSON.swift +76 -4
  335. package/Sources/SwiftUI/Runtime/LazyRows.swift +99 -0
  336. package/Sources/SwiftUI/Runtime/Measurements.swift +140 -0
  337. package/Sources/SwiftUI/Runtime/Ops.swift +58 -12
  338. package/Sources/SwiftUI/Runtime/Reconciler.swift +10 -1
  339. package/Sources/SwiftUI/Runtime/Replay.swift +382 -0
  340. package/Sources/SwiftUI/Runtime/Runtime.swift +977 -198
  341. package/Sources/SwiftUI/Runtime/Sameness.swift +152 -0
  342. package/Sources/SwiftUI/Runtime/ViewPath.swift +89 -0
  343. package/Sources/SwiftUI/Runtime/ViewTraits.swift +175 -0
  344. package/Sources/SwiftUI/Runtime/WebAuthHeadless.swift +49 -0
  345. package/Sources/Symbols/Symbols.swift +25 -0
  346. package/Sources/Translation/Translation.swift +423 -0
  347. package/Sources/UIKit/Activity.swift +213 -0
  348. package/Sources/UIKit/Alerts.swift +69 -0
  349. package/Sources/UIKit/AppDelegate.swift +222 -0
  350. package/Sources/UIKit/Appearance.swift +84 -0
  351. package/Sources/UIKit/ColorConversions.swift +70 -0
  352. package/Sources/UIKit/Controls.swift +225 -0
  353. package/Sources/UIKit/Device.swift +200 -0
  354. package/Sources/UIKit/Feedback.swift +65 -0
  355. package/Sources/UIKit/Font.swift +307 -0
  356. package/Sources/UIKit/Graphics.swift +204 -0
  357. package/Sources/UIKit/HostingController.swift +185 -0
  358. package/Sources/UIKit/Identity.swift +24 -0
  359. package/Sources/UIKit/Introspectable.swift +510 -0
  360. package/Sources/UIKit/Pasteboard.swift +363 -0
  361. package/Sources/UIKit/PhotoAlbum.swift +81 -0
  362. package/Sources/UIKit/RemoteNotifications.swift +15 -0
  363. package/Sources/UIKit/Representable.swift +288 -0
  364. package/Sources/UIKit/SafeArea.swift +12 -0
  365. package/Sources/UIKit/SceneDelegates.swift +132 -0
  366. package/Sources/UIKit/Scenes.swift +40 -0
  367. package/Sources/UIKit/ScrollView.swift +157 -0
  368. package/Sources/UIKit/ShortcutItems.swift +67 -0
  369. package/Sources/UIKit/SymbolConfiguration.swift +124 -0
  370. package/Sources/UIKit/TargetAction.swift +123 -0
  371. package/Sources/UIKit/TextPaste.swift +161 -0
  372. package/Sources/UIKit/TextView.swift +538 -0
  373. package/Sources/UIKit/UIKit.swift +292 -0
  374. package/Sources/UIKit/UIView.swift +855 -0
  375. package/Sources/UIKit/ViewTranslation.swift +311 -0
  376. package/Sources/UniformTypeIdentifiers/UTType.swift +196 -0
  377. package/Sources/UserNotifications/Content.swift +370 -0
  378. package/Sources/UserNotifications/NotificationCenter.swift +309 -0
  379. package/Sources/WeatherKit/WeatherKit.swift +228 -0
  380. package/Sources/WebKit/WebKit.swift +245 -0
  381. package/Sources/os/os.swift +376 -0
  382. package/Sources/zlib/zlib.swift +6 -0
  383. package/index.d.ts +1 -1
  384. package/index.mjs +2 -2
  385. package/manifest.d.mts +2 -0
  386. package/manifest.mjs +39 -0
  387. package/package.json +5 -3
  388. package/sync-sources.mjs +21 -7
  389. package/Sources/FoundationLite/Common.swift +0 -3
  390. package/Sources/FoundationLite/FoundationLite.swift +0 -171
  391. package/Sources/FoundationLite/Interaction.swift +0 -146
  392. package/Sources/FoundationLite/Timer.swift +0 -48
  393. package/Sources/_FoundationCommon/_FoundationCommon.swift +0 -10
@@ -0,0 +1,68 @@
1
+ // `AxisValueLabel(format:)` and the scale ranges of `.chartXScale(range:)`.
2
+ //
3
+ // A formatted label is the tick's value converted to the format's input
4
+ // type and formatted with it (the default text when the value is of
5
+ // another type). A scale range (`.plotDimension(startPadding:endPadding:)`,
6
+ // a range of points) is accepted: the page maps the domain to the whole plot.
7
+
8
+ import Foundation
9
+
10
+ extension AxisValueLabel {
11
+ /// The value formatted with `format` (`.dateTime.month().day()`, `.number`…).
12
+ public init<F: FormatStyle>(format: F, centered: Bool? = nil, anchor: UnitPoint? = nil,
13
+ multiLabelAlignment: Alignment? = nil, collisionResolution: AxisValueLabelCollisionResolution = .automatic,
14
+ offsetsMarks: Bool? = nil, orientation: AxisValueLabelOrientation = .automatic,
15
+ horizontalSpacing: CGFloat? = nil, verticalSpacing: CGFloat? = nil)
16
+ where F.FormatInput: Plottable, F.FormatOutput == String {
17
+ self.init(_label: .formatted { value in F.FormatInput(_plottableValue: value).map { format.format($0) } })
18
+ }
19
+ }
20
+
21
+ /// How overlapping axis labels are resolved; accepted.
22
+ public struct AxisValueLabelCollisionResolution: Hashable, Sendable {
23
+ let name: String
24
+ public static let automatic = AxisValueLabelCollisionResolution(name: "automatic")
25
+ public static let greedy = AxisValueLabelCollisionResolution(name: "greedy")
26
+ public static let truncate = AxisValueLabelCollisionResolution(name: "truncate")
27
+ public static let disabled = AxisValueLabelCollisionResolution(name: "disabled")
28
+ }
29
+
30
+ /// Which way axis labels run; accepted (labels are horizontal).
31
+ public struct AxisValueLabelOrientation: Hashable, Sendable {
32
+ let name: String
33
+ public static let automatic = AxisValueLabelOrientation(name: "automatic")
34
+ public static let horizontal = AxisValueLabelOrientation(name: "horizontal")
35
+ public static let vertical = AxisValueLabelOrientation(name: "vertical")
36
+ public static let verticalReversed = AxisValueLabelOrientation(name: "verticalReversed")
37
+ }
38
+
39
+ /// The points a scale maps its domain to; accepted.
40
+ public protocol ScaleRange {}
41
+
42
+ /// The plot's length along the axis, less the paddings.
43
+ public struct PlotDimensionScaleRange: ScaleRange, Hashable, Sendable {}
44
+
45
+ extension ScaleRange where Self == PlotDimensionScaleRange {
46
+ public static var plotDimension: PlotDimensionScaleRange { PlotDimensionScaleRange() }
47
+ public static func plotDimension(padding: CGFloat) -> PlotDimensionScaleRange { PlotDimensionScaleRange() }
48
+ public static func plotDimension(startPadding: CGFloat = 0, endPadding: CGFloat = 0) -> PlotDimensionScaleRange {
49
+ PlotDimensionScaleRange()
50
+ }
51
+ }
52
+
53
+ extension ClosedRange: ScaleRange where Bound == CGFloat {}
54
+
55
+ nonisolated extension View {
56
+ /// Accepted (see the file comment).
57
+ public func chartXScale<R: ScaleRange>(range: R) -> some View { self }
58
+ public func chartYScale<R: ScaleRange>(range: R) -> some View { self }
59
+
60
+ /// The domain, as `chartXScale(domain:)`; the range is accepted.
61
+ public func chartXScale<D: ScaleDomain, R: ScaleRange>(domain: D, range: R) -> some View {
62
+ chartXScale(domain: domain)
63
+ }
64
+
65
+ public func chartYScale<D: ScaleDomain, R: ScaleRange>(domain: D, range: R) -> some View {
66
+ chartYScale(domain: domain)
67
+ }
68
+ }
@@ -535,8 +535,20 @@ private struct _ChartResolver {
535
535
  for set in axis.ticks {
536
536
  let count = set.values.count
537
537
  for (index, value) in set.values.enumerated() {
538
- let unit = axis.scale.unit(value) ?? 0
538
+ let scaled = axis.scale.unit(value)
539
+ // A value outside the scale's domain (a date bin that starts
540
+ // before the data) has no mark, as in Swift Charts.
541
+ if let scaled, scaled < -1e-9 || scaled > 1 + 1e-9 { continue }
542
+ let unit = scaled ?? 0
539
543
  let at = _unit(isX ? unit : 1 - unit)
544
+ // A date's label starts at its tick, as Swift Charts places it;
545
+ // other values are centered on theirs.
546
+ func label(_ text: String) -> _JSON {
547
+ if isX, case .date = value {
548
+ return .object(["at": .number(at), "text": .string(text), "anchor": .string("leading")])
549
+ }
550
+ return .object(["at": .number(at), "text": .string(text)])
551
+ }
540
552
  let marks = set.marks.content?(AxisValue(index: index, count: count, value: value))._items
541
553
  ?? [.gridLine, .tick, .label(.automatic)]
542
554
  for item in marks {
@@ -546,9 +558,11 @@ private struct _ChartResolver {
546
558
  case .tick:
547
559
  ticks.append(.number(at))
548
560
  case .label(.automatic):
549
- labels.append(.object(["at": .number(at), "text": .string(defaultLabel(value, axis: axis))]))
561
+ labels.append(label(defaultLabel(value, axis: axis)))
550
562
  case .label(.text(let text)):
551
- labels.append(.object(["at": .number(at), "text": .string(text)]))
563
+ labels.append(label(text))
564
+ case .label(.formatted(let format)):
565
+ labels.append(label(format(value) ?? defaultLabel(value, axis: axis)))
552
566
  case .label(.view(let view)):
553
567
  let edge: Double = position == "top" || position == "leading" ? 0 : 1
554
568
  views.append(_ChartItemView(
@@ -0,0 +1,57 @@
1
+ /// `ChartProxy`, `.chartOverlay`, `.chartBackground` and the selection
2
+ /// modifiers. The page lays charts out and draws their marks (Phase 7), so
3
+ /// the Swift side knows no plot geometry: a proxy maps no value to a
4
+ /// position or back (nil), and its plot frame is unknown (nil). An overlay
5
+ /// or background is drawn over or under the chart with that proxy. The
6
+ /// page reports no taps or drags on a chart, so a selection binding is
7
+ /// never set by the viewer.
8
+
9
+ /// Positions in a chart's plot, for overlays: unknown in the browser.
10
+ public struct ChartProxy {
11
+ init() {}
12
+
13
+ /// Nil: the plot's geometry is the page's.
14
+ public func position<P: Plottable>(forX value: P) -> CGFloat? { nil }
15
+ public func position<P: Plottable>(forY value: P) -> CGFloat? { nil }
16
+ public func position<X: Plottable, Y: Plottable>(for value: (x: X, y: Y)) -> CGPoint? { nil }
17
+ public func value<P: Plottable>(atX position: CGFloat, as type: P.Type = P.self) -> P? { nil }
18
+ public func value<P: Plottable>(atY position: CGFloat, as type: P.Type = P.self) -> P? { nil }
19
+ public func value<X: Plottable, Y: Plottable>(at location: CGPoint, as type: (X, Y).Type = (X, Y).self) -> (X, Y)? { nil }
20
+ /// Nil: the plot's frame is the page's.
21
+ public var plotFrame: Anchor<CGRect>? { nil }
22
+ /// Zero.
23
+ public var plotSize: CGSize { .zero }
24
+ }
25
+
26
+ nonisolated extension View {
27
+ /// Draws `content` over the chart with a proxy (see the file comment).
28
+ public func chartOverlay<V: View>(alignment: Alignment = .center,
29
+ @ViewBuilder content: @escaping (ChartProxy) -> V) -> some View {
30
+ overlay(alignment: alignment) { content(ChartProxy()) }
31
+ }
32
+
33
+ /// Draws `content` under the chart with a proxy (see the file comment).
34
+ public func chartBackground<V: View>(alignment: Alignment = .center,
35
+ @ViewBuilder content: @escaping (ChartProxy) -> V) -> some View {
36
+ background(alignment: alignment) { content(ChartProxy()) }
37
+ }
38
+
39
+ /// Styles the chart's plot area with `content`. The page lays the plot
40
+ /// out inside the chart, so the plot is not a view of its own here:
41
+ /// `content` gets the whole chart (an overlay covers the axes too).
42
+ public func chartPlotStyle<Content: View>(@ViewBuilder content: @escaping (ChartPlotContent) -> Content) -> some View {
43
+ content(ChartPlotContent(content: AnyView(self)))
44
+ }
45
+
46
+ /// Accepted: the viewer cannot select a value on the page.
47
+ public func chartXSelection<P: Plottable>(value: Binding<P?>) -> some View { self }
48
+ public func chartXSelection<P: Plottable & Comparable>(range: Binding<ClosedRange<P>?>) -> some View { self }
49
+ public func chartYSelection<P: Plottable>(value: Binding<P?>) -> some View { self }
50
+ public func chartYSelection<P: Plottable & Comparable>(range: Binding<ClosedRange<P>?>) -> some View { self }
51
+ }
52
+
53
+ /// The plot area `chartPlotStyle` styles: here, the whole chart (see there).
54
+ public struct ChartPlotContent: View {
55
+ let content: AnyView
56
+ public var body: some View { content }
57
+ }
@@ -0,0 +1,498 @@
1
+ // Localized strings, as `Text("key")`, `String(localized:)` and
2
+ // `Bundle.localizedString(forKey:value:table:)` find them: the app's string
3
+ // tables are `<language>.lproj/<Table>.strings` and `.stringsdict` files in its
4
+ // bundle, which the CLI compiles from the Xcode target's string catalogs
5
+ // (`.xcstrings`) and classic `.strings` files, as Xcode does (packages/cli,
6
+ // strings.ts). Both are XML property lists: a `.strings` table maps keys to
7
+ // format strings, a `.stringsdict` maps keys to an `NSStringLocalizedFormatKey`
8
+ // whose `%#@name@` tokens a variable resolves (a plural rule).
9
+ //
10
+ // A key is looked up in the table of the language the app runs in (the
11
+ // viewer's, among the bundle's `.lproj` folders), then in the development
12
+ // language's; a key found in neither is its own text. Formatting follows
13
+ // `String(format:)` for the specifiers Xcode puts in keys (`%@`, `%lld`, `%lf`,
14
+ // `%d`, positional `%1$@`…), with each argument in the slot its specifier names.
15
+ // The engine lives in SwiftUI so that `Text` uses it; FoundationBridge's
16
+ // `Bundle` and `String(localized:)` call it too.
17
+
18
+ import Foundation
19
+
20
+ /// A value interpolated into a localized string: what its specifier formats.
21
+ public enum _LocalizationArgument: Hashable, Sendable, Codable {
22
+ case string(String)
23
+ case int(Int64)
24
+ case uint(UInt64)
25
+ case double(Double)
26
+
27
+ /// The number a plural rule counts (nil for a string that is no number).
28
+ var number: Double? {
29
+ switch self {
30
+ case .int(let value): Double(value)
31
+ case .uint(let value): Double(value)
32
+ case .double(let value): value
33
+ case .string(let value): Double(value)
34
+ }
35
+ }
36
+
37
+ var text: String {
38
+ switch self {
39
+ case .string(let value): value
40
+ case .int(let value): String(value)
41
+ case .uint(let value): String(value)
42
+ case .double(let value): value.rounded() == value && abs(value) < 1e15 ? String(Int64(value)) : String(value)
43
+ }
44
+ }
45
+ }
46
+
47
+ /// A number type and the specifier SwiftUI and Foundation write for it in a
48
+ /// localization key (`%lld` for `Int`): one generic interpolation takes them
49
+ /// all, as SwiftUI's does, which keeps type checking fast.
50
+ public protocol _FormatSpecifiable {
51
+ static var _specifier: String { get }
52
+ var _localizationArgument: _LocalizationArgument { get }
53
+ }
54
+
55
+ extension Int: _FormatSpecifiable {
56
+ public static var _specifier: String { "%lld" }
57
+ public var _localizationArgument: _LocalizationArgument { .int(Int64(self)) }
58
+ }
59
+ extension Int64: _FormatSpecifiable {
60
+ public static var _specifier: String { "%lld" }
61
+ public var _localizationArgument: _LocalizationArgument { .int(self) }
62
+ }
63
+ extension Int32: _FormatSpecifiable {
64
+ public static var _specifier: String { "%d" }
65
+ public var _localizationArgument: _LocalizationArgument { .int(Int64(self)) }
66
+ }
67
+ extension Int16: _FormatSpecifiable {
68
+ public static var _specifier: String { "%d" }
69
+ public var _localizationArgument: _LocalizationArgument { .int(Int64(self)) }
70
+ }
71
+ extension Int8: _FormatSpecifiable {
72
+ public static var _specifier: String { "%d" }
73
+ public var _localizationArgument: _LocalizationArgument { .int(Int64(self)) }
74
+ }
75
+ extension UInt: _FormatSpecifiable {
76
+ public static var _specifier: String { "%llu" }
77
+ public var _localizationArgument: _LocalizationArgument { .uint(UInt64(self)) }
78
+ }
79
+ extension UInt64: _FormatSpecifiable {
80
+ public static var _specifier: String { "%llu" }
81
+ public var _localizationArgument: _LocalizationArgument { .uint(self) }
82
+ }
83
+ extension UInt32: _FormatSpecifiable {
84
+ public static var _specifier: String { "%u" }
85
+ public var _localizationArgument: _LocalizationArgument { .uint(UInt64(self)) }
86
+ }
87
+ extension UInt16: _FormatSpecifiable {
88
+ public static var _specifier: String { "%u" }
89
+ public var _localizationArgument: _LocalizationArgument { .uint(UInt64(self)) }
90
+ }
91
+ extension UInt8: _FormatSpecifiable {
92
+ public static var _specifier: String { "%u" }
93
+ public var _localizationArgument: _LocalizationArgument { .uint(UInt64(self)) }
94
+ }
95
+ extension Double: _FormatSpecifiable {
96
+ public static var _specifier: String { "%lf" }
97
+ public var _localizationArgument: _LocalizationArgument { .double(self) }
98
+ }
99
+ extension Float: _FormatSpecifiable {
100
+ public static var _specifier: String { "%f" }
101
+ public var _localizationArgument: _LocalizationArgument { .double(Double(self)) }
102
+ }
103
+
104
+ /// A piece of a formatted localized string: text, or the argument (by index)
105
+ /// that a view takes the place of (an `Image` or `Text` in `Text("…\(image)")`).
106
+ public enum _LocalizedPiece: Equatable, Sendable {
107
+ case text(String)
108
+ case argument(Int)
109
+ }
110
+
111
+ /// A table entry: a format string, or a `.stringsdict` rule.
112
+ enum _LocalizedEntry: Equatable {
113
+ case format(String)
114
+ case rule(formatKey: String, variables: [String: [String: String]])
115
+ }
116
+
117
+ public enum _Localization {
118
+ /// Where the app's resources are mounted (`SB_BUNDLE_PATH`), as `Bundle.main` reads them.
119
+ public static var mainBundlePath: String { _environment("SB_BUNDLE_PATH") ?? "/bundle" }
120
+
121
+ // MARK: Languages
122
+
123
+ /// The `<language>.lproj` folders of a bundle (without `Base`), sorted;
124
+ /// read once per bundle (an app's bundle does not change while it runs).
125
+ public static func localizations(bundlePath: String) -> [String] {
126
+ cacheLock.lock()
127
+ defer { cacheLock.unlock() }
128
+ if let cached = languagesByBundle[bundlePath] { return cached }
129
+ let names = (try? FileManager.default.contentsOfDirectory(atPath: bundlePath)) ?? []
130
+ let found = names.filter { $0.hasSuffix(".lproj") }.map { String($0.dropLast(6)) }.filter { $0 != "Base" }.sorted()
131
+ languagesByBundle[bundlePath] = found
132
+ return found
133
+ }
134
+
135
+ /// The localization the app runs in: the viewer's language (`SB_LOCALE`)
136
+ /// matched against `available`, else the development localization.
137
+ public static func preferredLocalization(from available: [String]) -> String? {
138
+ preferredLocalizations(from: available, forPreferences: _environment("SB_LOCALE").map { [$0] }).first
139
+ }
140
+
141
+ /// The first of `available` that matches a preference, by whole tag
142
+ /// (`pt-BR`, case and `_` / `-` aside), then by script (`zh-Hant` for
143
+ /// `zh-TW`), then by language (`pt`); without a match, `en` when listed,
144
+ /// else the first.
145
+ public static func preferredLocalizations(from available: [String], forPreferences preferences: [String]?) -> [String] {
146
+ guard !available.isEmpty else { return [] }
147
+ func normalized(_ tag: String) -> String { tag.replacingOccurrences(of: "_", with: "-").lowercased() }
148
+ func language(_ tag: String) -> Substring { normalized(tag).split(separator: "-").first ?? "" }
149
+ for preference in preferences ?? [] {
150
+ if let exact = available.first(where: { normalized($0) == normalized(preference) }) { return [exact] }
151
+ if let script = chineseScript(preference), let match = available.first(where: { normalized($0) == "zh-\(script)" }) { return [match] }
152
+ if let close = available.first(where: { language($0) == language(preference) && !normalized($0).contains("-") }) { return [close] }
153
+ if let close = available.first(where: { language($0) == language(preference) }) { return [close] }
154
+ }
155
+ if available.contains("en") { return ["en"] }
156
+ return [available[0]]
157
+ }
158
+
159
+ /// `hant` for traditional Chinese (`zh-TW`, `zh-HK`, `zh-MO`, `zh-Hant…`), `hans` for the rest of `zh`.
160
+ private static func chineseScript(_ tag: String) -> String? {
161
+ let parts = tag.replacingOccurrences(of: "_", with: "-").lowercased().split(separator: "-")
162
+ guard parts.first == "zh" else { return nil }
163
+ if parts.contains("hant") || parts.contains("tw") || parts.contains("hk") || parts.contains("mo") { return "hant" }
164
+ return "hans"
165
+ }
166
+
167
+ /// The language the app's strings are written in. Xcode records it in the
168
+ /// app's Info.plist; the CLI ships its tables, and English is the default.
169
+ static func developmentLocalization(among available: [String]) -> String? {
170
+ available.contains("en") ? "en" : available.first
171
+ }
172
+
173
+ // MARK: Tables
174
+
175
+ /// Loaded tables and bundles' languages, behind a lock: views on any
176
+ /// thread look strings up (tests run in parallel on the host).
177
+ private static let cacheLock = NSLock()
178
+ nonisolated(unsafe) private static var tables: [String: [String: _LocalizedEntry]] = [:]
179
+ nonisolated(unsafe) private static var languagesByBundle: [String: [String]] = [:]
180
+
181
+ /// The entries of `<bundle>/<language>.lproj/<table>.strings` and `.stringsdict` (empty when absent).
182
+ static func table(_ name: String, language: String, bundlePath: String) -> [String: _LocalizedEntry] {
183
+ let base = "\(bundlePath)/\(language).lproj/\(name)"
184
+ cacheLock.lock()
185
+ defer { cacheLock.unlock() }
186
+ if let cached = tables[base] { return cached }
187
+ var entries: [String: _LocalizedEntry] = [:]
188
+ if let data = FileManager.default.contents(atPath: "\(base).strings"),
189
+ let strings = try? PropertyListDecoder().decode([String: String].self, from: data) {
190
+ for (key, value) in strings { entries[key] = .format(value) }
191
+ }
192
+ if let data = FileManager.default.contents(atPath: "\(base).stringsdict"),
193
+ let rules = try? PropertyListDecoder().decode([String: [String: _PlistValue]].self, from: data) {
194
+ for (key, rule) in rules {
195
+ guard case .string(let formatKey)? = rule["NSStringLocalizedFormatKey"] else { continue }
196
+ var variables: [String: [String: String]] = [:]
197
+ for (name, value) in rule where name != "NSStringLocalizedFormatKey" {
198
+ if case .dictionary(let cases) = value { variables[name] = cases }
199
+ }
200
+ entries[key] = .rule(formatKey: formatKey, variables: variables)
201
+ }
202
+ }
203
+ tables[base] = entries
204
+ return entries
205
+ }
206
+
207
+ /// Forgets the loaded tables and languages (tests write new ones).
208
+ public static func _resetTables() {
209
+ cacheLock.lock()
210
+ defer { cacheLock.unlock() }
211
+ tables = [:]
212
+ languagesByBundle = [:]
213
+ }
214
+
215
+ /// The entry for `key`: the running language's table, then the development language's.
216
+ /// `language` overrides the viewer's (a test's, or a resource's locale).
217
+ static func entry(forKey key: String, table: String?, bundlePath: String, language: String? = nil) -> (entry: _LocalizedEntry, language: String)? {
218
+ let available = localizations(bundlePath: bundlePath)
219
+ guard !available.isEmpty else { return nil }
220
+ let name = (table?.isEmpty ?? true) ? "Localizable" : table!
221
+ var languages: [String] = []
222
+ let preferred = language.flatMap { preferredLocalizations(from: available, forPreferences: [$0]).first } ?? preferredLocalization(from: available)
223
+ if let preferred { languages.append(preferred) }
224
+ if let development = developmentLocalization(among: available), !languages.contains(development) { languages.append(development) }
225
+ for language in languages {
226
+ if let entry = self.table(name, language: language, bundlePath: bundlePath)[key] { return (entry, language) }
227
+ }
228
+ return nil
229
+ }
230
+
231
+ /// The language plural rules follow: the one the app runs in.
232
+ static func runningLanguage(bundlePath: String) -> String {
233
+ preferredLocalization(from: localizations(bundlePath: bundlePath)) ?? _environment("SB_LOCALE") ?? "en"
234
+ }
235
+
236
+ // MARK: Resolving
237
+
238
+ /// `Bundle.localizedString(forKey:value:table:)`: the key's format string
239
+ /// (a rule's, with its tokens resolved for no particular count, as
240
+ /// `other`), else `value` when it is not empty, else the key.
241
+ public static func localizedString(forKey key: String, value: String?, table: String?, bundlePath: String) -> String {
242
+ switch entry(forKey: key, table: table, bundlePath: bundlePath) {
243
+ case (.format(let format), _)?: return format
244
+ case (.rule(let formatKey, let variables), let language)?:
245
+ return expand(formatKey, variables: variables, arguments: [], language: language)
246
+ case nil:
247
+ if let value, !value.isEmpty { return value }
248
+ return key
249
+ }
250
+ }
251
+
252
+ /// `key` localized and formatted with `arguments`: the pieces of text, and
253
+ /// the indices of the arguments (nil entries: views) that go between them.
254
+ /// A key with no arguments is not formatted (`"100%"` stays as it is).
255
+ /// `defaultValue` is the format when the tables lack the key (else the key itself).
256
+ public static func resolve(key: String, arguments: [_LocalizationArgument?], table: String? = nil,
257
+ bundlePath: String? = nil, defaultValue: String? = nil, language: String? = nil) -> [_LocalizedPiece] {
258
+ let path = bundlePath ?? mainBundlePath
259
+ let found = entry(forKey: key, table: table, bundlePath: path, language: language)
260
+ let format: String
261
+ switch found {
262
+ case (.format(let text), _)?: format = text
263
+ case (.rule(let formatKey, let variables), let language)?:
264
+ format = expand(formatKey, variables: variables, arguments: arguments, language: language)
265
+ case nil: format = defaultValue ?? key
266
+ }
267
+ if arguments.isEmpty { return [.text(format)] }
268
+ return _LocalizedFormat.format(format, arguments: arguments)
269
+ }
270
+
271
+ /// `resolve` as one string, views left out.
272
+ public static func string(key: String, arguments: [_LocalizationArgument?], table: String? = nil, bundlePath: String? = nil,
273
+ defaultValue: String? = nil, language: String? = nil) -> String {
274
+ resolve(key: key, arguments: arguments, table: table, bundlePath: bundlePath, defaultValue: defaultValue, language: language).map { piece in
275
+ if case .text(let text) = piece { text } else { "" }
276
+ }.joined()
277
+ }
278
+
279
+ /// `key` formatted with `arguments`, not looked up (a key with no arguments is its own text).
280
+ public static func formatted(key: String, arguments: [_LocalizationArgument]) -> String {
281
+ if arguments.isEmpty { return key }
282
+ return _LocalizedFormat.format(key, arguments: arguments).map { piece in
283
+ if case .text(let text) = piece { text } else { "" }
284
+ }.joined()
285
+ }
286
+
287
+ /// A rule's format key with each `%#@name@` token replaced by the case its
288
+ /// variable picks for the token's argument (plural rules), repeatedly, as
289
+ /// cases may hold tokens. Every specifier is numbered first, so a case's
290
+ /// own unnumbered specifiers take its token's argument and the others keep theirs.
291
+ static func expand(_ formatKey: String, variables: [String: [String: String]], arguments: [_LocalizationArgument?], language: String) -> String {
292
+ var format = _LocalizedFormat.numbered(formatKey)
293
+ for _ in 0..<8 {
294
+ guard let token = _LocalizedFormat.firstToken(in: format) else { break }
295
+ let variable = variables[token.name] ?? [:]
296
+ let argument = token.position - 1 < arguments.count ? arguments[token.position - 1] : nil
297
+ let chosen: String
298
+ if variable["NSStringFormatSpecTypeKey"] == "NSStringPluralRuleType" {
299
+ let number = argument?.number
300
+ let category = number.map { _PluralRules.category(for: $0, language: language) } ?? "other"
301
+ if number == 0, let zero = variable["zero"] {
302
+ chosen = zero
303
+ } else {
304
+ chosen = variable[category] ?? variable["other"] ?? ""
305
+ }
306
+ } else {
307
+ // A device rule (settled at compile time) or one the browser has no use for.
308
+ chosen = variable["iphone"] ?? variable["other"] ?? ""
309
+ }
310
+ let replacement = _LocalizedFormat.numbered(chosen, defaultPosition: token.position)
311
+ format.replaceSubrange(token.range, with: replacement)
312
+ }
313
+ return format
314
+ }
315
+ }
316
+
317
+ /// A property list value of a `.stringsdict`: a string or a dictionary of strings.
318
+ enum _PlistValue: Decodable {
319
+ case string(String)
320
+ case dictionary([String: String])
321
+ case other
322
+
323
+ init(from decoder: any Decoder) throws {
324
+ let container = try decoder.singleValueContainer()
325
+ if let string = try? container.decode(String.self) {
326
+ self = .string(string)
327
+ } else if let dictionary = try? container.decode([String: String].self) {
328
+ self = .dictionary(dictionary)
329
+ } else {
330
+ self = .other
331
+ }
332
+ }
333
+ }
334
+
335
+ // MARK: - Formatting
336
+
337
+ enum _LocalizedFormat {
338
+ /// One `%` specifier: its range, explicit position (`%2$@`), the rest of
339
+ /// the spec without the position (`-5`, `.2`, `ll`) and its conversion.
340
+ struct Specifier {
341
+ let range: Range<String.Index>
342
+ let position: Int?
343
+ let spec: String
344
+ let conversion: Character
345
+ /// `%#@name@`: a stringsdict token.
346
+ let token: String?
347
+ }
348
+
349
+ /// The specifiers of a format string, in order (`%%` left out).
350
+ static func specifiers(in format: String) -> [Specifier] {
351
+ var out: [Specifier] = []
352
+ var index = format.startIndex
353
+ while let percent = format[index...].firstIndex(of: "%") {
354
+ var cursor = format.index(after: percent)
355
+ guard cursor < format.endIndex else { break }
356
+ if format[cursor] == "%" {
357
+ index = format.index(after: cursor)
358
+ continue
359
+ }
360
+ // Position: digits followed by `$`.
361
+ var position: Int?
362
+ var digits = cursor
363
+ while digits < format.endIndex, format[digits].isASCII, format[digits].isNumber { digits = format.index(after: digits) }
364
+ if digits > cursor, digits < format.endIndex, format[digits] == "$" {
365
+ position = Int(format[cursor..<digits])
366
+ cursor = format.index(after: digits)
367
+ }
368
+ let specStart = cursor
369
+ if cursor < format.endIndex, format[cursor] == "#", format.index(after: cursor) < format.endIndex, format[format.index(after: cursor)] == "@" {
370
+ // `%#@name@`
371
+ let nameStart = format.index(cursor, offsetBy: 2)
372
+ if let end = format[nameStart...].firstIndex(of: "@") {
373
+ out.append(Specifier(range: percent..<format.index(after: end), position: position, spec: "",
374
+ conversion: "@", token: String(format[nameStart..<end])))
375
+ index = format.index(after: end)
376
+ continue
377
+ }
378
+ }
379
+ while cursor < format.endIndex, "-+ #0'".contains(format[cursor]) { cursor = format.index(after: cursor) }
380
+ while cursor < format.endIndex, format[cursor].isASCII, format[cursor].isNumber || format[cursor] == "*" { cursor = format.index(after: cursor) }
381
+ if cursor < format.endIndex, format[cursor] == "." {
382
+ cursor = format.index(after: cursor)
383
+ while cursor < format.endIndex, format[cursor].isASCII, format[cursor].isNumber { cursor = format.index(after: cursor) }
384
+ }
385
+ while cursor < format.endIndex, "hlqLztj".contains(format[cursor]) { cursor = format.index(after: cursor) }
386
+ guard cursor < format.endIndex else { break }
387
+ let conversion = format[cursor]
388
+ guard "@dDiuUxXoOfFeEgGaAcCsSp".contains(conversion) else {
389
+ index = format.index(after: cursor)
390
+ continue
391
+ }
392
+ out.append(Specifier(range: percent..<format.index(after: cursor), position: position,
393
+ spec: String(format[specStart..<cursor]), conversion: conversion, token: nil))
394
+ index = format.index(after: cursor)
395
+ }
396
+ return out
397
+ }
398
+
399
+ /// `format` with every unnumbered specifier numbered: in order from 1, or
400
+ /// all `defaultPosition` (a plural case's specifiers are its token's argument).
401
+ static func numbered(_ format: String, defaultPosition: Int? = nil) -> String {
402
+ var result = ""
403
+ var last = format.startIndex
404
+ var next = 1
405
+ for specifier in specifiers(in: format) {
406
+ result += format[last..<specifier.range.lowerBound]
407
+ let position = specifier.position ?? defaultPosition ?? next
408
+ if specifier.position == nil, defaultPosition == nil { next += 1 }
409
+ if let token = specifier.token {
410
+ result += "%\(position)$#@\(token)@"
411
+ } else {
412
+ result += "%\(position)$\(specifier.spec)\(specifier.conversion)"
413
+ }
414
+ last = specifier.range.upperBound
415
+ }
416
+ result += format[last...]
417
+ return result
418
+ }
419
+
420
+ static func firstToken(in format: String) -> (name: String, position: Int, range: Range<String.Index>)? {
421
+ for specifier in specifiers(in: format) {
422
+ if let token = specifier.token { return (token, specifier.position ?? 1, specifier.range) }
423
+ }
424
+ return nil
425
+ }
426
+
427
+ /// The pieces of `format` with its specifiers filled: unnumbered ones take
428
+ /// the arguments in order, numbered ones theirs; a view argument (nil)
429
+ /// becomes an `.argument` piece; `%%` is `%`.
430
+ static func format(_ format: String, arguments: [_LocalizationArgument?]) -> [_LocalizedPiece] {
431
+ var pieces: [_LocalizedPiece] = []
432
+ var text = ""
433
+ var last = format.startIndex
434
+ var next = 0
435
+ func literal(_ part: Substring) { text += part.replacingOccurrences(of: "%%", with: "%") }
436
+ for specifier in specifiers(in: format) {
437
+ literal(format[last..<specifier.range.lowerBound])
438
+ last = specifier.range.upperBound
439
+ let index: Int
440
+ if let position = specifier.position {
441
+ index = position - 1
442
+ } else {
443
+ index = next
444
+ next += 1
445
+ }
446
+ guard index >= 0, index < arguments.count else { continue }
447
+ guard let argument = arguments[index] else {
448
+ if !text.isEmpty { pieces.append(.text(text)); text = "" }
449
+ pieces.append(.argument(index))
450
+ continue
451
+ }
452
+ text += render(argument, spec: specifier.spec, conversion: specifier.conversion)
453
+ }
454
+ literal(format[last...])
455
+ if !text.isEmpty || pieces.isEmpty { pieces.append(.text(text)) }
456
+ return pieces
457
+ }
458
+
459
+ /// One argument as its specifier formats it.
460
+ static func render(_ argument: _LocalizationArgument, spec: String, conversion: Character) -> String {
461
+ let flags = spec.filter { !"hlqLztj".contains($0) }
462
+ switch conversion {
463
+ case "@", "s", "S":
464
+ return argument.text
465
+ case "d", "D", "i":
466
+ let value: Int64 = switch argument {
467
+ case .int(let v): v
468
+ case .uint(let v): Int64(truncatingIfNeeded: v)
469
+ case .double(let v): v.isFinite ? Int64(v) : 0
470
+ case .string(let v): Int64(v) ?? 0
471
+ }
472
+ return flags.isEmpty ? String(value) : String(format: "%\(flags)lld", value)
473
+ case "u", "U", "x", "X", "o", "O":
474
+ let value: UInt64 = switch argument {
475
+ case .int(let v): UInt64(bitPattern: v)
476
+ case .uint(let v): v
477
+ case .double(let v): v.isFinite && v >= 0 ? UInt64(v) : 0
478
+ case .string(let v): UInt64(v) ?? 0
479
+ }
480
+ let lower = Character(conversion.lowercased())
481
+ let code = lower == "u" ? "u" : String(conversion)
482
+ return String(format: "%\(flags)ll\(code)", value)
483
+ case "f", "F", "e", "E", "g", "G", "a", "A":
484
+ let value: Double = switch argument {
485
+ case .int(let v): Double(v)
486
+ case .uint(let v): Double(v)
487
+ case .double(let v): v
488
+ case .string(let v): Double(v) ?? 0
489
+ }
490
+ return String(format: "%\(flags)\(conversion)", value)
491
+ case "c", "C":
492
+ if case .int(let v) = argument, let scalar = Unicode.Scalar(UInt32(truncatingIfNeeded: v)) { return String(Character(scalar)) }
493
+ return argument.text
494
+ default:
495
+ return argument.text
496
+ }
497
+ }
498
+ }