From bb34cb3c46682afa184c2ba1b486c616630fa0f5 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 3 Sep 2026 20:09:06 +1200 Subject: [PATCH 1/7] Update wordpress-rs to 0.8.0 --- Modules/Package.resolved | 6 +++--- Modules/Package.swift | 2 +- Package.resolved | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/Package.resolved b/Modules/Package.resolved index b0266cb038a9..67f396fc5d67 100644 --- a/Modules/Package.resolved +++ b/Modules/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "290c677a1f25cc1f2edde128a8aeed0e05518150573f3f2a100ff9682e7528e5", + "originHash" : "99d98c14b91770de283a7383acb622184d4770999b658b05b7be89012876c82c", "pins" : [ { "identity" : "alamofire", @@ -336,8 +336,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/automattic/wordpress-rs", "state" : { - "revision" : "ce52fb236c0e9acef1ae60e8d4fba0f5fbd11b7d", - "version" : "0.7.0" + "revision" : "a9cf67e4895c0bcfad75c6f1e07206c22bfaf1bb", + "version" : "0.8.0" } }, { diff --git a/Modules/Package.swift b/Modules/Package.swift index 157fc629184e..8b79a1fb8816 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -66,7 +66,7 @@ let package = Package( .package(url: "https://github.com/wordpress-mobile/GutenbergKit", from: "0.19.0"), .package( url: "https://github.com/automattic/wordpress-rs", - exact: "0.7.0" + exact: "0.8.0" ), .package( url: "https://github.com/Automattic/color-studio", diff --git a/Package.resolved b/Package.resolved index 0e31d78f2a9a..959ce71fb8b4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -336,8 +336,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Automattic/wordpress-rs", "state" : { - "revision" : "ce52fb236c0e9acef1ae60e8d4fba0f5fbd11b7d", - "version" : "0.7.0" + "revision" : "a9cf67e4895c0bcfad75c6f1e07206c22bfaf1bb", + "version" : "0.8.0" } }, { From 85fafcb2978ec5bd7a8353f680d525f3a18f76b4 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 3 Sep 2026 20:09:06 +1200 Subject: [PATCH 2/7] Adopt the typed GMT dates from wordpress-rs 0.8.0 Application password created/lastUsed and user registeredDate now arrive as Date instead of strings, so drop the manual parsing. --- .../MockWordPressClientAPI.swift | 3 ++- .../Services/ApplicationPasswordService.swift | 15 ++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift b/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift index 894985eea66b..d81050f9e555 100644 --- a/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift +++ b/Modules/Tests/WordPressCoreTests/MockWordPressClientAPI.swift @@ -137,7 +137,8 @@ final class MockUsersRequestExecutor: UsersRequestExecutor, @unchecked Sendable locale: "en_US", nickname: "testuser", slug: "testuser", - registeredDate: "2024-01-01T00:00:00", + registeredDate: Date(timeIntervalSince1970: 1_704_067_200), // 2024-01-01 + roles: [], capabilities: UserCapabilitiesMap(map: [:]), extraCapabilities: UserCapabilitiesMap(map: [:]), diff --git a/WordPress/Classes/Services/ApplicationPasswordService.swift b/WordPress/Classes/Services/ApplicationPasswordService.swift index 009361a448d2..a389a8233235 100644 --- a/WordPress/Classes/Services/ApplicationPasswordService.swift +++ b/WordPress/Classes/Services/ApplicationPasswordService.swift @@ -29,7 +29,8 @@ extension ApplicationPasswordService: ApplicationTokenListDataProvider { } if self.currentApplicationPasswordUUID == nil { - self.currentApplicationPasswordUUID = try? await apiClient.api.applicationPasswords.retrieveCurrentWithViewContext().data.uuid.uuid + self.currentApplicationPasswordUUID = try? await apiClient.api.applicationPasswords + .retrieveCurrentWithViewContext().data.uuid.uuid } return try await fetchTokens(forUserId: userId) @@ -47,21 +48,17 @@ extension ApplicationPasswordService: ApplicationTokenListDataProvider { extension ApplicationTokenItem { init?(_ rawToken: ApplicationPasswordWithEditContext) { - guard - let uuid = UUID(uuidString: rawToken.uuid.uuid), - let createdAt = Date.fromWordPressDate(rawToken.created) - else { + guard let uuid = UUID(uuidString: rawToken.uuid.uuid) else { return nil } - let lastUsed = rawToken.lastUsed.flatMap(Date.fromWordPressDate(_:)) - + // wordpress-rs 0.8.0 delivers these as parsed `Date`s, not strings. self = ApplicationTokenItem( name: rawToken.name, uuid: uuid, appId: rawToken.appId.appId, - createdAt: createdAt, - lastUsed: lastUsed, + createdAt: rawToken.created, + lastUsed: rawToken.lastUsed, lastIpAddress: rawToken.lastIp?.value ) } From 06b309c692283f8b1738b3bdfa9dd975eda6dac2 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 3 Sep 2026 20:09:06 +1200 Subject: [PATCH 3/7] Handle the optional comment dateGmt from wordpress-rs 0.8.0 The field is nil only for a never-published comment, which the moderation list never shows, so fall back to distantPast. --- Modules/Sources/WordPressComments/Models/CommentDetail.swift | 4 ++-- .../Sources/WordPressComments/Models/CommentListItem.swift | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Modules/Sources/WordPressComments/Models/CommentDetail.swift b/Modules/Sources/WordPressComments/Models/CommentDetail.swift index 129b9054905d..58b3529aafcc 100644 --- a/Modules/Sources/WordPressComments/Models/CommentDetail.swift +++ b/Modules/Sources/WordPressComments/Models/CommentDetail.swift @@ -34,7 +34,7 @@ struct CommentDetail: Equatable, Sendable { parentID: comment.parent, contentHTML: comment.content.rendered, link: comment.link, - date: comment.dateGmt, + date: comment.dateGmt ?? .distantPast, status: CommentListItem.Status(comment.status), hasEditContext: false ) @@ -52,7 +52,7 @@ struct CommentDetail: Equatable, Sendable { parentID: comment.parent, contentHTML: comment.content.rendered, link: comment.link, - date: comment.dateGmt, + date: comment.dateGmt ?? .distantPast, status: CommentListItem.Status(comment.status), hasEditContext: true ) diff --git a/Modules/Sources/WordPressComments/Models/CommentListItem.swift b/Modules/Sources/WordPressComments/Models/CommentListItem.swift index 11329227dbe2..0ae0a727044f 100644 --- a/Modules/Sources/WordPressComments/Models/CommentListItem.swift +++ b/Modules/Sources/WordPressComments/Models/CommentListItem.swift @@ -46,7 +46,9 @@ struct CommentListItem: Identifiable, Equatable, Sendable { avatarURL = comment.authorAvatarUrls.avatarURL postID = comment.post snippet = Self.snippet(fromHTML: comment.content.rendered) - date = comment.dateGmt + // `dateGmt` became optional in wordpress-rs 0.8.0 (nil only for a + // never-published comment, which the moderation list never shows). + date = comment.dateGmt ?? .distantPast status = Status(comment.status) } From d48259d86bf795d4d6978a0142afc606ed44a278 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 3 Sep 2026 20:09:06 +1200 Subject: [PATCH 4/7] Wrap non-GMT date fixtures in WpDateString wordpress-rs 0.8.0 delivers the site-timezone date and modified fields as WpDateString instead of String. --- .../WordPressCommentsTests/Support/CommentBuilders.swift | 6 +++--- .../Tests/Features/Posts/CustomPostEditorServiceTests.swift | 4 ++-- .../Features/Posts/CustomPostSettingsViewModelTests.swift | 4 ++-- .../Tests/Features/Posts/PostSettingsTests.swift | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift b/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift index b83b5e214cba..27e6bba611a1 100644 --- a/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift +++ b/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift @@ -18,7 +18,7 @@ func makeComment( authorName: authorName, authorUrl: "", content: CommentContentWithViewContext(rendered: content), - date: "2023-11-14T22:13:20", + date: WpDateString(value: "2023-11-14T22:13:20"), dateGmt: date, link: "https://example.com/?p=\(post)#comment-\(id)", parent: 0, @@ -57,7 +57,7 @@ extension CommentWithViewContext { authorName: authorName, authorUrl: authorUrl, content: CommentContentWithViewContext(rendered: content), - date: "2023-11-14T22:13:20", + date: WpDateString(value: "2023-11-14T22:13:20"), dateGmt: date, link: "https://example.com/?p=\(post)#comment-\(id)", parent: parent, @@ -93,7 +93,7 @@ extension CommentWithEditContext { authorUrl: authorUrl, authorUserAgent: "", content: CommentContentWithEditContext(raw: content, rendered: content), - date: "2023-11-14T22:13:20", + date: WpDateString(value: "2023-11-14T22:13:20"), dateGmt: date, link: "https://example.com/?p=\(post)#comment-\(id)", parent: parent, diff --git a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift index 5277be0bc4c2..a4050541e664 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostEditorServiceTests.swift @@ -358,11 +358,11 @@ private func makeRemotePost( ) -> AnyPostWithEditContext { AnyPostWithEditContext( id: PostId(1), - date: "2025-01-01T00:00:00", + date: WpDateString(value: "2025-01-01T00:00:00"), dateGmt: Date(timeIntervalSince1970: 0), guid: PostGuidWithEditContext(raw: nil, rendered: ""), link: "https://example.com", - modified: "2025-01-01T00:00:00", + modified: WpDateString(value: "2025-01-01T00:00:00"), modifiedGmt: Date(timeIntervalSince1970: 0), slug: "test-post", status: .draft, diff --git a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift index cbf93ccfa1db..55eb9f14bb03 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/CustomPostSettingsViewModelTests.swift @@ -247,11 +247,11 @@ private func makePostWithDisabledConnection( let additionalFields = try WpAdditionalFields.fromJsonString(json: json) return AnyPostWithEditContext( id: PostId(1), - date: "2025-01-01T00:00:00", + date: WpDateString(value: "2025-01-01T00:00:00"), dateGmt: Date(timeIntervalSince1970: 0), guid: PostGuidWithEditContext(raw: nil, rendered: ""), link: "https://example.com", - modified: "2025-01-01T00:00:00", + modified: WpDateString(value: "2025-01-01T00:00:00"), modifiedGmt: Date(timeIntervalSince1970: 0), slug: "test-post", status: status, diff --git a/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift b/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift index b5b795fd5a8b..948187ca1737 100644 --- a/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift +++ b/Tests/KeystoneTests/Tests/Features/Posts/PostSettingsTests.swift @@ -1484,11 +1484,11 @@ private func makeRemotePost( ) -> AnyPostWithEditContext { AnyPostWithEditContext( id: PostId(1), - date: "2025-01-01T00:00:00", + date: WpDateString(value: "2025-01-01T00:00:00"), dateGmt: Date(timeIntervalSince1970: 0), guid: PostGuidWithEditContext(raw: nil, rendered: ""), link: "https://example.com", - modified: "2025-01-01T00:00:00", + modified: WpDateString(value: "2025-01-01T00:00:00"), modifiedGmt: Date(timeIntervalSince1970: 0), slug: "test-post", status: .draft, From 5ab8454bb6e1ad1d6c250b720eb85b633ff04ef8 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Thu, 3 Sep 2026 19:37:01 +1200 Subject: [PATCH 5/7] Send the device language on WordPress.com requests wordpress-rs 0.8.0 moved the WordPress.com locale from per-endpoint params to a client-level language provider. Register one on both WP.com clients so responses are localized to the user's preferred language. The provider maps the language through the wordpress-rs table rather than the bundled Languages.json, so regional variants resolve and unsupported languages send no locale. --- .../Login/SelfHostedSiteAuthenticator.swift | 2 +- .../Networking/JetpackSocialFactory.swift | 9 +++++++-- .../Networking/WordPressDotComClient.swift | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift index 2fbc6010f3b6..13b303b97ad8 100644 --- a/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift +++ b/WordPress/Classes/Login/SelfHostedSiteAuthenticator.swift @@ -678,7 +678,7 @@ private extension SelfHostedSiteAuthenticator { } } -private final class EmptyAppNotifier: WpAppNotifier { +final class EmptyAppNotifier: WpAppNotifier { func requestedWithInvalidAuthentication(requestUrl: String) async { // Do nothing. } diff --git a/WordPress/Classes/Networking/JetpackSocialFactory.swift b/WordPress/Classes/Networking/JetpackSocialFactory.swift index 4d4d34411644..172c4672366a 100644 --- a/WordPress/Classes/Networking/JetpackSocialFactory.swift +++ b/WordPress/Classes/Networking/JetpackSocialFactory.swift @@ -34,8 +34,13 @@ public final class JetpackSocialFactory: Sendable { } let service = SiteSocialConnectionsService( client: WPComApiClient( - urlSession: URLSession(configuration: .ephemeral), - authentication: configuration.authentication + delegate: WpApiClientDelegate( + authProvider: .staticWithAuth(auth: configuration.authentication), + requestExecutor: WpRequestExecutor(urlSession: URLSession(configuration: .ephemeral)), + middlewarePipeline: .default, + appNotifier: EmptyAppNotifier(), + languageProvider: WPComDeviceLanguageProvider() + ) ), siteId: configuration.siteId, canMarkAsShared: canMarkAsShared diff --git a/WordPress/Classes/Networking/WordPressDotComClient.swift b/WordPress/Classes/Networking/WordPressDotComClient.swift index 8fa6f662235d..be85ea01977b 100644 --- a/WordPress/Classes/Networking/WordPressDotComClient.swift +++ b/WordPress/Classes/Networking/WordPressDotComClient.swift @@ -21,7 +21,8 @@ actor WordPressDotComClient: MediaHostProtocol { middlewarePipeline: WpApiMiddlewarePipeline(middlewares: [ WpComTrafficDebugger() ]), - appNotifier: WpComNotifier() + appNotifier: WpComNotifier(), + languageProvider: WPComDeviceLanguageProvider() ) self.api = WPComApiClient(delegate: delegate) @@ -173,3 +174,16 @@ extension RequestMethod: @retroactive CustomStringConvertible { } } } + +/// Asks WordPress.com to localize responses to the user's preferred language. +/// +/// Reads the same source as `WordPressComRestApi` but maps it through the wordpress-rs language +/// table, so regional variants such as `en-gb` resolve and unsupported languages send no locale. +final class WPComDeviceLanguageProvider: WpComLanguageProvider { + func currentLanguage() -> WpComLanguage? { + guard let identifier = Locale.preferredLanguages.first else { + return nil + } + return WpComLanguage(locale: Locale(identifier: identifier)) + } +} From b36187488d4b5439416f684c6bb876e5b2d17574 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 4 Sep 2026 09:05:51 +1200 Subject: [PATCH 6/7] Make the comment date optional instead of defaulting it wordpress-rs 0.8.0 delivers the comment's GMT date as optional. Carry nil through CommentListItem, CommentDetail, and the detail header instead of substituting distantPast, and omit the date text in the row, author header, and info sheet when it is missing. --- .../WordPressComments/Models/CommentDetail.swift | 8 ++++---- .../WordPressComments/Models/CommentListItem.swift | 8 +++----- .../ViewModels/CommentDetailViewModel.swift | 2 +- .../WordPressComments/Views/CommentRowView.swift | 8 +++++--- .../Views/Detail/CommentAuthorHeader.swift | 12 ++++++++---- .../WordPressCommentsTests/CommentDetailTests.swift | 5 +++++ .../CommentListItemTests.swift | 5 +++++ .../Support/CommentBuilders.swift | 6 +++--- 8 files changed, 34 insertions(+), 20 deletions(-) diff --git a/Modules/Sources/WordPressComments/Models/CommentDetail.swift b/Modules/Sources/WordPressComments/Models/CommentDetail.swift index 58b3529aafcc..a9e12ad959b1 100644 --- a/Modules/Sources/WordPressComments/Models/CommentDetail.swift +++ b/Modules/Sources/WordPressComments/Models/CommentDetail.swift @@ -16,7 +16,7 @@ struct CommentDetail: Equatable, Sendable { let parentID: Int64? // nil when the wire value is 0 (top-level) let contentHTML: String let link: URL? - let date: Date + let date: Date? var status: CommentListItem.Status /// False when the fetch fell back to view context (no email/IP; M3 edit /// needs content.raw, also unavailable). @@ -34,7 +34,7 @@ struct CommentDetail: Equatable, Sendable { parentID: comment.parent, contentHTML: comment.content.rendered, link: comment.link, - date: comment.dateGmt ?? .distantPast, + date: comment.dateGmt, status: CommentListItem.Status(comment.status), hasEditContext: false ) @@ -52,7 +52,7 @@ struct CommentDetail: Equatable, Sendable { parentID: comment.parent, contentHTML: comment.content.rendered, link: comment.link, - date: comment.dateGmt ?? .distantPast, + date: comment.dateGmt, status: CommentListItem.Status(comment.status), hasEditContext: true ) @@ -69,7 +69,7 @@ struct CommentDetail: Equatable, Sendable { parentID: Int64, contentHTML: String, link: String, - date: Date, + date: Date?, status: CommentListItem.Status, hasEditContext: Bool ) { diff --git a/Modules/Sources/WordPressComments/Models/CommentListItem.swift b/Modules/Sources/WordPressComments/Models/CommentListItem.swift index 0ae0a727044f..bda56781b00a 100644 --- a/Modules/Sources/WordPressComments/Models/CommentListItem.swift +++ b/Modules/Sources/WordPressComments/Models/CommentListItem.swift @@ -19,7 +19,7 @@ struct CommentListItem: Identifiable, Equatable, Sendable { let avatarURL: URL? let postID: Int64 let snippet: String - let date: Date + let date: Date? var status: Status init( @@ -28,7 +28,7 @@ struct CommentListItem: Identifiable, Equatable, Sendable { avatarURL: URL?, postID: Int64, snippet: String, - date: Date, + date: Date?, status: Status ) { self.id = id @@ -46,9 +46,7 @@ struct CommentListItem: Identifiable, Equatable, Sendable { avatarURL = comment.authorAvatarUrls.avatarURL postID = comment.post snippet = Self.snippet(fromHTML: comment.content.rendered) - // `dateGmt` became optional in wordpress-rs 0.8.0 (nil only for a - // never-published comment, which the moderation list never shows). - date = comment.dateGmt ?? .distantPast + date = comment.dateGmt status = Status(comment.status) } diff --git a/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift b/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift index 5a1ede3f41ae..bb0be5109352 100644 --- a/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift +++ b/Modules/Sources/WordPressComments/ViewModels/CommentDetailViewModel.swift @@ -28,7 +28,7 @@ final class CommentDetailViewModel: ObservableObject { let authorName: String let avatarURL: URL? let postID: Int64 - let date: Date + let date: Date? let status: CommentListItem.Status } diff --git a/Modules/Sources/WordPressComments/Views/CommentRowView.swift b/Modules/Sources/WordPressComments/Views/CommentRowView.swift index 3fce673585f6..34cc266de872 100644 --- a/Modules/Sources/WordPressComments/Views/CommentRowView.swift +++ b/Modules/Sources/WordPressComments/Views/CommentRowView.swift @@ -25,9 +25,11 @@ struct CommentRowView: View { .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(2) - Text(item.date, format: .relative(presentation: .named)) - .font(.caption) - .foregroundStyle(.secondary) + if let date = item.date { + Text(date, format: .relative(presentation: .named)) + .font(.caption) + .foregroundStyle(.secondary) + } } } .accessibilityElement(children: .combine) diff --git a/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift b/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift index fa112bc50239..b8fa720e9aa8 100644 --- a/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift +++ b/Modules/Sources/WordPressComments/Views/Detail/CommentAuthorHeader.swift @@ -31,9 +31,11 @@ struct CommentAuthorHeader: View { Text(header.authorName) .font(.subheadline.weight(.semibold)) postLine - Text(header.date, format: .relative(presentation: .named)) - .font(.caption) - .foregroundStyle(.secondary) + if let date = header.date { + Text(date, format: .relative(presentation: .named)) + .font(.caption) + .foregroundStyle(.secondary) + } } Spacer(minLength: 0) Image(systemName: "info.circle") @@ -73,7 +75,9 @@ private struct CommentAuthorInfoSheet: View { var body: some View { NavigationStack { List { - LabeledContent(Strings.infoDateLabel, value: header.date.formatted(.dateTime)) + if let date = header.date { + LabeledContent(Strings.infoDateLabel, value: date.formatted(.dateTime)) + } if let url = detail?.authorURL { Link(destination: url) { LabeledContent(Strings.infoWebsiteLabel, value: url.absoluteString) diff --git a/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift b/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift index 2c77e3519586..13f56b1294d1 100644 --- a/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift +++ b/Modules/Tests/WordPressCommentsTests/CommentDetailTests.swift @@ -28,4 +28,9 @@ struct CommentDetailTests { let detail = CommentDetail(comment: .detailBuilder(authorName: "")) #expect(detail.authorName == Strings.anonymousAuthor) } + + @Test func missingDateMapsToNil() { + let detail = CommentDetail(comment: .detailBuilder(date: nil)) + #expect(detail.date == nil) + } } diff --git a/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift b/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift index 99628662deaf..ca34c72b3d08 100644 --- a/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift +++ b/Modules/Tests/WordPressCommentsTests/CommentListItemTests.swift @@ -30,6 +30,11 @@ struct CommentListItemTests { #expect(item.avatarURL == nil) } + @Test func missingDateMapsToNil() { + let item = CommentListItem(comment: makeComment(date: nil)) + #expect(item.date == nil) + } + @Test func statusMapping() { #expect(CommentListItem(comment: makeComment(status: .approved)).status == .approved) #expect(CommentListItem(comment: makeComment(status: .spam)).status == .spam) diff --git a/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift b/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift index 27e6bba611a1..1e0c00fd0df0 100644 --- a/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift +++ b/Modules/Tests/WordPressCommentsTests/Support/CommentBuilders.swift @@ -10,7 +10,7 @@ func makeComment( content: String = "

Hello world

", post: Int64 = 10, status: CommentStatus = .approved, - date: Date = Date(timeIntervalSince1970: 1_700_000_000) + date: Date? = Date(timeIntervalSince1970: 1_700_000_000) ) -> CommentWithViewContext { CommentWithViewContext( id: id, @@ -49,7 +49,7 @@ extension CommentWithViewContext { post: Int64 = 10, parent: Int64 = 0, status: CommentStatus = .approved, - date: Date = Date(timeIntervalSince1970: 1_700_000_000) + date: Date? = Date(timeIntervalSince1970: 1_700_000_000) ) -> CommentWithViewContext { CommentWithViewContext( id: id, @@ -82,7 +82,7 @@ extension CommentWithEditContext { post: Int64 = 10, parent: Int64 = 0, status: CommentStatus = .approved, - date: Date = Date(timeIntervalSince1970: 1_700_000_000) + date: Date? = Date(timeIntervalSince1970: 1_700_000_000) ) -> CommentWithEditContext { CommentWithEditContext( id: id, From 4ffe4a1feed21d2f197406b67a8daec93dd6f6d9 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 4 Sep 2026 09:09:11 +1200 Subject: [PATCH 7/7] Pick the first supported language from all preferred languages Walk Locale.preferredLanguages in order instead of stopping at the first entry, so a user whose top language WordPress.com does not support still gets their next preference rather than no locale. --- .../Networking/WordPressDotComClient.swift | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/WordPress/Classes/Networking/WordPressDotComClient.swift b/WordPress/Classes/Networking/WordPressDotComClient.swift index be85ea01977b..7c892fd93e1d 100644 --- a/WordPress/Classes/Networking/WordPressDotComClient.swift +++ b/WordPress/Classes/Networking/WordPressDotComClient.swift @@ -175,15 +175,18 @@ extension RequestMethod: @retroactive CustomStringConvertible { } } -/// Asks WordPress.com to localize responses to the user's preferred language. +/// Asks WordPress.com to localize responses to the first of the user's preferred languages that +/// WordPress.com supports. /// -/// Reads the same source as `WordPressComRestApi` but maps it through the wordpress-rs language -/// table, so regional variants such as `en-gb` resolve and unsupported languages send no locale. +/// Maps through the wordpress-rs language table, so regional variants such as `en-gb` resolve. +/// Sends no locale when none of the preferred languages is supported. final class WPComDeviceLanguageProvider: WpComLanguageProvider { func currentLanguage() -> WpComLanguage? { - guard let identifier = Locale.preferredLanguages.first else { - return nil + for identifier in Locale.preferredLanguages { + if let language = WpComLanguage(locale: Locale(identifier: identifier)) { + return language + } } - return WpComLanguage(locale: Locale(identifier: identifier)) + return nil } }