diff --git a/Podfile.lock b/Podfile.lock index f4440e678747..1054a42eb947 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -71,7 +71,7 @@ PODS: - WordPressKit (~> 17.0) - WordPressShared (~> 2.1-beta) - WordPressUI (~> 1.7-beta) - - WordPressKit (17.1.0): + - WordPressKit (17.1.1): - NSObject-SafeExpectations (~> 0.0.4) - UIDeviceIdentifier (~> 2.0) - WordPressShared (~> 2.0-beta) @@ -132,6 +132,7 @@ SPEC REPOS: - WordPress-Aztec-iOS - WordPress-Editor-iOS - WordPressAuthenticator + - WordPressKit - WordPressShared trunk: - Alamofire @@ -160,7 +161,6 @@ SPEC REPOS: - SVProgressHUD - SwiftLint - UIDeviceIdentifier - - WordPressKit - WordPressUI - wpxmlrpc - ZendeskCommonUISDK @@ -216,7 +216,7 @@ SPEC CHECKSUMS: WordPress-Aztec-iOS: 3732c6d865a5c9f35788377bdeda8a80ea10d0a1 WordPress-Editor-iOS: 453345420ced3d3ef20f0051b3df46ff10281e0c WordPressAuthenticator: 898acaac75c5ade9b900c02622a15b9aef8fde1a - WordPressKit: 4042625f32513a98fe1bc8552f1aa59ef236bcdc + WordPressKit: a4af3d5d071734aaec75ccecefc7850ece6068fc WordPressShared: 0160364ed24f4d67fed4e85003fefa837faad84f WordPressUI: ec5ebcf7e63e797ba51d07513e340c1b14cf45a4 wpxmlrpc: 68db063041e85d186db21f674adf08d9c70627fd diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 6d5d65bf54fd..f55f786a0085 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,28 @@ * [*] Add "Parent Page" to "Page Settings" [#23136] * [*] Add "Slug" and "Excerpt" fields to "Page Settings" [#23135] * [*] Make it easier to "Share" and "Blaze" a published post with an updated success view [##23128] +* [*] Add support for viewing trashed posts and pages and restoring them from the editor [#23142] +* [***] [Jetpack-only] Reorganized Stats to include updated Traffic and Insights tabs, along with a newly added Subscribers tab to improve subscriber metrics analysis [#23159] +* [*] Impove the "Post Settings" screen groups/ordering to better align with Gutenberg [#23164] +* [*] Update the "More" menu in the Editor to use modern iOS design and update copy to match Gutenberg [#23145] +* [*] Update the "Revisions" list design and fix an issue with the footer displaying incorrect "Date Created" for drafts [#23145] +* [***] Fix multiple post sync issues: [#21895], [#22037], [#8111], [#10168], [#17343], [#14221], [#12800], [#12073], [#14572], [#9319], [#21941], [#3070], [#3978], [#9449], [#13023], [#21472] +* [**] Fix [#4870], [#14798], an issue where the app sometimes overwrites the changes made on the remote. The app will now show a new "Conflict Resolution" screen if there is a conflict. It will also no longer try to auto-upload changes to an existing published post or publish a draft, eliminating multiple possible failure points. +* [**] Fix [#12121], [#13724], [#14251], [#18517], [#17086], [#15767], [#16514], [#13654] and other untracked issues with the "Publish Date" field in Post Settings. The app now has a new date picker that makes it easier to pick and remove the selected date. It also makes it clear if the blog is in a different time zones from your local timezone. The "Publish Date" field was also removed from the Post Settings screen for draft posts – the date needs to be selected right before publishing. +* [**] Add a new "Media Uploads" screen to the "Publishing Sheet" where you can see the status of the pending uploads. It also shows error messages for each individual upload (if any) and allows you to cancel the failing uploads. +* [*] Fix [#21940], [#13432], [#11435], issues where "Discard Changes" would sometimes delete the entire draft or appear when no changes were made +* [*] Fix [#12099] by removing the "Status" field from Post Settings that was leading to multiple unexpected and confusing scenarios. The app now manages the transitions between state the same way as Gutenberg, so it should feel more familiar to the users. +* [*] Fix [#10663], an issue with unclear error messages. The messages for "synchronous" operations will not appear as alerts with detailed information about the error. The error for drafts will also appear more clearly. +* [*] Update the "Autosave Available" dialog to work the same way as in Gutenberg and to better represent what this feature is – it can no longer be confused with a "Data Conflict" dialog (fixes [#13093]) +* [*] Fix [#22107], [#19540], [#13632] by removing the "Draft Uploaded" snackbar +* [*] Fix editor state restoration in the Jetpack app +* [*] Update the support for pending posts to have clearer flows for both admins and contributors +* [*] Fix [#19886], an issue with a missing error message when selecting a page author that's not eligible to be a page author +* [*] Fix [#22969] an issue where Post List content occasionally stops updating +* [*] Fix [#3862] by adding a "Password Protected" badge in [#23154] +* [*] Fix [#21093], a rare crash in "View Stats for Posts" – [#23155] +* [*] Fix [#22247], a rare crash in Post List +* [*] Update the app to use `wp.createPost` and `wp.editPost` instead of the respective deprecated `metaWeblog.*` methods (closes [#1385]) 24.8 ----- diff --git a/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift b/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift index d2308bef2a56..e90449a4563a 100644 --- a/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift +++ b/WordPress/Classes/Models/AbstractPost+TitleForVisibility.swift @@ -6,9 +6,15 @@ extension AbstractPost { static let publicLabel = NSLocalizedString("Public", comment: "Privacy setting for posts set to 'Public' (default). Should be the same as in core WP.") /// A title describing the status. Ie.: "Public" or "Private" or "Password protected" - /// - /// - warning: deprecated (kahu-offline-mode) (use ``PostVisibility``) @objc var titleForVisibility: String { + guard FeatureFlag.syncPublishing.enabled else { + return _titleForVisibility + } + return PostVisibility(post: self).localizedTitle + } + + /// - warning: deprecated (kahu-offline-mode) (use ``PostVisibility``) + @objc private var _titleForVisibility: String { if password != nil { return AbstractPost.passwordProtectedLabel } else if status == .publishPrivate { diff --git a/WordPress/Classes/Models/AbstractPost.swift b/WordPress/Classes/Models/AbstractPost.swift index 569c5bdb1c7f..3e020ecf5943 100644 --- a/WordPress/Classes/Models/AbstractPost.swift +++ b/WordPress/Classes/Models/AbstractPost.swift @@ -132,6 +132,15 @@ extension AbstractPost { } } + var localizedPostType: String { + switch self { + case is Page: + return NSLocalizedString("postType.page", value: "page", comment: "Localized post type: `Page`") + default: + return NSLocalizedString("postType.post", value: "post", comment: "Localized post type: `Post`") + } + } + // MARK: - Misc /// Represent the supported properties used to sort posts. diff --git a/WordPress/Classes/Models/Notifications/Actions/Follow.swift b/WordPress/Classes/Models/Notifications/Actions/Follow.swift index 7b2aea8c89d6..ff908b6091cd 100644 --- a/WordPress/Classes/Models/Notifications/Actions/Follow.swift +++ b/WordPress/Classes/Models/Notifications/Actions/Follow.swift @@ -1,9 +1,9 @@ /// Encapsulates logic to follow a blog final class Follow: DefaultNotificationActionCommand { - static let title = NSLocalizedString("Follow", comment: "Prompt to follow a blog.") - static let hint = NSLocalizedString("Follows the blog.", comment: "VoiceOver accessibility hint, informing the user the button can be used to follow a blog.") - static let selectedTitle = NSLocalizedString("Following", comment: "User is following the blog.") - static let selectedHint = NSLocalizedString("Unfollows the blog.", comment: "VoiceOver accessibility hint, informing the user the button can be used to unfollow a blog.") + static let title = NSLocalizedString("notifications.action.subscribe.title", value: "Subscribe", comment: "Prompt to subscribe to a blog.") + static let hint = NSLocalizedString("notifications.action.subscribe.hint", value: "Subscribe to the blog.", comment: "VoiceOver accessibility hint, informing the user the button can be used to subscribe to a blog.") + static let selectedTitle = NSLocalizedString("notifications.action.subscribe.selectedTitle", value: "Subscribed", comment: "User is subscribed to the blog.") + static let selectedHint = NSLocalizedString("notifications.action.subscribe.selectedHint", value: "Unsubscribe from the blog.", comment: "VoiceOver accessibility hint, informing the user the button can be used to unsubscribe from a blog.") override var actionTitle: String { return Follow.title diff --git a/WordPress/Classes/Models/Notifications/NotificationSettings.swift b/WordPress/Classes/Models/Notifications/NotificationSettings.swift index 4ca3c5c0990d..347dc992c543 100644 --- a/WordPress/Classes/Models/Notifications/NotificationSettings.swift +++ b/WordPress/Classes/Models/Notifications/NotificationSettings.swift @@ -173,8 +173,9 @@ open class NotificationSettings { comment: "Setting: indicates if Comment Likes will be notified"), postLiked: NSLocalizedString("Likes on my posts", comment: "Setting: indicates if Replies to your comments will be notified"), - follower: NSLocalizedString("Site follows", - comment: "Setting: indicates if New Follows will be notified"), + follower: NSLocalizedString("notification.settings.description.subscriber", + value: "Site subscriptions", + comment: "Setting: indicates if New Subscriptions will be notified"), achievement: NSLocalizedString("Site achievements", comment: "Setting: indicates if Achievements will be notified"), mention: NSLocalizedString("Username mentions", diff --git a/WordPress/Classes/Models/Role.swift b/WordPress/Classes/Models/Role.swift index 9bd0615bea19..c414a1b5c7f2 100644 --- a/WordPress/Classes/Models/Role.swift +++ b/WordPress/Classes/Models/Role.swift @@ -21,18 +21,3 @@ extension Role { return context.firstObject(ofType: Role.self, matching: predicate) } } - -extension Role { - @objc var color: UIColor { - switch slug { - case .some("super-admin"): - return WPStyleGuide.People.superAdminColor - case .some("administrator"): - return WPStyleGuide.People.adminColor - case .some("editor"): - return WPStyleGuide.People.editorColor - default: - return WPStyleGuide.People.otherRoleColor - } - } -} diff --git a/WordPress/Classes/Services/PeopleService.swift b/WordPress/Classes/Services/PeopleService.swift index 04f6e6adaef8..dd8520c3ae42 100644 --- a/WordPress/Classes/Services/PeopleService.swift +++ b/WordPress/Classes/Services/PeopleService.swift @@ -76,31 +76,6 @@ struct PeopleService { }) } - /// Loads a page of Email Followers associated to the current blog, starting at the specified offset. - /// - /// - Parameters: - /// - offset: Number of records to skip. - /// - count: Number of records to retrieve. By default set to 20. - /// - success: Closure to be executed on success with the number of followers retrieved and a bool indicating if more are available. - /// - failure: Closure to be executed on failure. - /// - func loadEmailFollowersPage(_ offset: Int = 0, - count: Int = 20, - success: @escaping ((_ retrieved: Int, _ shouldLoadMore: Bool) -> Void), - failure: ((Error) -> Void)? = nil) { - let page = (offset / count) + 1 - remote.getEmailFollowers(siteID, page: page, max: count, success: { followers, hasMore in - self.coreDataStack.performAndSave({ context in - self.mergePeople(followers, in: context) - }, completion: { - success(followers.count, hasMore) - }, on: .main) - }, failure: { error in - DDLogError(String(describing: error)) - failure?(error) - }) - } - /// Loads a page of Viewers associated to the current blog, starting at the specified offset. /// /// - Parameters: diff --git a/WordPress/Classes/Services/PostCoordinator.swift b/WordPress/Classes/Services/PostCoordinator.swift index dd5b0aa88980..2ea04cc4724e 100644 --- a/WordPress/Classes/Services/PostCoordinator.swift +++ b/WordPress/Classes/Services/PostCoordinator.swift @@ -256,7 +256,7 @@ class PostCoordinator: NSObject { } } - private func handleError(_ error: Error, for post: AbstractPost) { + func handleError(_ error: Error, for post: AbstractPost) { guard let topViewController = UIApplication.shared.mainWindow?.topmostPresentedViewController else { wpAssertionFailure("Failed to show an error alert") return @@ -332,6 +332,16 @@ class PostCoordinator: NSObject { save(post) } + /// Restores a trashed post by moving it to draft. + @MainActor + func restore(_ post: AbstractPost) async throws { + wpAssert(post.isOriginal()) + + var changes = RemotePostUpdateParameters() + changes.status = Post.Status.draft.rawValue + try await _update(post, changes: changes) + } + /// Sets the post state to "updating" and performs the given changes. private func _performChanges(_ changes: RemotePostUpdateParameters, for post: AbstractPost) { Task { @MainActor in @@ -442,6 +452,9 @@ class PostCoordinator: NSObject { retryDelay = min(32, retryDelay * 1.5) return retryDelay } + func setLongerDelay() { + retryDelay = max(retryDelay, 20) + } var retryDelay: TimeInterval weak var retryTimer: Timer? @@ -499,10 +512,11 @@ class PostCoordinator: NSObject { } private func startSync(for post: AbstractPost) { - guard let revision = post.getLatestRevisionNeedingSync() else { - let worker = getWorker(for: post) + if let worker = workers[post.objectID], worker.error != nil { worker.error = nil postDidUpdateNotification(for: post) + } + guard let revision = post.getLatestRevisionNeedingSync() else { return DDLogInfo("sync: \(post.objectID.shortDescription) is already up to date") } startSync(for: post, revision: revision) @@ -600,14 +614,16 @@ class PostCoordinator: NSObject { worker.error = error postDidUpdateNotification(for: operation.post) - if !PostCoordinator.isTerminalError(error) { - let delay = worker.nextRetryDelay - worker.retryTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self, weak worker] _ in - guard let self, let worker else { return } - self.didRetryTimerFire(for: worker) - } - worker.log("scheduled retry with delay: \(delay)s.") + if PostCoordinator.isTerminalError(error) { + worker.setLongerDelay() + } + + let delay = worker.nextRetryDelay + worker.retryTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self, weak worker] _ in + guard let self, let worker else { return } + self.didRetryTimerFire(for: worker) } + worker.log("scheduled retry with delay: \(delay)s.") if let error = error as? PostRepository.PostSaveError, case .deleted = error { operation.log("post was permanently deleted") diff --git a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift index 6dc775a463d6..7b15048bc74a 100644 --- a/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift +++ b/WordPress/Classes/Utility/Analytics/WPAnalyticsEvent.swift @@ -41,6 +41,7 @@ import Foundation case editorPostSlugChanged case editorPostExcerptChanged case editorPostSiteChanged + case editorPostLegacyMoreMenuShown // Resolve post version conflict case resolveConflictScreenShown @@ -594,6 +595,11 @@ import Foundation case readingPreferencesSaved case readingPreferencesClosed + // Stats Subscribers + case statsSubscribersViewMoreTapped + case statsEmailsViewMoreTapped + case statsSubscribersChartTapped + /// A String that represents the event var value: String { switch self { @@ -661,6 +667,8 @@ import Foundation return "editor_post_excerpt_changed" case .editorPostSiteChanged: return "editor_post_site_changed" + case .editorPostLegacyMoreMenuShown: + return "editor_post_legacy_more_menu_shown" case .resolveConflictScreenShown: return "resolve_conflict_screen_shown" case .resolveConflictSaveTapped: @@ -1615,6 +1623,14 @@ import Foundation case .readingPreferencesClosed: return "reader_reading_preferences_closed" + // Stats Subscribers + case .statsSubscribersViewMoreTapped: + return "stats_subscribers_view_more_tapped" + case .statsEmailsViewMoreTapped: + return "stats_emails_view_more_tapped" + case .statsSubscribersChartTapped: + return "stats_subscribers_chart_tapped" + } // END OF SWITCH } diff --git a/WordPress/Classes/Utility/CollectionView/AdaptiveCollectionViewFlowLayout.swift b/WordPress/Classes/Utility/CollectionView/AdaptiveCollectionViewFlowLayout.swift new file mode 100644 index 000000000000..58941b0345f9 --- /dev/null +++ b/WordPress/Classes/Utility/CollectionView/AdaptiveCollectionViewFlowLayout.swift @@ -0,0 +1,18 @@ +/// A flow layout that properly invalidates the layout when the collection view's bounds changed, +/// (e.g., orientation changes). +/// +/// This method ensures that we work with the latest/correct bounds after the size change, and potentially +/// avoids race conditions where we might get incorrect bounds while the view is still in transition. +/// +/// See: https://developer.apple.com/documentation/uikit/uicollectionviewlayout/1617781-shouldinvalidatelayout +class AdaptiveCollectionViewFlowLayout: UICollectionViewFlowLayout { + + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + // NOTE: Apparently we need to *manually* invalidate the layout because `invalidateLayout()` + // is NOT called after this method returns true. + if let collectionView, collectionView.bounds.size != newBounds.size { + invalidateLayout() + } + return super.shouldInvalidateLayout(forBoundsChange: newBounds) + } +} diff --git a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift index 0d87e45fa9f5..86de50e099df 100644 --- a/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift +++ b/WordPress/Classes/ViewRelated/Aztec/ViewControllers/AztecPostViewController.swift @@ -740,7 +740,6 @@ class AztecPostViewController: UIViewController, PostEditor { navigationController?.navigationBar.isTranslucent = false navigationController?.navigationBar.accessibilityIdentifier = "Azctec Editor Navigation Bar" navigationItem.leftBarButtonItems = navigationBarManager.leftBarButtonItems - navigationItem.rightBarButtonItems = navigationBarManager.rightBarButtonItemsAztec navigationItem.titleView = navigationBarManager.blogTitleViewLabel } @@ -823,6 +822,7 @@ class AztecPostViewController: UIViewController, PostEditor { reloadEditorContents() reloadPublishButton() refreshTitleViewForMediaUploadIfNeeded() + navigationItem.rightBarButtonItems = post.status == .trash ? [] : navigationBarManager.rightBarButtonItemsAztec } func refreshTitleViewForMediaUploadIfNeeded() { @@ -1197,7 +1197,7 @@ private extension AztecPostViewController { if (post.revisions ?? []).count > 0 { alert.addDefaultActionWithTitle(MoreSheetAlert.historyTitle) { [unowned self] _ in - self.displayHistory() + self.displayRevisionsList() } } diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift index c9f61cbbbfac..e8cf27fa7d1e 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController+MoreActions.swift @@ -11,7 +11,11 @@ extension GutenbergViewController { case managedObjectContextMissing = 2 } + // - warning: deprecated (kahu-offline-mode) + // TODO: Remove when/if confirmed that this is never invoked by Gutenberg. func displayMoreSheet() { + WPAnalytics.track(.editorPostLegacyMoreMenuShown) + // Dismisses and locks the Notices Store from displaying any new notices. ActionDispatcher.dispatch(NoticeAction.lock) let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) @@ -52,7 +56,7 @@ extension GutenbergViewController { if (post.revisions ?? []).count > 0 { alert.addDefaultActionWithTitle(MoreSheetAlert.historyTitle) { [weak self] _ in - self?.displayHistory() + self?.displayRevisionsList() ActionDispatcher.dispatch(NoticeAction.unlock) } } @@ -86,11 +90,68 @@ extension GutenbergViewController { present(alert, animated: true) } + + func makeMoreMenu() -> UIMenu { + UIMenu(title: "", image: nil, identifier: nil, options: [], children: [ + UIDeferredMenuElement.uncached { [weak self] in + $0(self?.makeMoreMenuSections() ?? []) + } + ]) + } + + private func makeMoreMenuSections() -> [UIMenuElement] { + var sections: [UIMenuElement] = [ + UIMenu(title: "", subtitle: "", options: .displayInline, children: makeMoreMenuActions()) + ] + if let string = makeContextStructureString() { + sections.append(UIAction(subtitle: string, attributes: [.disabled], handler: { _ in })) + } + return sections + } + + private func makeMoreMenuActions() -> [UIAction] { + var actions: [UIAction] = [] + + let toggleModeTitle = mode == .richText ? Strings.codeEditor : Strings.visualEditor + let toggleModeIconName = mode == .richText ? "curlybraces" : "doc.richtext" + actions.append(UIAction(title: toggleModeTitle, image: UIImage(systemName: toggleModeIconName)) { [weak self] _ in + self?.toggleEditingMode() + }) + + actions.append(UIAction(title: Strings.preview, image: UIImage(systemName: "safari")) { [weak self] _ in + self?.displayPreview() + }) + + let revisionCount = (post.revisions ?? []).count + if revisionCount > 0 { + actions.append(UIAction(title: Strings.revisions + " (\(revisionCount))", image: UIImage(systemName: "clock.arrow.circlepath")) { [weak self] _ in + self?.displayRevisionsList() + }) + } + + let settingsTitle = self.post is Page ? Strings.pageSettings : Strings.postSettings + actions.append(UIAction(title: settingsTitle, image: UIImage(systemName: "gearshape")) { [weak self] _ in + self?.displayPostSettings() + }) + let helpTitle = JetpackFeaturesRemovalCoordinator.jetpackFeaturesEnabled() ? Strings.helpAndSupport : Strings.help + actions.append(UIAction(title: helpTitle, image: UIImage(systemName: "questionmark.circle")) { [weak self] _ in + self?.showEditorHelp() + }) + return actions + } + + private func makeContextStructureString() -> String? { + guard mode == .richText, let contentInfo = contentInfo else { + return nil + } + return String(format: Strings.contentStructure, contentInfo.blockCount, contentInfo.wordCount, contentInfo.characterCount) + } } // MARK: - Constants extension GutenbergViewController { + // - warning: deprecated (kahu-offline-mode) struct MoreSheetAlert { static let htmlTitle = NSLocalizedString("Switch to HTML Mode", comment: "Switches the Editor to HTML Mode") static let richTitle = NSLocalizedString("Switch to Visual Mode", comment: "Switches the Editor to Rich Text Mode") @@ -104,3 +165,15 @@ extension GutenbergViewController { static let editorHelpTitle = NSLocalizedString("Help", comment: "Open editor help options") } } + +private enum Strings { + static let codeEditor = NSLocalizedString("postEditor.moreMenu.codeEditor", value: "Code Editor", comment: "Post Editor / Button in the 'More' menu") + static let visualEditor = NSLocalizedString("postEditor.moreMenu.visualEditor", value: "Visual Editor", comment: "Post Editor / Button in the 'More' menu") + static let preview = NSLocalizedString("postEditor.moreMenu.preview", value: "Preview", comment: "Post Editor / Button in the 'More' menu") + static let revisions = NSLocalizedString("postEditor.moreMenu.revisions", value: "Revisions", comment: "Post Editor / Button in the 'More' menu") + static let pageSettings = NSLocalizedString("postEditor.moreMenu.pageSettings", value: "Page Settings", comment: "Post Editor / Button in the 'More' menu") + static let postSettings = NSLocalizedString("postEditor.moreMenu.postSettings", value: "Post Settings", comment: "Post Editor / Button in the 'More' menu") + static let helpAndSupport = NSLocalizedString("postEditor.moreMenu.helpAndSupport", value: "Help & Support", comment: "Post Editor / Button in the 'More' menu") + static let help = NSLocalizedString("postEditor.moreMenu.help", value: "Help", comment: "Post Editor / Button in the 'More' menu") + static let contentStructure = NSLocalizedString("postEditor.moreMenu.contentStructure", value: "Blocks: %li, Words: %li, Characters: %li", comment: "Post Editor / 'More' menu details labels with 'Blocks', 'Words' and 'Characters' counts as parameters (in that order)") +} diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index d445bd0c665e..16eb9741a50a 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -438,7 +438,6 @@ class GutenbergViewController: UIViewController, PostEditor, FeaturedImageDelega navigationController?.navigationBar.isTranslucent = false navigationController?.navigationBar.accessibilityIdentifier = "Gutenberg Editor Navigation Bar" navigationItem.leftBarButtonItems = navigationBarManager.leftBarButtonItems - navigationItem.rightBarButtonItems = navigationBarManager.rightBarButtonItems // Add bottom border line let screenScale = UIScreen.main.scale @@ -450,6 +449,15 @@ class GutenbergViewController: UIViewController, PostEditor, FeaturedImageDelega borderBottom.frame = CGRect(x: 0, y: navigationController?.navigationBar.frame.size.height ?? 0 - borderWidth, width: navigationController?.navigationBar.frame.size.width ?? 0, height: borderWidth) borderBottom.autoresizingMask = [.flexibleWidth, .flexibleTopMargin] navigationController?.navigationBar.addSubview(borderBottom) + + if FeatureFlag.syncPublishing.enabled { + navigationBarManager.moreButton.menu = makeMoreMenu() + navigationBarManager.moreButton.showsMenuAsPrimaryAction = true + } + } + + @objc private func buttonMoreTapped() { + displayMoreSheet() } private func reloadBlogIconView() { @@ -481,6 +489,7 @@ class GutenbergViewController: UIViewController, PostEditor, FeaturedImageDelega reloadBlogIconView() reloadEditorContents() reloadPublishButton() + navigationItem.rightBarButtonItems = post.status == .trash ? [] : navigationBarManager.rightBarButtonItems } func toggleEditingMode() { diff --git a/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayGeneralViewModel.swift b/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayGeneralViewModel.swift index 2c7b45427c04..4f8adb78ff81 100644 --- a/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayGeneralViewModel.swift +++ b/WordPress/Classes/ViewRelated/Jetpack/Branding/Fullscreen Overlay/JetpackFullscreenOverlayGeneralViewModel.swift @@ -395,7 +395,7 @@ private extension JetpackFullscreenOverlayGeneralViewModel { enum Reader { static let title = NSLocalizedString("jetpack.fullscreen.overlay.phaseOne.reader.title", - value: "Follow any site with the Jetpack app", + value: "Subscribe to any site with the Jetpack app", comment: "Title of a screen displayed when the user accesses the Reader screen from the WordPress app. The screen showcases the Jetpack app.") static let subtitle = NSLocalizedString("jetpack.fullscreen.overlay.phaseOne.reader.subtitle", value: "Switch to the Jetpack app to find, follow, and like all your favorite sites and posts with Reader.", diff --git a/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift b/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift index 40fff9d454e0..f3c3e78f3658 100644 --- a/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift +++ b/WordPress/Classes/ViewRelated/NUX/UnifiedProloguePages.swift @@ -19,7 +19,7 @@ enum UnifiedProloguePageType: CaseIterable { case .analytics: return NSLocalizedString("Watch your audience grow with in-depth analytics.", comment: "Caption displayed in promotional screens shown during the login flow.") case .reader: - return NSLocalizedString("Follow your favorite sites and discover new blogs.", comment: "Caption displayed in promotional screens shown during the login flow.") + return NSLocalizedString("prologue.title.reader", value: "Subscribe to your favorite sites and discover new blogs.", comment: "Caption displayed in promotional screens shown during the login flow.") } } } diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift index 7768533e241a..c52af34258c3 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift @@ -467,7 +467,7 @@ private extension NotificationSettingsViewController { case .blog: return NSLocalizedString("Your Sites", comment: "Displayed in the Notification Settings View") case .followedSites: - return NSLocalizedString("Followed Sites", comment: "Displayed in the Notification Settings View") + return NSLocalizedString("notification.settings.header.subscribedSites", value: "Subscribed Sites", comment: "Displayed in the Notification Settings View") case .other: return NSLocalizedString("Other", comment: "Displayed in the Notification Settings View") case .wordPressCom: @@ -483,8 +483,8 @@ private extension NotificationSettingsViewController { return NSLocalizedString("Customize your site settings for Likes, Comments, Follows, and more.", comment: "Notification Settings for your own blogs") case .followedSites: - return NSLocalizedString("Customize your followed site settings for New Posts and Comments", - comment: "Notification Settings for your followed sites") + return NSLocalizedString("notification.settings.footer.subscribedSites", value: "Customize your subscribed site settings for New Posts and Comments", + comment: "Notification Settings for your subscribed sites") case .other: return nil case .wordPressCom: diff --git a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController/NotificationsViewController.swift b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController/NotificationsViewController.swift index 6cc5236b77fd..ab07b60a889b 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController/NotificationsViewController.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationsViewController/NotificationsViewController.swift @@ -1838,7 +1838,7 @@ private extension NotificationsViewController { case .none: return NSLocalizedString("All", comment: "Displays all of the Notifications, unfiltered") case .unread: return NSLocalizedString("Unread", comment: "Filters Unread Notifications") case .comment: return NSLocalizedString("Comments", comment: "Filters Comments Notifications") - case .follow: return NSLocalizedString("Follows", comment: "Filters Follows Notifications") + case .follow: return NSLocalizedString("notifications.filter.subscribers.title", value: "Subscribers", comment: "Filters Subscribers Notifications") case .like: return NSLocalizedString("Likes", comment: "Filters Likes Notifications") } } @@ -1858,7 +1858,7 @@ private extension NotificationsViewController { case .none: return "" case .unread: return NSLocalizedString("unread", comment: "Displayed in the confirmation alert when marking unread notifications as read.") case .comment: return NSLocalizedString("comment", comment: "Displayed in the confirmation alert when marking comment notifications as read.") - case .follow: return NSLocalizedString("follow", comment: "Displayed in the confirmation alert when marking follow notifications as read.") + case .follow: return NSLocalizedString("notifications.filter.subscriptions.confirmationMessageTitle", value: "subscribe", comment: "Displayed in the confirmation alert when marking follow notifications as read.") case .like: return NSLocalizedString("like", comment: "Displayed in the confirmation alert when marking like notifications as read.") } } @@ -1871,8 +1871,8 @@ private extension NotificationsViewController { comment: "Displayed in the Notifications Tab as a title, when the Unread Filter shows no unread notifications as a title") case .comment: return NSLocalizedString("No comments yet", comment: "Displayed in the Notifications Tab as a title, when the Comments Filter shows no notifications") - case .follow: return NSLocalizedString("No followers yet", - comment: "Displayed in the Notifications Tab as a title, when the Follow Filter shows no notifications") + case .follow: return NSLocalizedString("notifications.noresults.subscribers", value: "No subscribers yet", + comment: "Displayed in the Notifications Tab as a title, when the Subscriber Filter shows no notifications") case .like: return NSLocalizedString("No likes yet", comment: "Displayed in the Notifications Tab as a title, when the Likes Filter shows no notifications") } diff --git a/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift b/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift index 9264baa712e5..d457eaadf466 100644 --- a/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift +++ b/WordPress/Classes/ViewRelated/Notifications/Style/WPStyleGuide+Notifications.swift @@ -250,8 +250,8 @@ extension WPStyleGuide { button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 0) // Strings - let normalText = NSLocalizedString("Follow", comment: "Prompt to follow a blog.") - let selectedText = NSLocalizedString("Following", comment: "User is following the blog.") + let normalText = NSLocalizedString("notifications.button.subscribe", value: "Subscribe", comment: "Prompt to subscribe to a blog.") + let selectedText = NSLocalizedString("notifications.button.subscribed", value: "Subscribed", comment: "User is subscribed to the blog.") button.setTitle(normalText, for: .normal) button.setTitle(selectedText, for: .selected) @@ -259,7 +259,7 @@ extension WPStyleGuide { // Default accessibility label and hint. button.accessibilityLabel = normalText - button.accessibilityHint = NSLocalizedString("Follows the blog.", comment: "VoiceOver accessibility hint, informing the user the button can be used to follow a blog.") + button.accessibilityHint = NSLocalizedString("notifications.button.subscribedHint", value: "Subscribes to the blog.", comment: "VoiceOver accessibility hint, informing the user the button can be used to subscribe to a blog.") } // MARK: - Constants diff --git a/WordPress/Classes/ViewRelated/Pages/PageEditorPresenter.swift b/WordPress/Classes/ViewRelated/Pages/PageEditorPresenter.swift index 38a76380e71a..7bba5acabe54 100644 --- a/WordPress/Classes/ViewRelated/Pages/PageEditorPresenter.swift +++ b/WordPress/Classes/ViewRelated/Pages/PageEditorPresenter.swift @@ -9,7 +9,8 @@ struct PageEditorPresenter { return false } - guard page.status != .trash else { + if page.status == .trash && !FeatureFlag.syncPublishing.enabled { + // No editing posts that are trashed. return false } diff --git a/WordPress/Classes/ViewRelated/Pages/PageListSectionHeaderView.swift b/WordPress/Classes/ViewRelated/Pages/PageListSectionHeaderView.swift deleted file mode 100644 index 688e5a2d43ea..000000000000 --- a/WordPress/Classes/ViewRelated/Pages/PageListSectionHeaderView.swift +++ /dev/null @@ -1,19 +0,0 @@ -import UIKit - -class PageListSectionHeaderView: UIView { - - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var separator: UIView! - - func setTitle(_ title: String) { - titleLabel.text = title.uppercased(with: .current) - } - - override func awakeFromNib() { - super.awakeFromNib() - - backgroundColor = .listBackground - titleLabel.backgroundColor = .listBackground - WPStyleGuide.applyBorderStyle(separator) - } -} diff --git a/WordPress/Classes/ViewRelated/Pages/PageListSectionHeaderView.xib b/WordPress/Classes/ViewRelated/Pages/PageListSectionHeaderView.xib deleted file mode 100644 index 2e5cbbcac673..000000000000 --- a/WordPress/Classes/ViewRelated/Pages/PageListSectionHeaderView.xib +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/Classes/ViewRelated/People/People.storyboard b/WordPress/Classes/ViewRelated/People/People.storyboard index c6020e76d931..c3e0a39b2a3a 100644 --- a/WordPress/Classes/ViewRelated/People/People.storyboard +++ b/WordPress/Classes/ViewRelated/People/People.storyboard @@ -1,9 +1,9 @@ - + - + @@ -12,12 +12,12 @@ - + - + - - + + - + - + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + @@ -127,7 +132,6 @@ - @@ -170,7 +174,7 @@ - + @@ -193,14 +197,14 @@ - + - + - + @@ -214,16 +218,16 @@ - + - - + diff --git a/WordPress/Classes/ViewRelated/People/PeopleCell.swift b/WordPress/Classes/ViewRelated/People/PeopleCell.swift index 2de2ada0857b..de40c50be052 100644 --- a/WordPress/Classes/ViewRelated/People/PeopleCell.swift +++ b/WordPress/Classes/ViewRelated/People/PeopleCell.swift @@ -2,6 +2,7 @@ import UIKit import WordPressShared import WordPressUI import Gravatar +import DesignSystem class PeopleCell: WPTableViewCell { @IBOutlet private weak var avatarImageView: CircularImageView! @@ -14,8 +15,17 @@ class PeopleCell: WPTableViewCell { override func awakeFromNib() { super.awakeFromNib() - WPStyleGuide.configureLabel(displayNameLabel, textStyle: .callout) - WPStyleGuide.configureLabel(usernameLabel, textStyle: .caption2) + displayNameLabel.textColor = .DS.Foreground.primary + displayNameLabel.font = .DS.font(.bodyMedium(.regular)) + + usernameLabel.textColor = .DS.Foreground.secondary + usernameLabel.font = .DS.font(.bodyMedium(.regular)) + } + + override func prepareForReuse() { + super.prepareForReuse() + + avatarImageView.image = UIImage(named: "gravatar") } func bindViewModel(_ viewModel: PeopleCellViewModel) { @@ -24,14 +34,12 @@ class PeopleCell: WPTableViewCell { displayNameLabel.textColor = viewModel.usernameColor usernameLabel.text = viewModel.usernameText usernameLabel.isHidden = viewModel.usernameHidden - roleBadge.borderColor = viewModel.roleBorderColor roleBadge.backgroundColor = viewModel.roleBackgroundColor roleBadge.textColor = viewModel.roleTextColor roleBadge.text = viewModel.roleText roleBadge.isHidden = viewModel.roleHidden superAdminRoleBadge.text = viewModel.superAdminText superAdminRoleBadge.isHidden = viewModel.superAdminHidden - superAdminRoleBadge.borderColor = viewModel.superAdminBorderColor superAdminRoleBadge.backgroundColor = viewModel.superAdminBackgroundColor badgeStackView.isHidden = viewModel.roleHidden && viewModel.superAdminHidden } diff --git a/WordPress/Classes/ViewRelated/People/PeopleCellViewModel.swift b/WordPress/Classes/ViewRelated/People/PeopleCellViewModel.swift index 07cd2c9f3e01..fa3f07d8c844 100644 --- a/WordPress/Classes/ViewRelated/People/PeopleCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/People/PeopleCellViewModel.swift @@ -1,5 +1,6 @@ import Foundation import WordPressShared +import DesignSystem struct PeopleCellViewModel { let displayName: String @@ -28,16 +29,22 @@ struct PeopleCellViewModel { return .text } - var roleBorderColor: UIColor { - return role?.color ?? WPStyleGuide.People.otherRoleColor - } - var roleBackgroundColor: UIColor { - return role?.color ?? WPStyleGuide.People.otherRoleColor + switch role?.slug { + case .some("super-admin"), .some("administrator"): + return .DS.Foreground.primary + default: + return .DS.Background.secondary + } } var roleTextColor: UIColor { - return WPStyleGuide.People.RoleBadge.textColor + switch role?.slug { + case .some("super-admin"), .some("administrator"): + return .DS.Background.primary + default: + return .DS.Foreground.primary + } } var roleText: String { @@ -52,12 +59,8 @@ struct PeopleCellViewModel { return NSLocalizedString("Super Admin", comment: "User role badge") } - var superAdminBorderColor: UIColor { - return superAdminBackgroundColor - } - var superAdminBackgroundColor: UIColor { - return WPStyleGuide.People.superAdminColor + return .DS.Foreground.primary } var superAdminHidden: Bool { diff --git a/WordPress/Classes/ViewRelated/People/PeopleRoleBadgeLabel.swift b/WordPress/Classes/ViewRelated/People/PeopleRoleBadgeLabel.swift index 3d96a0c86ab1..6d0d3289065c 100644 --- a/WordPress/Classes/ViewRelated/People/PeopleRoleBadgeLabel.swift +++ b/WordPress/Classes/ViewRelated/People/PeopleRoleBadgeLabel.swift @@ -1,5 +1,5 @@ import UIKit -import WordPressShared.WPStyleGuide +import DesignSystem class PeopleRoleBadgeLabel: BadgeLabel { override init(frame: CGRect) { @@ -13,11 +13,10 @@ class PeopleRoleBadgeLabel: BadgeLabel { } private func setupView() { - adjustsFontForContentSizeCategory = true adjustsFontSizeToFitWidth = true - horizontalPadding = WPStyleGuide.People.RoleBadge.padding - font = WPStyleGuide.People.RoleBadge.font - layer.borderWidth = WPStyleGuide.People.RoleBadge.borderWidth - layer.cornerRadius = WPStyleGuide.People.RoleBadge.cornerRadius + horizontalPadding = CGFloat.DS.Padding.single + verticalPadding = .DS.Padding.half + font = .DS.font(.footnote) + layer.cornerRadius = .DS.Radius.small } } diff --git a/WordPress/Classes/ViewRelated/People/PeopleViewController.swift b/WordPress/Classes/ViewRelated/People/PeopleViewController.swift index 59116a5e0b61..539b0302a20a 100644 --- a/WordPress/Classes/ViewRelated/People/PeopleViewController.swift +++ b/WordPress/Classes/ViewRelated/People/PeopleViewController.swift @@ -74,7 +74,7 @@ class PeopleViewController: UITableViewController { // Followers must be sorted out by creationDate! // switch filter { - case .followers, .email: + case .followers: return [NSSortDescriptor(key: "creationDate", ascending: true, selector: #selector(NSDate.compare(_:)))] default: return [NSSortDescriptor(key: "displayName", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))] @@ -150,7 +150,7 @@ class PeopleViewController: UITableViewController { // MARK: UITableViewDelegate override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return hasHorizontallyCompactView() ? CGFloat.leastNormalMagnitude : 0 + return .DS.Padding.single } override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { @@ -163,6 +163,37 @@ class PeopleViewController: UITableViewController { loadMorePeopleIfNeeded() } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + guard let blog = blog, let blogId = blog.dotComID?.intValue else { return } + + switch filter { + case .users, .viewers: + guard let viewController = PersonViewController.controllerWithBlog( + blog, + context: viewContext, + person: personAtIndexPath(indexPath), + screenMode: filter.screenMode + ) else { + return + } + navigationController?.pushViewController(viewController, animated: true) + case .followers: + let url = URL(string: "https://wordpress.com/subscribers/\(blogId)/\(personAtIndexPath(indexPath).ID)") + let configuration = WebViewControllerConfiguration(url: url) + configuration.authenticateWithDefaultAccount() + configuration.secureInteraction = true + configuration.onClose = { [weak self] in + self?.resetManagedPeople() + self?.refreshPeople() + } + let viewController = WebKitViewController(configuration: configuration) + let navWrapper = LightNavigationController(rootViewController: viewController) + navigationController?.present(navWrapper, animated: true) + } + } + // MARK: UIViewController override func viewDidLoad() { @@ -189,16 +220,6 @@ class PeopleViewController: UITableViewController { tableView.reloadData() } - @IBSegueAction func createPersonViewController(_ coder: NSCoder) -> PersonViewController? { - guard let selectedIndexPath = tableView.indexPathForSelectedRow, let blog = blog else { return nil } - - return PersonViewController(coder: coder, - blog: blog, - context: viewContext, - person: personAtIndexPath(selectedIndexPath), - screenMode: filter.screenMode) - } - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let navController = segue.destination as? UINavigationController, let inviteViewController = navController.topViewController as? InvitePersonViewController { @@ -251,11 +272,10 @@ extension PeopleViewController { case users = "users" case followers = "followers" - case email = "email" case viewers = "viewers" static var defaultFilters: [Filter] { - return [.users, .followers, .email] + return [.users, .followers] } var title: String { @@ -266,8 +286,6 @@ extension PeopleViewController { return NSLocalizedString("users.list.title.subscribers", value: "Subscribers", comment: "Site Subscribers") case .viewers: return NSLocalizedString("Viewers", comment: "Blog Viewers") - case .email: - return NSLocalizedString("users.list.title.emailSubscribers", value: "Email Subscribers", comment: "Site Email Subscribers") } } @@ -279,8 +297,6 @@ extension PeopleViewController { return .follower case .viewers: return .viewer - case .email: - return .emailFollower } } @@ -292,8 +308,6 @@ extension PeopleViewController { return .Follower case .viewers: return .Viewer - case .email: - return .Email } } } @@ -336,6 +350,8 @@ private extension PeopleViewController { // MARK: Sync Helpers func refreshPeople() { + self.isInitialLoad = true + self.refreshNoResultsView() loadPeoplePage() { [weak self] (retrieved, shouldLoadMore) in self?.isInitialLoad = false self?.refreshNoResultsView() @@ -382,8 +398,6 @@ private extension PeopleViewController { loadUsersPage(offset, success: success) case .viewers: service.loadViewersPage(offset, success: success) - case .email: - service.loadEmailFollowersPage(offset, success: success) } } @@ -531,6 +545,8 @@ private extension PeopleViewController { WPStyleGuide.configureColors(view: view, tableView: tableView) WPStyleGuide.configureAutomaticHeightRows(for: tableView) + tableView.separatorStyle = .none + tableView.backgroundColor = .DS.Background.primary setupFilterBar() setupTableView() diff --git a/WordPress/Classes/ViewRelated/People/PersonViewController.swift b/WordPress/Classes/ViewRelated/People/PersonViewController.swift index 4617f0f635fd..14c3c242097f 100644 --- a/WordPress/Classes/ViewRelated/People/PersonViewController.swift +++ b/WordPress/Classes/ViewRelated/People/PersonViewController.swift @@ -23,7 +23,6 @@ final class PersonViewController: UITableViewController { case User = "user" case Follower = "follower" case Viewer = "viewer" - case Email = "email" var title: String { switch self { @@ -33,8 +32,6 @@ final class PersonViewController: UITableViewController { return NSLocalizedString("user.details.title.subscriber", value: "Site's Subscriber", comment: "Site's Subscriber Profile. Displayed when the name is empty!") case .Viewer: return NSLocalizedString("user.details.title.viewer", value: "Site's Viewer", comment: "Site's Viewers Profile. Displayed when the name is empty!") - case .Email: - return NSLocalizedString("user.details.title.emailSubscriber", value: "Site's Email Subscriber", comment: "Site's Email Subscriber Profile. Displayed when the name is empty!") } } } @@ -73,6 +70,13 @@ final class PersonViewController: UITableViewController { super.init(coder: coder) } + class func controllerWithBlog(_ blog: Blog, context: NSManagedObjectContext, person: Person, screenMode: ScreenMode) -> PersonViewController? { + let storyboard = UIStoryboard(name: "People", bundle: nil) + return storyboard.instantiateViewController(identifier: "PersonViewController") { coder in + PersonViewController(coder: coder, blog: blog, context: context, person: person, screenMode: screenMode) + } + } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -230,8 +234,6 @@ private extension PersonViewController { case .Viewer: strongSelf.deleteViewer() return - case .Email: - strongSelf.deleteEmailFollower() } } @@ -251,9 +253,6 @@ private extension PersonViewController { case .Viewer: messageFirstLine = NSLocalizedString("If you remove this viewer, he or she will not be able to visit this site.", comment: "First line of remove viewer warning in confirmation dialog.") - case .Email: - messageFirstLine = NSLocalizedString("Removing followers makes them stop receiving updates from your site. If they choose to, they can still visit your site, and follow it again.", - comment: "First line of remove email follower warning in confirmation dialog.") } let messageSecondLineText = NSLocalizedString("Would you still like to remove this person?", @@ -298,23 +297,6 @@ private extension PersonViewController { _ = navigationController?.popViewController(animated: true) } - func deleteEmailFollower() { - guard let emailFollower = emailFollower, isEmailFollower else { - DDLogError("Error: Only email followers can be deleted here") - assertionFailure() - return - } - - service?.deleteEmailFollower(emailFollower, failure: { [weak self] error in - guard let strongSelf = self, let error = error as NSError? else { - return - } - - strongSelf.handleRemoveViewerOrFollowerError(error) - }) - _ = navigationController?.popViewController(animated: true) - } - func deleteViewer() { guard let viewer = viewer, isViewer else { DDLogError("Error: Only Viewers can be deleted here") @@ -542,8 +524,6 @@ private extension PersonViewController { return isFollower == true case .Viewer: return isViewer == true - case .Email: - return isEmailFollower } } @@ -587,8 +567,6 @@ private extension PersonViewController { return .viewer case .User: return try? Role.lookup(withBlogID: blog.objectID, slug: person.role, in: context)?.toUnmanaged() - case .Email: - return .follower } } } diff --git a/WordPress/Classes/ViewRelated/People/WPStyleGuide+People.swift b/WordPress/Classes/ViewRelated/People/WPStyleGuide+People.swift deleted file mode 100644 index cb4c6ab35b81..000000000000 --- a/WordPress/Classes/ViewRelated/People/WPStyleGuide+People.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation -import WordPressShared - -extension WPStyleGuide { - public struct People { - public struct RoleBadge { - // MARK: Metrics - public static let padding = CGFloat(4) - public static let borderWidth = CGFloat(1) - public static let cornerRadius = CGFloat(2) - - // MARK: Typography - public static var font: UIFont { - return WPStyleGuide.fontForTextStyle(.caption2) - } - - // MARK: Colors - public static let textColor = UIColor.textInverted - } - - // MARK: Colors - public static let superAdminColor = UIColor.accentDark - public static let adminColor = UIColor.neutral(.shade70) - public static let editorColor = UIColor.primaryDark - public static let otherRoleColor: UIColor = .primary - } -} diff --git a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift index aaa942a40f23..17dd6717a655 100644 --- a/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/AbstractPostListViewController.swift @@ -758,7 +758,9 @@ class AbstractPostListViewController: UIViewController, SiteStatsInformation.sharedInstance.oauth2Token = blog.authToken SiteStatsInformation.sharedInstance.siteID = blog.dotComID - let postURL = URL(string: post.permaLink! as String) + guard let postURL = post.permaLink.flatMap(URL.init) else { + return wpAssertionFailure("permalink missing or invalid") + } let postStatsTableViewController = PostStatsTableViewController.withJPBannerForBlog(postID: postID, postTitle: post.titleForDisplay(), postURL: postURL) diff --git a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift index ab5efecccb9a..08c88342ff85 100644 --- a/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift +++ b/WordPress/Classes/ViewRelated/Post/PostCardStatusViewModel.swift @@ -97,14 +97,10 @@ class PostCardStatusViewModel: NSObject, AbstractPostMenuViewModel { return .warning } switch post.status ?? .draft { - case .pending: - return .success - case .scheduled: - return .primary(.shade40) case .trash: return .error default: - return .neutral(.shade70) + return .secondaryLabel } } @@ -277,9 +273,18 @@ class PostCardStatusViewModel: NSObject, AbstractPostMenuViewModel { func statusAndBadges(separatedBy separator: String) -> String { let sticky = post.isStickyPost ? Constants.stickyLabel : "" let pending = (post.status == .pending && isSyncPublishingEnabled) ? Constants.pendingReview : "" + let visibility: String = { + let visibility = PostVisibility(post: post) + switch visibility { + case .public: + return "" + case .private, .protected: + return visibility.localizedTitle + } + }() let status = self.status ?? "" - return [status, pending, sticky].filter { !$0.isEmpty }.joined(separator: separator) + return [status, visibility, pending, sticky].filter { !$0.isEmpty }.joined(separator: separator) } /// Determine what the failed status message should be and return it. diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift index d4a93768833a..e0e83ebfeaa4 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+MoreOptions.swift @@ -157,7 +157,7 @@ extension PostEditor { } } - func displayHistory() { + func displayRevisionsList() { guard FeatureFlag.syncPublishing.enabled else { _displayHistory() return @@ -174,7 +174,7 @@ extension PostEditor { self.post.mt_excerpt = revision.postExcerpt // It's important to clear the pending uploads associated with the -     // post. The assumption is that if the revision on the remote, + // post. The assumption is that if the revision on the remote, // its associated media has to be also uploaded. MediaCoordinator.shared.cancelUploadOfAllMedia(for: self.post) self.post.media = [] diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift index 092852216ebf..baabd7344655 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor+Publish.swift @@ -429,6 +429,10 @@ extension PublishingEditor { return _discardChanges() } + guard post.status != .trash else { + return true // No revision is created for trashed posts + } + guard let context = post.managedObjectContext else { wpAssertionFailure("Missing managedObjectContext") return true @@ -724,7 +728,7 @@ extension PublishingEditor { wpAssert(post.latest() == post, "Must be opened with the latest verison of the post") - if !post.isUnsavedRevision { + if !post.isUnsavedRevision && post.status != .trash { DDLogDebug("Creating new revision") post = post._createRevision() } diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor.swift b/WordPress/Classes/ViewRelated/Post/PostEditor.swift index e5e1652fcae6..f86694d5d20d 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor.swift @@ -1,5 +1,6 @@ import UIKit import Combine +import WordPressFlux enum EditMode { case richText @@ -141,7 +142,13 @@ extension PostEditor where Self: UIViewController { guard FeatureFlag.syncPublishing.enabled else { return } - showAutosaveAvailableAlertIfNeeded() + + if post.original().status == .trash { + showPostTrashedOverlay() + } else { + showAutosaveAvailableAlertIfNeeded() + showTerminalUploadErrorAlertIfNeeded() + } var cancellables: [AnyCancellable] = [] @@ -160,6 +167,8 @@ extension PostEditor where Self: UIViewController { objc_setAssociatedObject(self, &cancellablesKey, cancellables, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + // MARK: - Autosave + private func showAutosaveAvailableAlertIfNeeded() { // The revision has unsaved local changes, takes precedence over autosave guard post.changes.isEmpty else { @@ -182,6 +191,8 @@ extension PostEditor where Self: UIViewController { present(alert, animated: true) } + // MARK: - App Termination + private func appWillTerminate() { guard let context = post.managedObjectContext else { return @@ -206,6 +217,8 @@ extension PostEditor where Self: UIViewController { } } + // MARK: - Conflict Resolution + private func postConflictResolved(_ notification: Foundation.Notification) { guard let userInfo = notification.userInfo, @@ -213,8 +226,86 @@ extension PostEditor where Self: UIViewController { else { return } - self.post = post - createRevisionOfPost() + self.configureWithUpdatedPost(post) + } + + // MARK: - Restore Trashed Post + + private func showPostTrashedOverlay() { + let overlay = PostTrashedOverlayView() + view.addSubview(overlay) + overlay.translatesAutoresizingMaskIntoConstraints = false + overlay.onOverlayTapped = { [weak self] in self?.showRestorePostAlert(with: $0) } + view.pinSubviewToAllEdges(overlay) + } + + private func showRestorePostAlert(with overlay: PostTrashedOverlayView) { + overlay.isUserInteractionEnabled = false + + let postType = post.localizedPostType.lowercased() + let alert = UIAlertController(title: String(format: Strings.trashedPostSheetTitle, postType), message: String(format: Strings.trashedPostSheetMessage, postType), preferredStyle: .alert) + alert.addAction(UIAlertAction(title: Strings.trashedPostSheetCancel, style: .cancel) { _ in + overlay.isUserInteractionEnabled = true + }) + alert.addAction(UIAlertAction(title: Strings.trashedPostSheetRecover, style: .default) { [weak self] _ in + guard let self else { return } + Task { + await self.restorePostFromTrash() + } + }) + present(alert, animated: true) + } + + @MainActor + private func restorePostFromTrash() async { + SVProgressHUD.show() + defer { SVProgressHUD.dismiss() } + let coordinator = PostCoordinator.shared + do { + try await coordinator.restore(post) + ActionDispatcher.dispatch(NoticeAction.post(Notice(title: Strings.trashedPostRestored))) + self.configureWithUpdatedPost(post) + } catch { + coordinator.handleError(error, for: post) + } + } + + private func configureWithUpdatedPost(_ post: AbstractPost) { + self.post = post // Even if it's the same instance, it's how you currently refresh the editor + self.createRevisionOfPost() + } + + // MARK: - Failed Media Uploads + + private func showTerminalUploadErrorAlertIfNeeded() { + let hasTerminalError = post.media.contains { + guard let error = $0.error else { return false } + return MediaCoordinator.isTerminalError(error) + } + if hasTerminalError { + let notice = Notice(title: Strings.failingMediaUploadsMessage, feedbackType: .error, actionTitle: Strings.failingMediaUploadsViewAction, actionHandler: { [weak self] _ in + self?.showMediaUploadDetails() + }) + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(700)) { + ActionDispatcherFacade().dispatch(NoticeAction.post(notice)) + } // Delay to let the editor show first + } + } + + private func showMediaUploadDetails() { + let viewController = PostMediaUploadsViewController(post: post) + let nav = UINavigationController(rootViewController: viewController) + nav.navigationBar.isTranslucent = true // Reset to default + viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .close, primaryAction: UIAction { [weak self] _ in + self?.dismiss(animated: true) + }) + if let sheetController = nav.sheetPresentationController { + sheetController.detents = [.medium(), .large()] + sheetController.prefersGrabberVisible = true + sheetController.preferredCornerRadius = 16 + nav.additionalSafeAreaInsets = UIEdgeInsets(top: 8, left: 0, bottom: 0, right: 0) + } + self.present(nav, animated: true) } } @@ -242,4 +333,14 @@ private enum Strings { static let autosaveAlertContinue = NSLocalizedString("autosaveAlert.viewChanges", value: "View Changes", comment: "An alert suggesting to load autosaved revision for a published post") static let autosaveAlertCancel = NSLocalizedString("autosaveAlert.cancel", value: "Cancel", comment: "An alert suggesting to load autosaved revision for a published post") + + static let trashedPostSheetTitle = NSLocalizedString("postEditor.recoverTrashedPostAlert.title", value: "Trashed %@", comment: "Editor, alert for recovering a trashed post") + static let trashedPostSheetMessage = NSLocalizedString("postEditor.recoverTrashedPostAlert.message", value: "A trashed %1$@ can't be edited. To edit this %1$@, you'll need to restore it by moving it back to a draft.", comment: "Editor, alert for recovering a trashed post") + static let trashedPostSheetCancel = NSLocalizedString("postEditor.recoverTrashedPostAlert.cancel", value: "Cancel", comment: "Editor, alert for recovering a trashed post") + static let trashedPostSheetRecover = NSLocalizedString("postEditor.recoverTrashedPostAlert.restore", value: "Restore", comment: "Editor, alert for recovering a trashed post") + static let trashedPostRestored = NSLocalizedString("postEditor.recoverTrashedPost.postRecoveredNoticeTitle", value: "Post restored as a draft", comment: "Editor, notice for successful recovery a trashed post") + + static let failingMediaUploadsMessage = NSLocalizedString("postEditor.postHasFailingMediaUploadsSnackbar.message", value: "Some media items failed to upload", comment: "A message for a snackbar informing the user that some media files requires their attention") + + static let failingMediaUploadsViewAction = NSLocalizedString("postEditor.postHasFailingMediaUploadsSnackbar.actionView", value: "View", comment: "A 'View' action for a snackbar informing the user that some media files requires their attention") } diff --git a/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift b/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift index db9fe23d2a50..b3be9854585d 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditorNavigationBarManager.swift @@ -117,7 +117,7 @@ class PostEditorNavigationBarManager { return button }() - private lazy var moreButton: UIButton = { + lazy var moreButton: UIButton = { let image = UIImage(named: "editor-more") let button = UIButton(type: .system) button.setImage(image, for: .normal) diff --git a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift index ffb01e458134..3fde3595718a 100644 --- a/WordPress/Classes/ViewRelated/Post/PostListViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/PostListViewController.swift @@ -161,7 +161,7 @@ final class PostListViewController: AbstractPostListViewController, InteractiveP let post = postAtIndexPath(indexPath) - guard post.status != .trash else { + if post.status == .trash && !FeatureFlag.syncPublishing.enabled { // No editing posts that are trashed. return } diff --git a/WordPress/Classes/ViewRelated/Post/PostMediaUploadsView.swift b/WordPress/Classes/ViewRelated/Post/PostMediaUploadsView.swift index a9e4f4a306e3..22818819b471 100644 --- a/WordPress/Classes/ViewRelated/Post/PostMediaUploadsView.swift +++ b/WordPress/Classes/ViewRelated/Post/PostMediaUploadsView.swift @@ -1,6 +1,19 @@ import Foundation import SwiftUI +final class PostMediaUploadsViewController: UIHostingController { + private let viewModel: PostMediaUploadsViewModel + + init(post: AbstractPost) { + self.viewModel = PostMediaUploadsViewModel(post: post) // Manange lifecycle + super.init(rootView: PostMediaUploadsView(viewModel: viewModel)) + } + + required dynamic init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + /// Displays upload progress for the media for the given post. struct PostMediaUploadsView: View { @ObservedObject var viewModel: PostMediaUploadsViewModel diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift index 93af5cf6eb91..336f8481866b 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+Swift.swift @@ -2,6 +2,7 @@ import UIKit import CoreData import Combine import WordPressKit +import SwiftUI extension PostSettingsViewController { static func make(for post: AbstractPost) -> PostSettingsViewController { @@ -178,6 +179,47 @@ extension PostSettingsViewController: UIAdaptivePresentationControllerDelegate { } } +// MARK: - PostSettingsViewController (Visibility) + +extension PostSettingsViewController { + @objc func showUpdatedPostVisibilityPicker() { + let view = PostVisibilityPicker(selection: .init(post: apost)) { [weak self] selection in + guard let self else { return } + + WPAnalytics.track(.editorPostVisibilityChanged, properties: ["via": "settings"]) + + switch selection.type { + case .public, .protected: + if self.apost.original().status == .scheduled { + // Keep it scheduled + } else { + self.apost.status = .publish + } + case .private: + if self.apost.original().status == .scheduled { + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) { + self.showWarningPostWillBePublishedAlert() + } + } + self.apost.status = .publishPrivate + } + self.apost.password = selection.password.isEmpty ? nil : selection.password + self.navigationController?.popViewController(animated: true) + self.reloadData() + } + let viewController = UIHostingController(rootView: view) + viewController.title = PostVisibilityPicker.title + viewController.configureDefaultNavigationBarAppearance() + navigationController?.pushViewController(viewController, animated: true) + } + + private func showWarningPostWillBePublishedAlert() { + let alert = UIAlertController(title: nil, message: Strings.warningPostWillBePublishedAlertMessage, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("postSettings.ok", value: "OK", comment: "Button OK"), style: .default)) + present(alert, animated: true) + } +} + // MARK: - PostSettingsViewController (Page Attributes) extension PostSettingsViewController { @@ -239,4 +281,6 @@ extension PostSettingsViewController { private enum Strings { static let errorMessage = NSLocalizedString("postSettings.updateFailedMessage", value: "Failed to update the post settings", comment: "Error message on post/page settings screen") + + static let warningPostWillBePublishedAlertMessage = NSLocalizedString("postSettings.warningPostWillBePublishedAlertMessage", value: "By changing the visibility to 'Private', the post will be published immediately", comment: "An alert message explaning that by changing the visibility to private, the post will be published immediately to your site") } diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m index 3b25081a3f3f..fa9f509a6054 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController.m @@ -67,7 +67,6 @@ @interface PostSettingsViewController () Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + onOverlayTapped?(self) + } +} diff --git a/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift b/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift index 23bfaac14d37..50f1c308f509 100644 --- a/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift +++ b/WordPress/Classes/ViewRelated/Post/PostVisibilityPicker.swift @@ -1,22 +1,26 @@ import SwiftUI struct PostVisibilityPicker: View { - @State private var selection: PostVisibility = .public - @State private var password = "" - @State private var isEnteringPassword = false + @State private var selection: Selection @State private var isDismissing = false + @FocusState private var isPasswordFieldFocused: Bool struct Selection { - var visibility: PostVisibility - var password: String? + var type: PostVisibility + var password = "" + + init(post: AbstractPost) { + self.type = PostVisibility(post: post) + self.password = post.password ?? "" + } } private let onSubmit: (Selection) -> Void static var title: String { Strings.title } - init(visibility: PostVisibility, onSubmit: @escaping (Selection) -> Void) { - self._selection = State(initialValue: visibility) + init(selection: Selection, onSubmit: @escaping (Selection) -> Void) { + self._selection = State(initialValue: selection) self.onSubmit = onSubmit } @@ -33,11 +37,13 @@ struct PostVisibilityPicker: View { private func makeRow(for visibility: PostVisibility) -> some View { Button(action: { withAnimation { + selection.type = visibility + selection.password = "" + if visibility == .protected { - isEnteringPassword = true + isPasswordFieldFocused = true } else { - selection = visibility - onSubmit(Selection(visibility: visibility, password: nil)) + onSubmit(selection) } } }, label: { @@ -47,52 +53,55 @@ struct PostVisibilityPicker: View { Text(visibility.localizedDetails) .font(.footnote) .foregroundStyle(.secondary) - .opacity(isEnteringPassword ? 0.5 : 1) + .opacity(visibility != .protected && isPasswordFieldFocused ? 0.4 : 1) } Spacer() Image(systemName: "checkmark") .tint(Color(uiColor: .primary)) - .opacity((selection == visibility && !isEnteringPassword) ? 1 : 0) + .opacity((selection.type == visibility && !isPasswordFieldFocused) ? 1 : 0) } }) .tint(.primary) - .disabled(isEnteringPassword && visibility != .protected) + .disabled(isPasswordFieldFocused && visibility != .protected) - if visibility == .protected, isEnteringPassword { + if visibility == .protected, selection.type == .protected { enterPasswordRows } } @ViewBuilder private var enterPasswordRows: some View { - PasswordField(password: $password) - .onSubmit(savePassword) + PasswordField(password: $selection.password, isFocused: isPasswordFieldFocused) + .focused($isPasswordFieldFocused) + .onSubmit(buttonSavePasswordTapped) - HStack { - Button(Strings.cancel) { - withAnimation { - password = "" - isEnteringPassword = false + if isPasswordFieldFocused { + HStack { + Button(Strings.cancel) { + withAnimation { + selection.type = .public + selection.password = "" + } } + .keyboardShortcut(.cancelAction) + Spacer() + Button(Strings.save, action: buttonSavePasswordTapped) + .font(.body.weight(.medium)) + .disabled(selection.password.trimmingCharacters(in: .whitespaces).isEmpty) } - .keyboardShortcut(.cancelAction) - Spacer() - Button(Strings.save, action: savePassword) - .font(.body.weight(.medium)) - .disabled(password.isEmpty) + .buttonStyle(.plain) + .foregroundStyle(Color(uiColor: .brand)) } - .buttonStyle(.plain) - .foregroundStyle(Color(uiColor: .brand)) } - private func savePassword() { + private func buttonSavePasswordTapped() { withAnimation { - selection = .protected - isEnteringPassword = false + isPasswordFieldFocused = false + selection.password = selection.password.trimmingCharacters(in: .whitespaces) isDismissing = true // Let the keyboard dismiss first to avoid janky animation DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(550)) { - onSubmit(Selection(visibility: .protected, password: password)) + onSubmit(selection) } } } @@ -101,13 +110,12 @@ struct PostVisibilityPicker: View { private struct PasswordField: View { @Binding var password: String @State var isSecure = true - @FocusState private var isFocused: Bool + let isFocused: Bool var body: some View { HStack { textField - .focused($isFocused) - if !password.isEmpty { + if isFocused && !password.isEmpty { Button(action: { password = "" }) { Image(systemName: "xmark.circle") .foregroundStyle(.secondary) @@ -119,8 +127,8 @@ private struct PasswordField: View { } } .buttonStyle(.plain) - .onAppear { isFocused = true } } + @ViewBuilder private var textField: some View { if isSecure { @@ -136,8 +144,12 @@ enum PostVisibility: Identifiable, CaseIterable { case `private` case protected + init(post: AbstractPost) { + self.init(status: post.status ?? .draft, password: post.password) + } + init(status: AbstractPost.Status, password: String?) { - if password != nil { + if let password, !password.isEmpty { self = .protected } else if status == .publishPrivate { self = .private @@ -163,7 +175,6 @@ enum PostVisibility: Identifiable, CaseIterable { case .private: NSLocalizedString("postVisibility.private.details", value: "Only visible to site admins and editors", comment: "Details for a 'Private' privacy setting") } } - } private enum Strings { diff --git a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift index 4f4a1a37a0f9..93269aaebe3f 100644 --- a/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Prepublishing/PrepublishingViewController.swift @@ -370,18 +370,17 @@ final class PrepublishingViewController: UIViewController, UITableViewDataSource // MARK: - Visibility private func configureVisibilityCell(_ cell: WPTableViewCell) { - cell.detailTextLabel?.text = viewModel.visibility.localizedTitle + cell.detailTextLabel?.text = viewModel.visibility.type.localizedTitle } private func didTapVisibilityCell() { - let view = PostVisibilityPicker(visibility: viewModel.visibility) { [weak self] selection in + let view = PostVisibilityPicker(selection: viewModel.visibility) { [weak self] selection in guard let self else { return } - self.viewModel.visibility = selection.visibility - if selection.visibility == .private { + self.viewModel.visibility = selection + if selection.type == .private { self.viewModel.publishDate = nil self.updatePublishButtonLabel() } - self.viewModel.password = selection.password self.reloadData() self.navigationController?.popViewController(animated: true) } @@ -401,7 +400,7 @@ final class PrepublishingViewController: UIViewController, UITableViewDataSource } else { cell.detailTextLabel?.text = Strings.immediately } - viewModel.visibility == .private ? cell.disable() : cell.enable() + viewModel.visibility.type == .private ? cell.disable() : cell.enable() } func didTapSchedule(_ indexPath: IndexPath) { @@ -543,8 +542,7 @@ extension PrepublishingOption { private final class PrepublishingViewModel { private let post: AbstractPost - var visibility: PostVisibility - var password: String? + var visibility: PostVisibilityPicker.Selection var publishDate: Date? var publishButtonTitle: String { @@ -557,8 +555,7 @@ private final class PrepublishingViewModel { init(post: AbstractPost) { self.post = post - self.visibility = PostVisibility(status: post.status ?? .draft, password: post.password) - self.password = post.password + self.visibility = .init(post: post) // Ask the user to provide the date every time (ignore the obscure WP dateCreated/dateModified logic) self.publishDate = nil } @@ -568,8 +565,8 @@ private final class PrepublishingViewModel { wpAssert(post.isRevision()) try await coordinator._publish(post.original(), options: .init( - visibility: visibility, - password: password, + visibility: visibility.type, + password: visibility.password, publishDate: publishDate )) } diff --git a/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift b/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift index c83410bf0356..c5e40da78f4c 100644 --- a/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Revisions/RevisionsTableViewController.swift @@ -1,3 +1,5 @@ +import UIKit + class RevisionsTableViewController: UITableViewController { typealias RevisionLoadedBlock = (AbstractPost) -> Void @@ -36,7 +38,7 @@ class RevisionsTableViewController: UITableViewController { required init(post: AbstractPost, onRevisionLoaded: @escaping RevisionLoadedBlock) { self.post = post self.onRevisionLoaded = onRevisionLoaded - super.init(nibName: nil, bundle: nil) + super.init(style: .insetGrouped) } required init?(coder aDecoder: NSCoder) { @@ -65,7 +67,7 @@ class RevisionsTableViewController: UITableViewController { private extension RevisionsTableViewController { private func setupUI() { - navigationItem.title = NSLocalizedString("History", comment: "Title of the post history screen") + navigationItem.title = Strings.title let cellNib = UINib(nibName: RevisionsTableViewCell.classNameWithoutNamespaces(), bundle: Bundle(for: RevisionsTableViewCell.self)) @@ -76,7 +78,9 @@ private extension RevisionsTableViewController { refreshControl.addTarget(self, action: #selector(refreshRevisions), for: .valueChanged) self.refreshControl = refreshControl - tableView.tableFooterView = tableViewFooter + if post?.original().isStatus(in: [.draft, .pending]) == false { + tableView.tableFooterView = tableViewFooter + } tableView.separatorColor = .divider WPStyleGuide.configureColors(view: view, tableView: tableView) @@ -113,7 +117,7 @@ private extension RevisionsTableViewController { @objc private func refreshRevisions() { if sectionCount == 0 { - configureAndDisplayNoResults(title: NoResultsText.loadingTitle, + configureAndDisplayNoResults(title: Strings.loading, accessoryView: NoResultsViewController.loadingAccessoryView()) } @@ -157,7 +161,7 @@ private extension RevisionsTableViewController { return } - SVProgressHUD.show(withStatus: NSLocalizedString("Loading...", comment: "Text displayed in HUD while a revision post is loading.")) + SVProgressHUD.show(withStatus: Strings.loading) let coreDataStack = ContextManager.shared let postRepository = PostRepository(coreDataStack: coreDataStack) @@ -229,16 +233,12 @@ extension RevisionsTableViewController: WPTableViewHandlerDelegate { return Sizes.cellEstimatedRowHeight } - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - guard let sectionInfo = tableViewHandler.resultsController?.sections?[section], - let headerView = Bundle.main.loadNibNamed(PageListSectionHeaderView.classNameWithoutNamespaces(), - owner: nil, - options: nil)?.first as? PageListSectionHeaderView else { - return UIView() + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + guard let sections = tableViewHandler.resultsController?.sections, + sections.indices.contains(section) else { + return nil } - - headerView.setTitle(sectionInfo.name) - return headerView + return sections[section].name } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { @@ -287,8 +287,8 @@ extension RevisionsTableViewController: RevisionsView { case (true, let count) where count == 0: // When the API call successed but there are no revisions loaded // This is an edge cas. It shouldn't happen since we open the revisions list only if the post revisions array is not empty. - configureAndDisplayNoResults(title: NoResultsText.noResultsTitle, - subtitle: NoResultsText.noResultsSubtitle) + configureAndDisplayNoResults(title: Strings.noResultsTitle, + subtitle: Strings.noResultsSubtitle) default: hideNoResults() } @@ -311,7 +311,7 @@ private extension Date { private static let shortDateFormatter: DateFormatter = { let formatter = DateFormatter() - formatter.dateStyle = .short + formatter.dateStyle = .medium formatter.timeStyle = .none return formatter }() @@ -326,10 +326,14 @@ private extension Date { } struct NoResultsText { - static let loadingTitle = NSLocalizedString("Loading history...", comment: "Displayed while a call is loading the history.") static let reloadButtonTitle = NSLocalizedString("Try again", comment: "Re-load the history again. It appears if the loading call fails.") - static let noResultsTitle = NSLocalizedString("No history yet", comment: "Displayed when a call is made to load the revisions but there's no result or an error.") - static let noResultsSubtitle = NSLocalizedString("When you make changes in the editor you'll be able to see the history here", comment: "Displayed when a call is made to load the history but there's no result or an error.") static let errorTitle = NSLocalizedString("Oops", comment: "Title for the view when there's an error loading the history") static let errorSubtitle = NSLocalizedString("There was an error loading the history", comment: "Text displayed when there is a failure loading the history.") } + +private enum Strings { + static let title = NSLocalizedString("revisions.title", value: "Revisions", comment: "Post revisions list screen title") + static let loading = NSLocalizedString("revisions.loadingTitle", value: "Loading…", comment: "Post revisions list screen / loading view title") + static let noResultsTitle = NSLocalizedString("revisions.emptyStateTitle", value: "No revisions yet", comment: "Displayed when a call is made to load the revisions but there's no result or an error.") + static let noResultsSubtitle = NSLocalizedString("revisions.emptyStateSubtitle", value: "When you make changes in the editor you'll be able to see the revision history here", comment: "Displayed when a call is made to load the history but there's no result or an error.") +} diff --git a/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewController.swift b/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewController.swift index 1e4b1aa8b95c..cce161070ad8 100644 --- a/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewController.swift +++ b/WordPress/Classes/ViewRelated/Post/Search/PostSearchViewController.swift @@ -164,7 +164,10 @@ final class PostSearchViewController: UIViewController, UITableViewDelegate, UIS switch viewModel.posts[indexPath.row].latest() { case let post as Post: - guard post.status != .trash else { return } + if post.status == .trash && !FeatureFlag.syncPublishing.enabled { + // No editing posts that are trashed. + return + } delegate?.edit(post) case let page as Page: guard page.status != .trash else { return } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift b/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift index 7d497ecef7bf..378af68ae902 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderFollowedSitesViewController.swift @@ -455,7 +455,7 @@ extension ReaderFollowedSitesViewController: WPTableViewHandlerDelegate { let count = tableViewHandler.resultsController?.fetchedObjects?.count ?? 0 if count > 0 { - return NSLocalizedString("Followed Sites", comment: "Section title for sites the user has followed.") + return NSLocalizedString("reader.followedSites.empty", value: "Subscribed Sites", comment: "Section title for sites the user has subscribed to.") } return nil } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift b/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift index 5800dbda1994..fc4a935209f8 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderPost+Display.swift @@ -32,10 +32,12 @@ extension ReaderPost { .replacingOccurrences(of: "^\n+", with: "", options: .regularExpression) .replacingOccurrences(of: "\n{2,}", with: "\n\n", options: .regularExpression) .trim() - return content ?? contentPreviewForDisplay() - } else { - return contentPreviewForDisplay() + if let content { + let maxContentLength = isPad ? 4000 : 500 + return String(content.prefix(maxContentLength)) + } } + return contentPreviewForDisplay() } func countsForDisplay(isLoggedIn: Bool) -> String? { diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift index de7884ac049c..24d6f7639dac 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.swift @@ -123,6 +123,7 @@ private extension ReaderTagCardCell { func registerCells() { let tagCell = UINib(nibName: ReaderTagCell.classNameWithoutNamespaces(), bundle: nil) let footerView = UINib(nibName: ReaderTagFooterView.classNameWithoutNamespaces(), bundle: nil) + collectionView.register(ReaderTagCardEmptyCell.self, forCellWithReuseIdentifier: ReaderTagCardEmptyCell.defaultReuseID) collectionView.register(tagCell, forCellWithReuseIdentifier: ReaderTagCell.classNameWithoutNamespaces()) collectionView.register(footerView, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib index ed4a207514d1..210c8fdec766 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCell.xib @@ -25,12 +25,12 @@ - + - + @@ -43,7 +43,7 @@ - + diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift index 1c6db21d25ff..cf3b182844f3 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardCellViewModel.swift @@ -6,8 +6,18 @@ protocol ReaderTagCardCellViewModelDelegate: NSObjectProtocol { class ReaderTagCardCellViewModel: NSObject { - private typealias DataSource = UICollectionViewDiffableDataSource - private typealias Snapshot = NSDiffableDataSourceSnapshot + enum Section: Int { + case emptyState = 101 + case posts + } + + enum CardCellItem: Hashable { + case empty + case post(id: NSManagedObjectID) + } + + private typealias DataSource = UICollectionViewDiffableDataSource + private typealias Snapshot = NSDiffableDataSourceSnapshot let slug: String weak var viewDelegate: ReaderTagCardCellViewModelDelegate? = nil @@ -22,29 +32,21 @@ class ReaderTagCardCellViewModel: NSObject { .init(coreDataStack: coreDataStack) }() - private lazy var dataSource: DataSource? = { - guard let collectionView else { + private lazy var dataSource: DataSource? = { [weak self] in + guard let self, + let collectionView else { return nil } - let dataSource = DataSource(collectionView: collectionView) { [weak self] collectionView, indexPath, objectID in - guard let post = try? ContextManager.shared.mainContext.existingObject(with: objectID) as? ReaderPost, - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ReaderTagCell.classNameWithoutNamespaces(), for: indexPath) as? ReaderTagCell else { - return UICollectionViewCell() - } - cell.configure(parent: self?.parentViewController, - post: post, - isLoggedIn: self?.isLoggedIn ?? AccountHelper.isLoggedIn) - return cell - } - dataSource.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in - guard let slug = self?.slug, - kind == UICollectionView.elementKindSectionFooter, + + let dataSource = DataSource(collectionView: collectionView, cellProvider: self.cardCellProvider) + dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in + guard kind == UICollectionView.elementKindSectionFooter, let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ReaderTagFooterView.classNameWithoutNamespaces(), for: indexPath) as? ReaderTagFooterView else { return nil } - view.configure(with: slug) { [weak self] in + view.configure(with: self.slug) { [weak self] in self?.onTagButtonTapped() } return view @@ -65,6 +67,8 @@ class ReaderTagCardCellViewModel: NSObject { return resultsController }() + // MARK: Methods + init(parent: UIViewController?, tag: ReaderTagTopic, collectionView: UICollectionView?, @@ -122,13 +126,65 @@ class ReaderTagCardCellViewModel: NSObject { } +// MARK: - Private Methods + +private extension ReaderTagCardCellViewModel { + /// Configures and returns a collection view cell according to the index path and `CardCellItem`. + /// This method that satisfies the `UICollectionViewDiffableDataSource.CellProvider` closure signature. + func cardCellProvider(_ collectionView: UICollectionView, + _ indexPath: IndexPath, + _ item: CardCellItem) -> UICollectionViewCell? { + switch item { + case .empty: + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ReaderTagCardEmptyCell.defaultReuseID, + for: indexPath) as? ReaderTagCardEmptyCell else { + return UICollectionViewCell() + } + + cell.configure(tagTitle: slug) { [weak self] in + self?.fetchTagPosts(syncRemotely: true) + } + + return cell + + case .post(let objectID): + guard let post = try? ContextManager.shared.mainContext.existingObject(with: objectID) as? ReaderPost, + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ReaderTagCell.classNameWithoutNamespaces(), + for: indexPath) as? ReaderTagCell else { + return UICollectionViewCell() + } + + cell.configure(parent: parentViewController, post: post, isLoggedIn: isLoggedIn) + return cell + } + } + + /// Translates a diffable snapshot from `NSFetchedResultsController` to a snapshot that fits the collection view. + /// + /// Snapshots returned from `NSFetchedResultsController` always have the type ``, so + /// it needs to be converted to match the correct type required by the collection view. + /// + /// - Parameter snapshotRef: The snapshot returned from the `NSFetchedResultsController` + /// - Returns: `Snapshot` + private func collectionViewSnapshot(from snapshotRef: NSDiffableDataSourceSnapshotReference) -> Snapshot { + let coreDataSnapshot = snapshotRef as NSDiffableDataSourceSnapshot + let isEmpty = coreDataSnapshot.numberOfItems == .zero + var snapshot = Snapshot() + + // there must be at least one section. + snapshot.appendSections([isEmpty ? .emptyState : .posts]) + snapshot.appendItems(isEmpty ? [.empty] : coreDataSnapshot.itemIdentifiers.map { .post(id: $0) }) + + return snapshot + } +} + // MARK: - NSFetchedResultsControllerDelegate extension ReaderTagCardCellViewModel: NSFetchedResultsControllerDelegate { - func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { - dataSource?.apply(snapshot as Snapshot, animatingDifferences: false) { [weak self] in + dataSource?.apply(collectionViewSnapshot(from: snapshot), animatingDifferences: false) { [weak self] in self?.viewDelegate?.hideLoading() } } @@ -140,7 +196,9 @@ extension ReaderTagCardCellViewModel: NSFetchedResultsControllerDelegate { extension ReaderTagCardCellViewModel: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let sectionInfo = resultsController.sections?[safe: indexPath.section], + guard let section = dataSource?.sectionIdentifier(for: indexPath.section), + section == .posts, + let sectionInfo = resultsController.sections?[safe: indexPath.section], indexPath.row < sectionInfo.numberOfObjects else { return } @@ -156,10 +214,25 @@ extension ReaderTagCardCellViewModel: UICollectionViewDelegate { extension ReaderTagCardCellViewModel: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return cellSize() ?? .zero + guard let section = dataSource?.sectionIdentifier(for: indexPath.section), + let size = cellSize() else { + return .zero + } + + switch section { + case .emptyState: + return CGSize(width: collectionView.frame.width, height: size.height) + case .posts: + return size + } } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { + guard let sectionIdentifier = dataSource?.sectionIdentifier(for: section), + sectionIdentifier == .posts else { + return .zero + } + var viewSize = cellSize() ?? .zero viewSize.width = Constants.footerWidth return viewSize diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCardEmptyCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardEmptyCell.swift new file mode 100644 index 000000000000..3a9516e8f6c2 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCardEmptyCell.swift @@ -0,0 +1,120 @@ +import SwiftUI +import DesignSystem + +class ReaderTagCardEmptyCell: UICollectionViewCell, Reusable { + + var tagTitle: String { + get { + swiftUIView.tagTitle + } + set { + swiftUIView.tagTitle = newValue + } + } + + var retryHandler: (() -> Void)? = nil + + private lazy var swiftUIView: ReaderTagCardEmptyCellView = { + ReaderTagCardEmptyCellView(buttonTapped: { [weak self] in + self?.retryHandler?() + }) + }() + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(frame: CGRect) { + super.init(frame: .zero) + + let viewToEmbed = UIView.embedSwiftUIView(swiftUIView) + contentView.addSubview(viewToEmbed) + contentView.pinSubviewToAllEdges(viewToEmbed) + } + + override func prepareForReuse() { + tagTitle = String() + retryHandler = nil + super.prepareForReuse() + } + + func configure(tagTitle: String, retryHandler: (() -> Void)?) { + self.tagTitle = tagTitle + self.retryHandler = retryHandler + } +} + +// MARK: - SwiftUI + +private struct ReaderTagCardEmptyCellView: View { + + var tagTitle = String() + var buttonTapped: (() -> Void)? = nil + + @ScaledMetric(relativeTo: Font.TextStyle.callout) + private var iconLength = 32.0 + + var body: some View { + VStack(spacing: .DS.Padding.double) { + Image(systemName: "wifi.slash") + .resizable() + .frame(width: iconLength, height: iconLength) + .foregroundStyle(Color.DS.Foreground.secondary) + + // added to double the padding between the Image and the VStack. + Spacer().frame(height: .hairlineBorderWidth) + + VStack(spacing: .DS.Padding.single) { + Text(Strings.title) + .font(.callout) + .fontWeight(.semibold) + + Text(Strings.body) + .font(.callout) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + } + + Button { + buttonTapped?() + } label: { + Text(Strings.buttonTitle) + .font(.callout) + .padding(.vertical, .DS.Padding.half) + .padding(.horizontal, .DS.Padding.single) + } + } + .padding(.DS.Padding.single) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } + + private struct Strings { + static let title = NSLocalizedString( + "reader.tagStream.cards.emptyView.error.title", + value: "Posts failed to load", + comment: """ + The title of an empty state component for one of the tags in the tag stream. + This empty state component is displayed only when the app fails to load posts under this tag. + """ + ) + + static let body = NSLocalizedString( + "reader.tagStream.cards.emptyView.error.body", + value: "We couldn't load posts from this tag right now", + comment: """ + The body text of an empty state component for one of the tags in the tag stream. + This empty state component is displayed only when the app fails to load posts under this tag. + """ + ) + + static let buttonTitle = NSLocalizedString( + "reader.tagStream.cards.emptyView.button", + value: "Retry", + comment: """ + Verb. The button title of an empty state component for one of the tags in the tag stream. + This empty state component is displayed only when the app fails to load posts under this tag. + When tapped, the app will try to reload posts under this tag. + """ + ) + } +} diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagCell.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagCell.swift index 3e4ede23b710..59e55baf4d97 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagCell.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagCell.swift @@ -2,6 +2,8 @@ import WordPressUI class ReaderTagCell: UICollectionViewCell { + private typealias AccessibilityConstants = ReaderPostCardCell.Constants.Accessibility + @IBOutlet private weak var contentStackView: UIStackView! @IBOutlet private weak var headerStackView: UIStackView! @IBOutlet private weak var siteLabel: UILabel! @@ -38,24 +40,7 @@ class ReaderTagCell: UICollectionViewCell { func configure(parent: UIViewController?, post: ReaderPost, isLoggedIn: Bool) { viewModel = ReaderTagCellViewModel(parent: parent, post: post, isLoggedIn: isLoggedIn) - let blogName = post.blogNameForDisplay() - let postDate = post.shortDateForDisplay() - let postTitle = post.titleForDisplay() - let postSummary = post.summaryForDisplay(isPad: traitCollection.userInterfaceIdiom == .pad) - let postCounts = post.countsForDisplay(isLoggedIn: isLoggedIn) - - siteLabel.text = blogName - postDateLabel.text = postDate - titleLabel.text = postTitle - summaryLabel.text = postSummary - countsLabel.text = postCounts - - siteLabel.isHidden = blogName == nil - postDateLabel.isHidden = postDate == nil - titleLabel.isHidden = postTitle == nil - summaryLabel.isHidden = postSummary == nil - countsLabel.isHidden = postCounts == nil - + setupLabels(with: post, isLoggedIn: isLoggedIn) configureLikeButton(with: post) loadFeaturedImage(with: post) } @@ -131,6 +116,35 @@ private extension ReaderTagCell { likeButton.setImage(isLiked ? Constants.likedButtonImage : Constants.likeButtonImage, for: .normal) likeButton.tintColor = isLiked ? .jetpackGreen : .secondaryLabel likeButton.setTitleColor(likeButton.tintColor, for: .normal) + likeButton.accessibilityHint = post.isLiked ? AccessibilityConstants.likedButtonHint : AccessibilityConstants.likeButtonHint + } + + func setupLabels(with post: ReaderPost, isLoggedIn: Bool) { + let blogName = post.blogNameForDisplay() + let postDate = post.shortDateForDisplay() + let postTitle = post.titleForDisplay() + let postSummary = post.summaryForDisplay(isPad: traitCollection.userInterfaceIdiom == .pad) + let postCounts = post.countsForDisplay(isLoggedIn: isLoggedIn) + + siteLabel.text = blogName + postDateLabel.text = postDate + titleLabel.text = postTitle + summaryLabel.text = postSummary + countsLabel.text = postCounts + + siteLabel.isHidden = blogName == nil + postDateLabel.isHidden = postDate == nil + titleLabel.isHidden = postTitle == nil + summaryLabel.isHidden = postSummary == nil + countsLabel.isHidden = postCounts == nil + + headerStackView.isAccessibilityElement = true + headerStackView.accessibilityLabel = [blogName, postDate].compactMap { $0 }.joined(separator: ", ") + headerStackView.accessibilityHint = AccessibilityConstants.siteStackViewHint + headerStackView.accessibilityTraits = .button + countsLabel.accessibilityLabel = postCounts?.replacingOccurrences(of: " • ", with: ", ") + menuButton.accessibilityLabel = AccessibilityConstants.menuButtonLabel + menuButton.accessibilityHint = AccessibilityConstants.menuButtonHint } } diff --git a/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.swift b/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.swift index eafdfd17b493..3834b290e521 100644 --- a/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.swift +++ b/WordPress/Classes/ViewRelated/Reader/ReaderTagFooterView.swift @@ -12,10 +12,14 @@ class ReaderTagFooterView: UICollectionReusableView { setupStyles() let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onViewTapped)) addGestureRecognizer(tapGesture) + isAccessibilityElement = true + accessibilityTraits = .button } func configure(with slug: String, onTapped: @escaping () -> Void) { - moreLabel.setText(String(format: Constants.moreText, slug)) + let moreText = String(format: Constants.moreText, slug) + moreLabel.setText(moreText) + accessibilityLabel = moreText self.onTapped = onTapped } diff --git a/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderSelectInterestsViewController.swift b/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderSelectInterestsViewController.swift index d7574f346ac9..521f597aaa36 100644 --- a/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderSelectInterestsViewController.swift +++ b/WordPress/Classes/ViewRelated/Reader/Select Interests/ReaderSelectInterestsViewController.swift @@ -16,6 +16,7 @@ struct ReaderSelectInterestsConfiguration { class ReaderSelectInterestsViewController: UIViewController { private struct Constants { static let reuseIdentifier = ReaderInterestsCollectionViewCell.classNameWithoutNamespaces() + static let defaultCellIdentifier = "DefaultCell" static let interestsLabelMargin: CGFloat = 12 static let cellCornerRadius: CGFloat = 5 @@ -168,6 +169,7 @@ class ReaderSelectInterestsViewController: UIViewController { private func configureCollectionView() { let nib = UINib(nibName: String(describing: ReaderInterestsCollectionViewCell.self), bundle: nil) collectionView.register(nib, forCellWithReuseIdentifier: Constants.reuseIdentifier) + collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: Constants.defaultCellIdentifier) guard let layout = collectionView.collectionViewLayout as? ReaderInterestsCollectionViewFlowLayout else { return @@ -325,7 +327,7 @@ extension ReaderSelectInterestsViewController: UICollectionViewDataSource { guard let interest = dataSource.interest(for: indexPath.row) else { CrashLogging.main.logMessage("ReaderSelectInterestsViewController: Requested for data at invalid row", properties: ["row": indexPath.row], level: .warning) - return .init(frame: .zero) + return collectionView.dequeueReusableCell(withReuseIdentifier: Constants.defaultCellIdentifier, for: indexPath) } ReaderInterestsStyleGuide.applyCellLabelStyle(label: cell.label, diff --git a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift index 677e7ebf0e9c..430916f4ae63 100644 --- a/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift +++ b/WordPress/Classes/ViewRelated/Site Creation/Wizard/SiteCreator.swift @@ -71,7 +71,7 @@ final class SiteCreator { // MARK: - Helper Extensions -extension String { +private extension String { var subdomain: String { return components(separatedBy: ".").first ?? "" } diff --git a/WordPress/Classes/ViewRelated/Stats/Charts/StatsChartMarker.swift b/WordPress/Classes/ViewRelated/Stats/Charts/StatsChartMarker.swift new file mode 100644 index 000000000000..dcc9d8abc6d6 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Charts/StatsChartMarker.swift @@ -0,0 +1,281 @@ +import DGCharts + +class StatsChartMarker: MarkerView { + var dotColor: UIColor + var name: String + var minimumSize = CGSize() + + private var tooltipLabel: NSMutableAttributedString? + private var labelSize: CGSize = CGSize() + private var size: CGSize = CGSize() + var paragraphStyle: NSMutableParagraphStyle = { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = .center + paragraphStyle.lineSpacing = 4.0 + return paragraphStyle + }() + + public init(dotColor: UIColor, name: String) { + self.dotColor = dotColor + self.name = name + + super.init(frame: CGRect.zero) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func drawRect(context: CGContext, point: CGPoint) -> CGRect { + let chart = super.chartView + let width = size.width + + var rect = CGRect(origin: point, size: size) + + if point.y - size.height < 0 { + if point.x - size.width / 2.0 < 0 { + drawTopLeftRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) + } else if let chartWidth = chart?.bounds.width, point.x + width - size.width / 2.0 > chartWidth { + rect.origin.x -= size.width + drawTopRightRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) + } else { + rect.origin.x -= size.width / 2.0 + drawTopCenterRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) + } + + rect.origin.y += Constants.topInsets.top + rect.size.height -= Constants.topInsets.top + Constants.topInsets.bottom + } else { + rect.origin.y -= size.height + + if point.x - size.width / 2.0 < 0 { + drawLeftRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) + } else if let chartWidth = chart?.bounds.width, point.x + width - size.width / 2.0 > chartWidth { + rect.origin.x -= size.width + drawRightRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) + } else { + rect.origin.x -= size.width / 2.0 + drawCenterRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) + } + + rect.origin.y += Constants.insets.top + rect.size.height -= Constants.insets.top + Constants.insets.bottom + } + + return rect + } + + func drawDot(context: CGContext, xPosition: CGFloat, yPosition: CGFloat) { + context.setLineWidth(Constants.dotBorderWidth) + context.setStrokeColor(Constants.dotBorderColor) + context.setFillColor(dotColor.cgColor) + context.setShadow(offset: CGSize.zero, blur: Constants.shadowBlur, color: Constants.shadowColor) + + let square = CGRect(x: xPosition, y: yPosition, width: Constants.dotRadius * 2, height: Constants.dotRadius * 2) + context.addEllipse(in: square) + context.drawPath(using: .fillStroke) + } + + func drawCenterRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { + let arrowHeight = Constants.arrowSize.height + let arrowWidth = Constants.arrowSize.width + + drawDot(context: context, xPosition: x + width / 2.0 - Constants.dotRadius, yPosition: y + height - Constants.dotRadius) + + // Draw tooltip + context.setFillColor(Constants.tooltipColor.cgColor) + context.beginPath() + context.move(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y - Constants.dotRadius)) + // Top right corner + context.addQuadCurve(to: CGPoint(x: x + width, y: y + Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x + width, y: y - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius)) + // Bottom right corner + context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius), control: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + (width + arrowWidth) / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width / 2.0, y: y + height - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + (width - arrowWidth) / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius)) + // Bottom left corner + context.addQuadCurve(to: CGPoint(x: x, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x, y: y + Constants.cornerRadius - Constants.dotRadius)) + // Top left corner + context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius), control: CGPoint(x: x, y: y - Constants.dotRadius)) + context.fillPath() + } + + func drawLeftRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { + let arrowHeight = Constants.arrowSize.height + let arrowWidth = Constants.arrowSize.width + + drawDot(context: context, xPosition: x - Constants.dotRadius, yPosition: y + height - Constants.dotRadius) + + // Draw tooltip + context.setFillColor(Constants.tooltipColor.cgColor) + context.beginPath() + context.move(to: CGPoint(x: x, y: y - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y - Constants.dotRadius)) + // Top right corner + context.addQuadCurve(to: CGPoint(x: x + width, y: y + Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x + width, y: y - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius)) + // Bottom right corner + context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius), control: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + arrowWidth / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x, y: y + height - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x, y: y + Constants.cornerRadius - Constants.dotRadius)) + // Top left corner + context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius), control: CGPoint(x: x, y: y - Constants.dotRadius)) + context.fillPath() + } + + func drawRightRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { + let arrowHeight = Constants.arrowSize.height + let arrowWidth = Constants.arrowSize.width + + drawDot(context: context, xPosition: x + width - Constants.dotRadius, yPosition: y + height - Constants.dotRadius) + + // Draw tooltip + context.setFillColor(Constants.tooltipColor.cgColor) + context.beginPath() + context.move(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y - Constants.dotRadius)) + // Top right corner + context.addQuadCurve(to: CGPoint(x: x + width, y: y + Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x + width, y: y - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width, y: y + height - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width - arrowWidth / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius)) + // Bottom left corner + context.addQuadCurve(to: CGPoint(x: x, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x, y: y + height - arrowHeight - Constants.dotRadius)) + context.addLine(to: CGPoint(x: x, y: y + Constants.cornerRadius - Constants.dotRadius)) + // Top left corner + context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius), control: CGPoint(x: x, y: y - Constants.dotRadius)) + context.fillPath() + } + + func drawTopCenterRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { + let arrowHeight = Constants.arrowSize.height + let arrowWidth = Constants.arrowSize.width + + drawDot(context: context, xPosition: x + width / 2.0 - Constants.dotRadius, yPosition: y - Constants.dotRadius) + // Draw tooltip + context.setFillColor(Constants.tooltipColor.cgColor) + context.beginPath() + context.move(to: CGPoint(x: x + width / 2.0, y: y + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + (width + arrowWidth) / 2.0, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius)) + // Top right corner + context.addQuadCurve(to: CGPoint(x: x + width, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x + width, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width, y: y + height - Constants.cornerRadius + Constants.dotRadius)) + // Bottom right corner + context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height + Constants.dotRadius), control: CGPoint(x: x + width, y: y + height + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height + Constants.dotRadius)) + // Bottom left corner + context.addQuadCurve(to: CGPoint(x: x, y: y + height - Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x, y: y + height + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius)) + // Top left corner + context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius), control: CGPoint(x: x, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + (width - arrowWidth) / 2.0, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width / 2.0, y: y + Constants.dotRadius)) + context.fillPath() + } + + func drawTopLeftRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { + let arrowHeight = Constants.arrowSize.height + let arrowWidth = Constants.arrowSize.width + + drawDot(context: context, xPosition: x - Constants.dotRadius, yPosition: y - Constants.dotRadius) + + // Draw tooltip + context.setFillColor(Constants.tooltipColor.cgColor) + context.beginPath() + context.move(to: CGPoint(x: x, y: y + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + arrowWidth / 2.0, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius)) + // Top right corner + context.addQuadCurve(to: CGPoint(x: x + width, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x + width, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width, y: y - Constants.cornerRadius + height + Constants.dotRadius)) + // Bottom right corner + context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height + Constants.dotRadius), control: CGPoint(x: x + width, y: y + height + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height + Constants.dotRadius)) + // Bottom left corner + context.addQuadCurve(to: CGPoint(x: x, y: y + height - Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x, y: y + height + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x, y: y + Constants.dotRadius)) + context.fillPath() + } + + func drawTopRightRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { + let arrowHeight = Constants.arrowSize.height + + drawDot(context: context, xPosition: x + width - Constants.dotRadius, yPosition: y - Constants.dotRadius) + + // Draw tooltip + context.setFillColor(Constants.tooltipColor.cgColor) + context.beginPath() + context.move(to: CGPoint(x: x + width, y: y + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width, y: y + height + Constants.dotRadius - Constants.cornerRadius)) + // Bottom right corner + context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height + Constants.dotRadius), control: CGPoint(x: x + width, y: y + height + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height + Constants.dotRadius)) + // Bottom left corner + context.addQuadCurve(to: CGPoint(x: x, y: y + height - Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x, y: y + height + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius)) + // Top left corner + context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius), control: CGPoint(x: x, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width - arrowHeight / 2.0, y: y + arrowHeight + Constants.dotRadius)) + context.addLine(to: CGPoint(x: x + width, y: y + Constants.dotRadius)) + context.fillPath() + } + + override func draw(context: CGContext, point: CGPoint) { + guard let tooltipLabel = tooltipLabel else { + return + } + + context.saveGState() + let rect = drawRect(context: context, point: point) + UIGraphicsPushContext(context) + tooltipLabel.draw(in: rect) + UIGraphicsPopContext() + context.restoreGState() + } + + func text(for entry: ChartDataEntry) -> NSMutableAttributedString? { + return nil + } + + override func refreshContent(entry: ChartDataEntry, highlight: Highlight) { + + guard let text = text(for: entry) else { return } + tooltipLabel = text + + labelSize = text.size() + size.width = labelSize.width + Constants.insets.left + Constants.insets.right + size.height = labelSize.height + Constants.insets.top + Constants.insets.bottom + size.width = max(minimumSize.width, size.width) + size.height = max(minimumSize.height, size.height) + } +} + +private extension StatsChartMarker { + enum Constants { + static var tooltipColor: UIColor { + return UIColor(color: .muriel(name: .blue, .shade100)) + } + + static var shadowColor: CGColor { + return UIColor(red: 50 / 255, green: 50 / 255, blue: 71 / 255, alpha: 0.06).cgColor + } + + static var dotBorderColor: CGColor { + return UIColor.white.cgColor + } + + static let arrowSize = CGSize(width: 12, height: 8) + static let insets = UIEdgeInsets(top: 2.0, left: 16.0, bottom: 26.0, right: 16.0) + static let topInsets = UIEdgeInsets(top: 26.0, left: 8.0, bottom: 2.0, right: 8.0) + static let dotRadius = 8.0 + static let dotBorderWidth = 4.0 + static let cornerRadius = 10.0 + static let shadowBlur = 5.0 + } +} diff --git a/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartConfiguration.swift b/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartConfiguration.swift index 348909a8285f..2c1bdd1648ee 100644 --- a/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartConfiguration.swift +++ b/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartConfiguration.swift @@ -1,5 +1,10 @@ - struct StatsLineChartConfiguration { + enum StatType { + case viewsAndVisitors(StatsInsightsFilterDimension) + case subscribers + } + + let type: StatType let data: LineChartDataConvertible let styling: LineChartStyling let analyticsGranularity: LineChartAnalyticsPropertyGranularityValue? diff --git a/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift b/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift index b02c36c26dcd..10a8ddbeb5a8 100644 --- a/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift +++ b/WordPress/Classes/ViewRelated/Stats/Charts/StatsLineChartView.swift @@ -28,9 +28,13 @@ class StatsLineChartView: LineChartView { static let xAxisWidth = 4.0 static let xAxisTickWidth = 2.0 static let lineWidth = 2.0 - static let numberDaysInWeek = 7 + static let numberOfXAxisTicks = 7 } + /// The type of stat shown on the chart i.e. subscribers, views & visitors, etc. + /// + private let statType: StatsLineChartConfiguration.StatType + /// This adapts the data set for presentation by the Charts framework. /// private let lineChartData: LineChartDataConvertible @@ -51,8 +55,6 @@ class StatsLineChartView: LineChartView { /// private weak var statsLineChartViewDelegate: StatsLineChartViewDelegate? - private var statsInsightsFilterDimension: StatsInsightsFilterDimension - private var primaryDataSet: ChartDataSetProtocol? { return data?.dataSets.first } @@ -71,13 +73,13 @@ class StatsLineChartView: LineChartView { updateXAxisTicks() } - init(configuration: StatsLineChartConfiguration, delegate: StatsLineChartViewDelegate? = nil, statsInsightsFilterDimension: StatsInsightsFilterDimension = .views) { + init(configuration: StatsLineChartConfiguration, delegate: StatsLineChartViewDelegate? = nil) { + self.statType = configuration.type self.lineChartData = configuration.data self.styling = configuration.styling self.analyticsGranularity = configuration.analyticsGranularity self.statsLineChartViewDelegate = delegate self.xAxisDates = configuration.xAxisDates - self.statsInsightsFilterDimension = statsInsightsFilterDimension super.init(frame: .zero) @@ -102,7 +104,6 @@ private extension StatsLineChartView { configureChartViewBaseProperties() configureXAxis() - configureYAxis() } func captureAnalyticsEvent() { @@ -112,9 +113,12 @@ private extension StatsLineChartView { properties[LineChartAnalyticsPropertyGranularityKey] = specifiedAnalyticsGranularity.rawValue } - properties[LineChartAnalyticsPropertyKey] = statsInsightsFilterDimension.analyticsProperty - - WPAnalytics.track(.statsLineChartTapped, properties: properties) + if case let .viewsAndVisitors(statsInsightsFilterDimension) = statType { + properties[LineChartAnalyticsPropertyKey] = statsInsightsFilterDimension.analyticsProperty + WPAnalytics.track(.statsLineChartTapped, properties: properties) + } else if case .subscribers = statType { + WPAnalytics.track(.statsSubscribersChartTapped) + } } func configureAndPopulateData() { @@ -130,7 +134,7 @@ private extension StatsLineChartView { data = lineChartData - configureYAxisMaximum() + configureYAxis() } func configureLineChartViewBaseProperties() { @@ -222,7 +226,7 @@ private extension StatsLineChartView { if contentRect.width > 0 { xAxis.axisLineWidth = Constants.xAxisWidth - let count = max(xAxisDates.count, Constants.numberDaysInWeek) + let count = Constants.numberOfXAxisTicks let contentWidthMinusTicks = contentRect.width - (Constants.xAxisTickWidth * CGFloat(count)) xAxis.axisLineDashLengths = [Constants.xAxisTickWidth, (contentWidthMinusTicks / CGFloat(count - 1))] } @@ -232,36 +236,67 @@ private extension StatsLineChartView { let yAxis = leftAxis yAxis.axisLineColor = styling.lineColor - yAxis.axisMinimum = 0.0 yAxis.drawAxisLineEnabled = false yAxis.drawLabelsEnabled = true yAxis.drawZeroLineEnabled = true yAxis.gridColor = styling.lineColor yAxis.labelTextColor = styling.labelColor yAxis.labelFont = WPStyleGuide.fontForTextStyle(.footnote, symbolicTraits: [], maximumPointSize: WPStyleGuide.Stats.maximumChartAxisFontPointSize) - yAxis.setLabelCount(Constants.verticalAxisLabelCount, force: true) yAxis.valueFormatter = styling.yAxisValueFormatter yAxis.zeroLineColor = styling.lineColor // This adjustment is intended to prevent clipping observed with some labels // Potentially relevant : https://github.com/danielgindi/Charts/issues/992 extraTopOffset = Constants.topOffset - } - - func configureYAxisMaximum() { - let lowestMaxValue = Double(Constants.verticalAxisLabelCount - 1) - if let maxY = data?.getYMax(axis: .left), - maxY >= lowestMaxValue { - leftAxis.axisMaximum = VerticalAxisFormatter.roundUpAxisMaximum(maxY) - } else { - leftAxis.axisMaximum = lowestMaxValue + guard let data else { return } + let yAxisMax = data.getYMax(axis: .left) + + if case .viewsAndVisitors = statType { + yAxis.setLabelCount(Constants.verticalAxisLabelCount, force: true) + + yAxis.axisMinimum = 0 + + let lowestMaxValue = Double(Constants.verticalAxisLabelCount - 1) + let dataYMax = yAxisMax + if dataYMax >= lowestMaxValue { + yAxis.axisMaximum = VerticalAxisFormatter.roundUpAxisMaximum(dataYMax) + } else { + leftAxis.axisMaximum = lowestMaxValue + } + } else if case .subscribers = statType { + let yAxisMin = data.getYMin(axis: .left) + if yAxisMax == yAxisMin { + yAxis.setLabelCount(Constants.verticalAxisLabelCount, force: true) + + yAxis.axisMinimum = 0 + yAxis.axisMaximum = yAxisMax * 2 + } else { + + let yAxisDelta = Int(yAxisMax) - Int(yAxisMin) + let yAxisLabelCount = min(yAxisDelta + 1, Constants.verticalAxisLabelCount) + yAxis.setLabelCount(yAxisLabelCount, force: true) + + // When a line appears on axis minimum or maximum it loses half of its width + // Add/subtract little offset so line would appear full width + let yAxisOffset = ((yAxisMax - yAxisMin) / 100) + + yAxis.axisMinimum = yAxisMin - yAxisOffset + yAxis.axisMaximum = yAxisMax + yAxisOffset + } } } func drawChartMarker(for entry: ChartDataEntry) { - marker = ViewsVisitorsChartMarker.init(dotColor: styling.primaryLineColor, name: styling.legendTitle ?? "") - if let customMarker = self.marker as? ViewsVisitorsChartMarker { + switch statType { + case .viewsAndVisitors: + marker = ViewsVisitorsChartMarker(dotColor: styling.primaryLineColor, name: styling.legendTitle ?? "") + case .subscribers: + let date = xAxisDates[Int(entry.x)] + marker = SubscribersChartMarker(dotColor: styling.primaryLineColor, name: styling.legendTitle ?? "", date: date) + } + + if let customMarker = self.marker as? StatsChartMarker { customMarker.chartView = self } } @@ -313,7 +348,7 @@ extension StatsLineChartView: Accessible { } } -private class DateValueFormatter: NSObject, AxisValueFormatter { +class DateValueFormatter: NSObject, AxisValueFormatter { var dateFormatter: DateFormatter var xAxisDates: [Date] = [] diff --git a/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift b/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift index 7a7d5a4f811a..8fe7c3eabe98 100644 --- a/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift +++ b/WordPress/Classes/ViewRelated/Stats/Extensions/WPStyleGuide+Stats.swift @@ -72,6 +72,8 @@ extension WPStyleGuide { static func configureLabelAsSubtitle(_ label: UILabel) { label.textColor = .DS.Foreground.secondary label.font = .DS.font(.footnote) + label.adjustsFontSizeToFitWidth = true + label.maximumContentSizeCategory = .accessibilityLarge } static func configureLabelAsLink(_ label: UILabel) { diff --git a/WordPress/Classes/ViewRelated/Stats/Helpers/StatSection.swift b/WordPress/Classes/ViewRelated/Stats/Helpers/StatSection.swift index a843e8f2e46b..3fa927ee6379 100644 --- a/WordPress/Classes/ViewRelated/Stats/Helpers/StatSection.swift +++ b/WordPress/Classes/ViewRelated/Stats/Helpers/StatSection.swift @@ -439,7 +439,7 @@ } struct SubscribersHeaders { - static let chart = NSLocalizedString("stats.subscribers.chart.title", value: "Subscribers", comment: "Stats 'Subscribers' card header, contains chart") + static let chart = NSLocalizedString("stats.subscribers.growthChart.title", value: "Subscriber Growth", comment: "Stats 'Subscriber Growth' card header, contains a chart showing the progression in the number of subscribers") static let emailsSummaryStats = NSLocalizedString("stats.subscribers.emailsSummaryCard.title", value: "Emails", comment: "Stats 'Emails' card header") static let subscribersList = NSLocalizedString("stats.subscribers.subscribersListCard.title", value: "Subscribers", comment: "Stats 'Subscribers' card header") } diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/StatsBaseCell.swift b/WordPress/Classes/ViewRelated/Stats/Insights/StatsBaseCell.swift index e259a50ac9bc..ea20ae90c7ad 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/StatsBaseCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/StatsBaseCell.swift @@ -7,6 +7,8 @@ class StatsBaseCell: UITableViewCell { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = UIFont.preferredFont(forTextStyle: .headline) + label.maximumContentSizeCategory = .extraExtraExtraLarge + label.adjustsFontForContentSizeCategory = true label.adjustsFontSizeToFitWidth = true label.numberOfLines = 0 return label diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/StatsTotalInsightsCell.swift b/WordPress/Classes/ViewRelated/Stats/Insights/StatsTotalInsightsCell.swift index 685f091025d2..e140a38b325a 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/StatsTotalInsightsCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/StatsTotalInsightsCell.swift @@ -215,6 +215,7 @@ class StatsTotalInsightsCell: StatsBaseCell { countLabel.adjustsFontSizeToFitWidth = true countLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) countLabel.setContentHuggingPriority(.required, for: .vertical) + countLabel.maximumContentSizeCategory = .accessibilityLarge comparisonLabel.font = .preferredFont(forTextStyle: .subheadline) comparisonLabel.textColor = .textSubtle diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsChartMarker.swift b/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsChartMarker.swift index 54f265c35786..dfc6e6430a2a 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsChartMarker.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsChartMarker.swift @@ -2,250 +2,12 @@ import Foundation import DGCharts import UIKit -final class ViewsVisitorsChartMarker: MarkerView { - var dotColor: UIColor - var name: String - var minimumSize = CGSize() - - private var tooltipLabel: NSMutableAttributedString? - private var labelSize: CGSize = CGSize() - private var size: CGSize = CGSize() - private var paragraphStyle: NSMutableParagraphStyle = { - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center - paragraphStyle.lineSpacing = 4.0 - return paragraphStyle - }() - - public init(dotColor: UIColor, name: String) { - self.dotColor = dotColor - self.name = name - - super.init(frame: CGRect.zero) - } - - public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func drawRect(context: CGContext, point: CGPoint) -> CGRect { - let chart = super.chartView - let width = size.width - - var rect = CGRect(origin: point, size: size) - - if point.y - size.height < 0 { - if point.x - size.width / 2.0 < 0 { - drawTopLeftRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) - } else if let chartWidth = chart?.bounds.width, point.x + width - size.width / 2.0 > chartWidth { - rect.origin.x -= size.width - drawTopRightRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) - } else { - rect.origin.x -= size.width / 2.0 - drawTopCenterRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) - } - - rect.origin.y += Constants.topInsets.top - rect.size.height -= Constants.topInsets.top + Constants.topInsets.bottom - } else { - rect.origin.y -= size.height - - if point.x - size.width / 2.0 < 0 { - drawLeftRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) - } else if let chartWidth = chart?.bounds.width, point.x + width - size.width / 2.0 > chartWidth { - rect.origin.x -= size.width - drawRightRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) - } else { - rect.origin.x -= size.width / 2.0 - drawCenterRect(context: context, x: rect.origin.x, y: rect.origin.y, height: rect.height, width: rect.width) - } - - rect.origin.y += Constants.insets.top - rect.size.height -= Constants.insets.top + Constants.insets.bottom - } - - return rect - } - - func drawDot(context: CGContext, xPosition: CGFloat, yPosition: CGFloat) { - context.setLineWidth(Constants.dotBorderWidth) - context.setStrokeColor(Constants.dotBorderColor) - context.setFillColor(dotColor.cgColor) - context.setShadow(offset: CGSize.zero, blur: Constants.shadowBlur, color: Constants.shadowColor) - - let square = CGRect(x: xPosition, y: yPosition, width: Constants.dotRadius * 2, height: Constants.dotRadius * 2) - context.addEllipse(in: square) - context.drawPath(using: .fillStroke) - } - - func drawCenterRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { - let arrowHeight = Constants.arrowSize.height - let arrowWidth = Constants.arrowSize.width - - drawDot(context: context, xPosition: x + width / 2.0 - Constants.dotRadius, yPosition: y + height - Constants.dotRadius) - - // Draw tooltip - context.setFillColor(Constants.tooltipColor.cgColor) - context.beginPath() - context.move(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y - Constants.dotRadius)) - // Top right corner - context.addQuadCurve(to: CGPoint(x: x + width, y: y + Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x + width, y: y - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius)) - // Bottom right corner - context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius), control: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + (width + arrowWidth) / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width / 2.0, y: y + height - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + (width - arrowWidth) / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius)) - // Bottom left corner - context.addQuadCurve(to: CGPoint(x: x, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x, y: y + Constants.cornerRadius - Constants.dotRadius)) - // Top left corner - context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius), control: CGPoint(x: x, y: y - Constants.dotRadius)) - context.fillPath() - } - - func drawLeftRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { - let arrowHeight = Constants.arrowSize.height - let arrowWidth = Constants.arrowSize.width - - drawDot(context: context, xPosition: x - Constants.dotRadius, yPosition: y + height - Constants.dotRadius) - - // Draw tooltip - context.setFillColor(Constants.tooltipColor.cgColor) - context.beginPath() - context.move(to: CGPoint(x: x, y: y - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y - Constants.dotRadius)) - // Top right corner - context.addQuadCurve(to: CGPoint(x: x + width, y: y + Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x + width, y: y - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius)) - // Bottom right corner - context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius), control: CGPoint(x: x + width, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + arrowWidth / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x, y: y + height - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x, y: y + Constants.cornerRadius - Constants.dotRadius)) - // Top left corner - context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius), control: CGPoint(x: x, y: y - Constants.dotRadius)) - context.fillPath() - } - - func drawRightRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { - let arrowHeight = Constants.arrowSize.height - let arrowWidth = Constants.arrowSize.width - - drawDot(context: context, xPosition: x + width - Constants.dotRadius, yPosition: y + height - Constants.dotRadius) - - // Draw tooltip - context.setFillColor(Constants.tooltipColor.cgColor) - context.beginPath() - context.move(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y - Constants.dotRadius)) - // Top right corner - context.addQuadCurve(to: CGPoint(x: x + width, y: y + Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x + width, y: y - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width, y: y + height - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width - arrowWidth / 2.0, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height - arrowHeight - Constants.dotRadius)) - // Bottom left corner - context.addQuadCurve(to: CGPoint(x: x, y: y + height - arrowHeight - Constants.cornerRadius - Constants.dotRadius), control: CGPoint(x: x, y: y + height - arrowHeight - Constants.dotRadius)) - context.addLine(to: CGPoint(x: x, y: y + Constants.cornerRadius - Constants.dotRadius)) - // Top left corner - context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y - Constants.dotRadius), control: CGPoint(x: x, y: y - Constants.dotRadius)) - context.fillPath() - } - - func drawTopCenterRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { - let arrowHeight = Constants.arrowSize.height - let arrowWidth = Constants.arrowSize.width - - drawDot(context: context, xPosition: x + width / 2.0 - Constants.dotRadius, yPosition: y - Constants.dotRadius) - // Draw tooltip - context.setFillColor(Constants.tooltipColor.cgColor) - context.beginPath() - context.move(to: CGPoint(x: x + width / 2.0, y: y + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + (width + arrowWidth) / 2.0, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius)) - // Top right corner - context.addQuadCurve(to: CGPoint(x: x + width, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x + width, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width, y: y + height - Constants.cornerRadius + Constants.dotRadius)) - // Bottom right corner - context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height + Constants.dotRadius), control: CGPoint(x: x + width, y: y + height + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height + Constants.dotRadius)) - // Bottom left corner - context.addQuadCurve(to: CGPoint(x: x, y: y + height - Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x, y: y + height + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius)) - // Top left corner - context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius), control: CGPoint(x: x, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + (width - arrowWidth) / 2.0, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width / 2.0, y: y + Constants.dotRadius)) - context.fillPath() - } - - func drawTopLeftRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { - let arrowHeight = Constants.arrowSize.height - let arrowWidth = Constants.arrowSize.width - - drawDot(context: context, xPosition: x - Constants.dotRadius, yPosition: y - Constants.dotRadius) - - // Draw tooltip - context.setFillColor(Constants.tooltipColor.cgColor) - context.beginPath() - context.move(to: CGPoint(x: x, y: y + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + arrowWidth / 2.0, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius)) - // Top right corner - context.addQuadCurve(to: CGPoint(x: x + width, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x + width, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width, y: y - Constants.cornerRadius + height + Constants.dotRadius)) - // Bottom right corner - context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height + Constants.dotRadius), control: CGPoint(x: x + width, y: y + height + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height + Constants.dotRadius)) - // Bottom left corner - context.addQuadCurve(to: CGPoint(x: x, y: y + height - Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x, y: y + height + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x, y: y + Constants.dotRadius)) - context.fillPath() - } - - func drawTopRightRect(context: CGContext, x: CGFloat, y: CGFloat, height: CGFloat, width: CGFloat) { - let arrowHeight = Constants.arrowSize.height - - drawDot(context: context, xPosition: x + width - Constants.dotRadius, yPosition: y - Constants.dotRadius) - - // Draw tooltip - context.setFillColor(Constants.tooltipColor.cgColor) - context.beginPath() - context.move(to: CGPoint(x: x + width, y: y + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width, y: y + height + Constants.dotRadius - Constants.cornerRadius)) - // Bottom right corner - context.addQuadCurve(to: CGPoint(x: x + width - Constants.cornerRadius, y: y + height + Constants.dotRadius), control: CGPoint(x: x + width, y: y + height + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + Constants.cornerRadius, y: y + height + Constants.dotRadius)) - // Bottom left corner - context.addQuadCurve(to: CGPoint(x: x, y: y + height - Constants.cornerRadius + Constants.dotRadius), control: CGPoint(x: x, y: y + height + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x, y: y + arrowHeight + Constants.cornerRadius + Constants.dotRadius)) - // Top left corner - context.addQuadCurve(to: CGPoint(x: x + Constants.cornerRadius, y: y + arrowHeight + Constants.dotRadius), control: CGPoint(x: x, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width - arrowHeight / 2.0, y: y + arrowHeight + Constants.dotRadius)) - context.addLine(to: CGPoint(x: x + width, y: y + Constants.dotRadius)) - context.fillPath() - } - - override func draw(context: CGContext, point: CGPoint) { - guard let tooltipLabel = tooltipLabel else { - return - } - - context.saveGState() - let rect = drawRect(context: context, point: point) - UIGraphicsPushContext(context) - tooltipLabel.draw(in: rect) - UIGraphicsPopContext() - context.restoreGState() - } - - override func refreshContent(entry: ChartDataEntry, highlight: Highlight) { +class ViewsVisitorsChartMarker: StatsChartMarker { + override func text(for entry: ChartDataEntry) -> NSMutableAttributedString? { let yValue = Int(entry.y).description guard let data = chartView?.data, data.dataSetCount > 1, let lineChartDataSetPrevWeek = data.dataSet(at: 1) as? LineChartDataSet else { - return + return nil } let entryPrevWeek = lineChartDataSetPrevWeek.entries[Int(entry.x)] @@ -269,36 +31,7 @@ final class ViewsVisitorsChartMarker: MarkerView { let bottomRowStr = NSAttributedString(string: "\(yValue) \(name)", attributes: bottomRowAttributes) topRowStr.append(bottomRowStr) - tooltipLabel = topRowStr - - labelSize = topRowStr.size() - size.width = labelSize.width + Constants.insets.left + Constants.insets.right - size.height = labelSize.height + Constants.insets.top + Constants.insets.bottom - size.width = max(minimumSize.width, size.width) - size.height = max(minimumSize.height, size.height) - } -} - -private extension ViewsVisitorsChartMarker { - enum Constants { - static var tooltipColor: UIColor { - return UIColor(color: .muriel(name: .blue, .shade100)) - } - - static var shadowColor: CGColor { - return UIColor(red: 50 / 255, green: 50 / 255, blue: 71 / 255, alpha: 0.06).cgColor - } - - static var dotBorderColor: CGColor { - return UIColor.white.cgColor - } - static let arrowSize = CGSize(width: 12, height: 8) - static let insets = UIEdgeInsets(top: 2.0, left: 16.0, bottom: 26.0, right: 16.0) - static let topInsets = UIEdgeInsets(top: 26.0, left: 8.0, bottom: 2.0, right: 8.0) - static let dotRadius = 8.0 - static let dotBorderWidth = 4.0 - static let cornerRadius = 10.0 - static let shadowBlur = 5.0 + return topRowStr } } diff --git a/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsLineChartCell.swift b/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsLineChartCell.swift index d37be6f6ce6f..189872222dde 100644 --- a/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsLineChartCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Insights/ViewsVisitors/ViewsVisitorsLineChartCell.swift @@ -267,15 +267,15 @@ private extension ViewsVisitorsLineChartCell { return } - let configuration = StatsLineChartConfiguration(data: chartData[selectedSegmentIndex], - styling: chartStyling[selectedSegmentIndex], - analyticsGranularity: period?.analyticsGranularityLine, - indexToHighlight: 0, - xAxisDates: xAxisDates) - - let statsInsightsFilterDimension: StatsInsightsFilterDimension = selectedSegmentIndex == 0 ? .views : .visitors - - let chartView = StatsLineChartView(configuration: configuration, delegate: statsLineChartViewDelegate, statsInsightsFilterDimension: statsInsightsFilterDimension) + let filter: StatsInsightsFilterDimension = selectedSegmentIndex == 0 ? .views : .visitors + let configuration = StatsLineChartConfiguration(type: .viewsAndVisitors(filter), + data: chartData[selectedSegmentIndex], + styling: chartStyling[selectedSegmentIndex], + analyticsGranularity: period?.analyticsGranularityLine, + indexToHighlight: 0, + xAxisDates: xAxisDates) + + let chartView = StatsLineChartView(configuration: configuration, delegate: statsLineChartViewDelegate) resetChartContainerView() chartContainerView.addSubview(chartView) diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostCells.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostCells.swift index ae5354573297..61764277b2e4 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostCells.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostCells.swift @@ -6,6 +6,20 @@ class StatsGhostBaseCell: StatsBaseCell { override func awakeFromNib() { super.awakeFromNib() + commonInit() + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + private func commonInit() { headingLabel.isGhostableDisabled = true Style.configureCell(self) } @@ -24,7 +38,6 @@ class StatsGhostBaseCell: StatsBaseCell { class StatsGhostGrowAudienceCell: StatsGhostBaseCell, NibLoadable { } class StatsGhostTwoColumnCell: StatsGhostBaseCell, NibLoadable { } -class StatsGhostTopCell: StatsGhostBaseCell, NibLoadable { } class StatsGhostTopHeaderCell: StatsGhostBaseCell, NibLoadable { override func awakeFromNib() { super.awakeFromNib() diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostLineChartCell.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostLineChartCell.swift new file mode 100644 index 000000000000..5aaef8923418 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostLineChartCell.swift @@ -0,0 +1,86 @@ +import UIKit +import WordPressShared +import DesignSystem + +final class StatsGhostLineChartCell: StatsGhostBaseCell { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + let lineChart = StatsGhostLineChartView() + lineChart.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(lineChart) + topConstraint = lineChart.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0) + topConstraint?.isActive = true + NSLayoutConstraint.activate([ + lineChart.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .DS.Padding.double), + contentView.trailingAnchor.constraint(equalTo: lineChart.trailingAnchor, constant: .DS.Padding.double), + contentView.bottomAnchor.constraint(equalTo: lineChart.bottomAnchor, constant: .DS.Padding.single), + lineChart.heightAnchor.constraint(equalToConstant: 190), + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +final class StatsGhostLineChartView: UIView { + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + private func commonInit() { + backgroundColor = .clear + createMask() + } + + private func createMask() { + let maskLayer = CAShapeLayer() + maskLayer.frame = bounds + + let path = UIBezierPath() + path.move(to: CGPoint(x: 0, y: bounds.maxY)) + + let wavePoints = [ + CGPoint(x: bounds.width * 0.1, y: bounds.maxY * 0.8), + CGPoint(x: bounds.width * 0.3, y: bounds.maxY * 0.6), + CGPoint(x: bounds.width * 0.5, y: bounds.maxY * 0.4), + CGPoint(x: bounds.width * 0.7, y: bounds.maxY * 0.2), + CGPoint(x: bounds.width * 0.9, y: bounds.maxY * 0.5), + CGPoint(x: bounds.width, y: 0) + ] + + for (index, point) in wavePoints.enumerated() { + if index == 0 { + path.addLine(to: point) + } else { + let previousPoint = wavePoints[index - 1] + let midPointX = (previousPoint.x + point.x) / 2 + path.addCurve(to: point, controlPoint1: CGPoint(x: midPointX, y: previousPoint.y), controlPoint2: CGPoint(x: midPointX, y: point.y)) + } + } + + path.addLine(to: CGPoint(x: bounds.width, y: 0)) + path.addLine(to: CGPoint(x: bounds.width, y: bounds.maxY)) + path.addLine(to: CGPoint(x: 0, y: bounds.maxY)) + path.close() + + maskLayer.path = path.cgPath + maskLayer.fillColor = UIColor.white.cgColor + maskLayer.fillRule = .evenOdd + layer.mask = maskLayer + backgroundColor = .clear + } + + override func layoutSubviews() { + super.layoutSubviews() + layer.sublayers?.forEach { $0.frame = bounds } + createMask() + } +} diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostSingleValueCell.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostSingleValueCell.swift new file mode 100644 index 000000000000..c11bf06e95c4 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostSingleValueCell.swift @@ -0,0 +1,25 @@ +import UIKit +import WordPressShared +import DesignSystem + +final class StatsGhostSingleValueCell: StatsGhostBaseCell { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + let ghostView = UIView() + ghostView.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(ghostView) + topConstraint = ghostView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .DS.Padding.single) + topConstraint?.isActive = true + NSLayoutConstraint.activate([ + ghostView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .DS.Padding.double), + ghostView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -.DS.Padding.double), + ghostView.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.35), + ghostView.heightAnchor.constraint(equalToConstant: 36) + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTableViewRows.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTableViewRows.swift index 1ce5dcc8788d..0050af891b61 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTableViewRows.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTableViewRows.swift @@ -42,11 +42,12 @@ struct StatsGhostTwoColumnImmutableRow: StatsRowGhostable { struct StatsGhostTopImmutableRow: StatsRowGhostable { static let cell: ImmuTableCell = { - return ImmuTableCell.nib(StatsGhostTopCell.defaultNib, StatsGhostTopCell.self) + return ImmuTableCell.class(StatsGhostTopCell.self) }() var hideTopBorder = false var hideBottomBorder = false + var numberOfColumns: Int = 2 var statSection: StatSection? = nil // MARK: - Hashable @@ -66,6 +67,7 @@ struct StatsGhostTopImmutableRow: StatsRowGhostable { detailCell.topBorder?.isHidden = hideTopBorder detailCell.bottomBorder?.isHidden = hideBottomBorder detailCell.statSection = statSection + detailCell.numberOfColumns = numberOfColumns } } } @@ -132,3 +134,19 @@ struct StatsGhostTitleRow: StatsRowGhostable { enum GhostCellStyle { static let muriel = GhostStyle(beatStartColor: .placeholderElement, beatEndColor: .placeholderElementFaded) } + +struct StatsGhostSingleValueRow: StatsRowGhostable { + let statSection: StatSection? + + static let cell: ImmuTableCell = { + return ImmuTableCell.class(StatsGhostSingleValueCell.self) + }() +} + +struct StatsGhostLineChartRow: StatsRowGhostable { + let statSection: StatSection? + + static let cell: ImmuTableCell = { + return ImmuTableCell.class(StatsGhostLineChartCell.self) + }() +} diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTopCell.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTopCell.swift new file mode 100644 index 000000000000..bcbbbeb53f9d --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTopCell.swift @@ -0,0 +1,202 @@ +import UIKit +import DesignSystem +import WordPressUI + +final class StatsGhostTopCell: StatsGhostBaseCell, NibLoadable { + private let titleHeader: UIView = { + let header = UIView() + header.translatesAutoresizingMaskIntoConstraints = false + return header + }() + + private let valueHeaders: UIStackView = { + let headers = UIStackView() + headers.translatesAutoresizingMaskIntoConstraints = false + headers.spacing = .DS.Padding.double + headers.distribution = .fill + headers.alignment = .fill + headers.axis = .horizontal + return headers + }() + + private let contentRows: StatsGhostTopCellRow = { + let contentRows = StatsGhostTopCellRow() + contentRows.translatesAutoresizingMaskIntoConstraints = false + return contentRows + }() + + var numberOfColumns: Int = 2 { + didSet { + configureCell(with: numberOfColumns) + } + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + contentView.addSubviews([titleHeader, valueHeaders, contentRows]) + + topConstraint = valueHeaders.topAnchor.constraint(equalTo: contentView.topAnchor, constant: .DS.Padding.single) + topConstraint?.isActive = true + NSLayoutConstraint.activate([ + titleHeader.topAnchor.constraint(equalTo: valueHeaders.topAnchor), + titleHeader.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .DS.Padding.double), + titleHeader.widthAnchor.constraint(equalToConstant: 50), + titleHeader.heightAnchor.constraint(equalToConstant: .DS.Padding.double), + + valueHeaders.heightAnchor.constraint(equalToConstant: .DS.Padding.double), + valueHeaders.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -.DS.Padding.double), + + contentRows.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0), + contentRows.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0), + contentRows.topAnchor.constraint(equalTo: valueHeaders.bottomAnchor, constant: .DS.Padding.half), + contentRows.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -.DS.Padding.single) + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func configureCell(with count: Int) { + updateHeaders(count: count) + contentRows.updateColumns(count: count) + } + + private func updateHeaders(count: Int) { + valueHeaders.removeAllSubviews() + let headers = Array(repeating: UIView(), count: count-1) + headers.forEach { header in + configureHeader(header) + valueHeaders.addArrangedSubview(header) + } + } + + private func configureHeader(_ header: UIView) { + header.startGhostAnimation() + header.widthAnchor.constraint(equalToConstant: Constants.columnWidth).isActive = true + } +} + +final class StatsGhostTopCellRow: UIView { + private let avatarView = UIView() + private let columnsStackView = createStackView() + private let mainColumn = StatsGhostTopCellColumn() + + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupViews() + } + + private static func createStackView() -> UIStackView { + let stackView = UIStackView() + stackView.alignment = .fill + stackView.distribution = .fillEqually + stackView.axis = .horizontal + stackView.spacing = .DS.Padding.double + return stackView + } + + private func setupViews() { + [columnsStackView, mainColumn, avatarView].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + addSubview($0) + } + setupConstraints() + } + + private func setupConstraints() { + NSLayoutConstraint.activate([ + avatarView.heightAnchor.constraint(equalToConstant: .DS.Padding.medium), + avatarView.widthAnchor.constraint(equalToConstant: .DS.Padding.medium), + avatarView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: .DS.Padding.double), + avatarView.topAnchor.constraint(equalTo: topAnchor, constant: .DS.Padding.double), + avatarView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -.DS.Padding.double), + mainColumn.leadingAnchor.constraint(equalTo: avatarView.trailingAnchor, constant: .DS.Padding.double), + mainColumn.centerYAnchor.constraint(equalTo: centerYAnchor), + columnsStackView.leadingAnchor.constraint(equalTo: mainColumn.trailingAnchor, constant: .DS.Padding.double), + columnsStackView.centerYAnchor.constraint(equalTo: centerYAnchor), + columnsStackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -.DS.Padding.double) + ]) + } + + func updateColumns(count: Int) { + columnsStackView.removeAllSubviews() + mainColumn.isHidden = count <= 1 + + let columns = Array(repeating: StatsGhostTopCellColumn(width: StatsGhostTopCell.Constants.columnWidth), count: count-1) + columns.forEach(columnsStackView.addArrangedSubview) + } +} + +private class StatsGhostTopCellColumn: UIView { + private let topView = UIView() + private let bottomView = UIView() + private let width: CGFloat? + + init(width: CGFloat? = nil) { + self.width = width + super.init(frame: .zero) + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + let stackView = createStackView() + addSubview(stackView) + pinSubviewToAllEdges(stackView) + setupConstraints() + } + + private func createStackView() -> UIStackView { + let stackView = UIStackView(arrangedSubviews: [topView, bottomView]) + stackView.axis = .vertical + stackView.spacing = .DS.Padding.half + stackView.alignment = .fill + stackView.distribution = .equalSpacing + stackView.translatesAutoresizingMaskIntoConstraints = false + return stackView + } + + private func setupConstraints() { + var constraints = [ + topView.heightAnchor.constraint(equalToConstant: .DS.Padding.medium), + bottomView.heightAnchor.constraint(equalToConstant: .DS.Padding.double) + ] + + if let width = width { + constraints += [ + topView.widthAnchor.constraint(equalToConstant: width), + bottomView.widthAnchor.constraint(equalToConstant: width) + ] + } + + NSLayoutConstraint.activate(constraints) + startAnimations() + } + + private func startAnimations() { + topView.startGhostAnimation(style: GhostCellStyle.muriel) + bottomView.startGhostAnimation(style: GhostCellStyle.muriel) + } + + override func tintColorDidChange() { + super.tintColorDidChange() + topView.restartGhostAnimation(style: GhostCellStyle.muriel) + bottomView.restartGhostAnimation(style: GhostCellStyle.muriel) + } +} + +fileprivate extension StatsGhostTopCell { + enum Constants { + static let columnWidth: CGFloat = 60 + } +} diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTopCell.xib b/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTopCell.xib deleted file mode 100644 index 1ee2c60cf05c..000000000000 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/GhostViews/StatsGhostTopCell.xib +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/GrowAudienceCell.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/GrowAudienceCell.swift index 3c06549d4144..414135d33cfd 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/GrowAudienceCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/GrowAudienceCell.swift @@ -239,14 +239,14 @@ class GrowAudienceCell: UITableViewCell, NibLoadable { enum ReaderDiscover { static let detailsTitle = NSLocalizedString( "growAudienceCell.readerDiscover.details", - value: "Connect with other bloggers by following, liking and commenting on their posts.", + value: "Connect with other bloggers by subscribing, liking and commenting on their posts.", comment: "A detailed message to users about growing the audience for their site through reader discover." ) static let actionButtonTitle = NSLocalizedString( "growAudienceCell.readerDiscover.actionButton", - value: "Discover blogs to follow", - comment: "Title for button that will open up the follow topics screen." + value: "Discover blogs to subscribe", + comment: "Title for button that will open up the subscribe topics screen." ) static let completedTipTitle = NSLocalizedString( diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift index 693d50a28b6c..67f02b1c2ad1 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/StatsTotalRow.swift @@ -269,6 +269,11 @@ private extension StatsTotalRow { Style.configureViewAsSeparator(topExpandedSeparatorLine) Style.configureViewAsDataBar(dataBar) + [itemLabel, itemDetailLabel, dataLabel, secondDataLabel].forEach { + $0?.adjustsFontForContentSizeCategory = true + $0?.maximumContentSizeCategory = .extraExtraLarge + } + [itemLabel, itemDetailLabel].forEach { $0?.numberOfLines = rowData.multiline ? 0 : 1 } diff --git a/WordPress/Classes/ViewRelated/Stats/Shared Views/ViewMoreRow.swift b/WordPress/Classes/ViewRelated/Stats/Shared Views/ViewMoreRow.swift index bf3e253db375..957ce02956de 100644 --- a/WordPress/Classes/ViewRelated/Stats/Shared Views/ViewMoreRow.swift +++ b/WordPress/Classes/ViewRelated/Stats/Shared Views/ViewMoreRow.swift @@ -40,6 +40,7 @@ private extension ViewMoreRow { backgroundColor = .listForeground viewMoreLabel.text = NSLocalizedString("View more", comment: "Label for viewing more stats.") viewMoreLabel.textColor = WPStyleGuide.Stats.actionTextColor + viewMoreLabel.maximumContentSizeCategory = .extraExtraExtraLarge if statSection == .insightsFollowersWordPress || statSection == .insightsFollowersEmail || statSection == .subscribersList || diff --git a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift index 047c95a5668b..98c5f98fe51e 100644 --- a/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/SiteStatsDashboardViewController.swift @@ -364,7 +364,12 @@ struct SiteStatsDashboardPreferences { guard let siteID = SiteStatsInformation.sharedInstance.siteID?.intValue else { return nil } let key = Self.lastSelectedStatsTabTypeKey(forSiteID: siteID) - return StatsTabType(rawValue: UserPersistentStoreFactory.instance().integer(forKey: key)) + + guard let tabRawValue = UserPersistentStoreFactory.instance().object(forKey: key) as? Int else { + return nil + } + + return StatsTabType(rawValue: tabRawValue) } static func getSelectedPeriodUnit() -> StatsPeriodUnit? { diff --git a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersChartCell.swift b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersChartCell.swift index 728d1cf49cd2..d411b26322ca 100644 --- a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersChartCell.swift +++ b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersChartCell.swift @@ -30,7 +30,8 @@ class StatsSubscribersChartCell: StatsBaseCell, NibLoadable { private extension StatsSubscribersChartCell { func configureChartView() { - let configuration = StatsLineChartConfiguration(data: chartData, + let configuration = StatsLineChartConfiguration(type: .subscribers, + data: chartData, styling: chartStyling, analyticsGranularity: .days, indexToHighlight: 0, diff --git a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersLineChart.swift b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersLineChart.swift index 5a0eb66dcdda..159c7f50f15c 100644 --- a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersLineChart.swift +++ b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersLineChart.swift @@ -30,7 +30,7 @@ private struct SubscribersLineChartStyling: LineChartStyling { let primaryHighlightColor: UIColor? = UIColor(red: 209.0/255.0, green: 209.0/255.0, blue: 214.0/255.0, alpha: 1.0) let labelColor: UIColor = UIColor(light: .secondaryLabel, dark: .tertiaryLabel) let legendColor: UIColor? = nil - let legendTitle: String? = nil + let legendTitle: String? = NSLocalizedString("stats.subscribers.chart.legend", value: "Subscribers", comment: "Title for the legend of the Stats Subscribers line chart.") let lineColor: UIColor = .neutral(.shade5) let yAxisValueFormatter: AxisValueFormatter = VerticalAxisFormatter() } diff --git a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewController.swift b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewController.swift index 2df49148e7dd..ad974f3b51af 100644 --- a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewController.swift +++ b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewController.swift @@ -71,8 +71,10 @@ final class StatsSubscribersViewController: SiteStatsBaseTableViewController { return [ SubscriberChartRow.self, TopTotalsPeriodStatsRow.self, - StatsGhostTopImmutableRow.self, TotalInsightStatsRow.self, + StatsGhostTopImmutableRow.self, + StatsGhostLineChartRow.self, + StatsGhostSingleValueRow.self, StatsErrorRow.self ] } @@ -85,13 +87,14 @@ extension StatsSubscribersViewController: SiteStatsPeriodDelegate { guard let blog = RootViewCoordinator.sharedPresenter.mySitesCoordinator.currentBlog, let peopleViewController = PeopleViewController.controllerWithBlog(blog, selectedFilter: .followers) else { return } navigationController?.pushViewController(peopleViewController, animated: true) + WPAnalytics.track(.statsSubscribersViewMoreTapped) case .subscribersEmailsSummary: let detailTableViewController = SiteStatsDetailTableViewController.loadFromStoryboard() detailTableViewController.configure(statSection: statSection) navigationController?.pushViewController(detailTableViewController, animated: true) + WPAnalytics.track(.statsEmailsViewMoreTapped) default: - // TODO - DDLogInfo("\(statSection) selected") + break } } } diff --git a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewModel.swift b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewModel.swift index 0fabf6e09ca3..5e6405e72558 100644 --- a/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewModel.swift +++ b/WordPress/Classes/ViewRelated/Stats/Subscribers/StatsSubscribersViewModel.swift @@ -50,8 +50,8 @@ private extension StatsSubscribersViewModel { tableViewSnapshot.send(snapshot) } - func loadingRows(_ section: StatSection) -> [any StatsHashableImmuTableRow] { - return [StatsGhostTopImmutableRow(statSection: section)] + func loadingRows(_ section: StatSection, numberOfColumns: Int) -> [any StatsHashableImmuTableRow] { + return [StatsGhostTopImmutableRow(numberOfColumns: numberOfColumns, statSection: section)] } func errorRows(_ section: StatSection) -> [any StatsHashableImmuTableRow] { @@ -65,7 +65,7 @@ private extension StatsSubscribersViewModel { func subscribersTotalsRows() -> [any StatsHashableImmuTableRow] { switch store.subscribersList.value { case .loading, .idle: - return loadingRows(.subscribersTotal) + return [StatsGhostSingleValueRow(statSection: .subscribersTotal)] case .success(let subscribersData): return [ TotalInsightStatsRow( @@ -85,7 +85,7 @@ private extension StatsSubscribersViewModel { func chartRows() -> [any StatsHashableImmuTableRow] { switch store.chartSummary.value { case .loading, .idle: - return loadingRows(.subscribersChart) + return [StatsGhostLineChartRow(statSection: .subscribersChart)] case .success(let chartSummary): let xAxisDates = chartSummary.history.map { $0.date } let viewsChart = StatsSubscribersLineChart(counts: chartSummary.history.map { $0.count }) @@ -110,7 +110,7 @@ private extension StatsSubscribersViewModel { func emailsSummaryRows() -> [any StatsHashableImmuTableRow] { switch store.emailsSummary.value { case .loading, .idle: - return loadingRows(.subscribersEmailsSummary) + return loadingRows(.subscribersEmailsSummary, numberOfColumns: 3) case .success(let emailsSummary): return [ TopTotalsPeriodStatsRow( @@ -146,7 +146,7 @@ private extension StatsSubscribersViewModel { func subscribersListRows() -> [any StatsHashableImmuTableRow] { switch store.subscribersList.value { case .loading, .idle: - return loadingRows(.subscribersList) + return loadingRows(.subscribersList, numberOfColumns: 2) case .success(let subscribersData): return [ TopTotalsPeriodStatsRow( diff --git a/WordPress/Classes/ViewRelated/Stats/Subscribers/SubscribersChartMarker.swift b/WordPress/Classes/ViewRelated/Stats/Subscribers/SubscribersChartMarker.swift new file mode 100644 index 000000000000..357f9062a610 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Stats/Subscribers/SubscribersChartMarker.swift @@ -0,0 +1,39 @@ +import DGCharts + +class SubscribersChartMarker: StatsChartMarker { + let date: Date + + init(dotColor: UIColor, name: String, date: Date) { + self.date = date + super.init(dotColor: dotColor, name: name) + } + + public required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func text(for entry: ChartDataEntry) -> NSMutableAttributedString? { + let tightParagraphStyle = NSMutableParagraphStyle() + tightParagraphStyle.lineSpacing = 0 + tightParagraphStyle.lineHeightMultiple = 0.8 + tightParagraphStyle.alignment = .center + + let subscriberCountRowAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.preferredFont(forTextStyle: .headline), + .paragraphStyle: tightParagraphStyle, + .foregroundColor: UIColor.white] + + let subscriberLabelRowAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.preferredFont(forTextStyle: .footnote), + .paragraphStyle: tightParagraphStyle, + .foregroundColor: UIColor.white] + + let subscriberDateRowAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.preferredFont(forTextStyle: .footnote), + .paragraphStyle: paragraphStyle, + .foregroundColor: UIColor.white.withAlphaComponent(0.8)] + + let attrString = NSMutableAttributedString() + attrString.append(NSAttributedString(string: entry.y.abbreviatedString() + "\n", attributes: subscriberCountRowAttributes)) + attrString.append(NSAttributedString(string: "\(name)\n", attributes: subscriberLabelRowAttributes)) + attrString.append(NSAttributedString(string: DateValueFormatter().dateFormatter.string(from: date), attributes: subscriberDateRowAttributes)) + return attrString + } +} diff --git a/WordPress/Resources/ar.lproj/Localizable.strings b/WordPress/Resources/ar.lproj/Localizable.strings index 672661919755..dfd420a68af6 100644 --- a/WordPress/Resources/ar.lproj/Localizable.strings +++ b/WordPress/Resources/ar.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-24 12:15:26+0000 */ +/* Translation-Revision-Date: 2024-05-05 16:02:21+0000 */ /* Plural-Forms: nplurals=6; plural=(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n % 100 >= 3 && n % 100 <= 10) ? 3 : ((n % 100 >= 11 && n % 100 <= 99) ? 4 : 5)))); */ /* Generator: GlotPress/4.0.1 */ /* Language: ar */ @@ -8853,6 +8853,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "ملف صوت"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "إلغاء"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "لقد أجريت تغييرات غير محفوظة على هذه التدوينة من جهاز مختلف. المحرَّر: %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "الحفظ التلقائي متاح"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "عرض التغييرات"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "يتعذر تحميل الصورة. يرجى اختيار صورة مختلفة أو المحاولة مرة أخرى لاحقًا."; @@ -10305,6 +10317,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "جارٍ نقل التدوينة إلى سلة المهملات..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "تجاهل التغييرات"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "تجاهل المسودة"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "متابعة التحرير"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "حفظ المسودة"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "جار إنشاء الحفظ التلقائي..."; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "تم تحميل المراجعة"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "جار حفظ المسودة..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "بواسطة %@."; @@ -10317,6 +10350,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@، %2$@."; +/* Badge for post cells */ +"postList.badgePendingReview" = "في انتظار المراجعة"; + +/* Cancels an Action */ +"postList.cancel" = "إلغاء"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "الحذف نهائيًا"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "هل تريد بالتأكيد حذف \"%@s\" نهائيًا؟"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "يرفع التطبيق التغييرات التي سبق إجراؤها على الخادم. يرجى المحاولة مرة أخرى لاحقًا."; + +/* A generic error message title */ +"postList.genericErrorMessage" = "حدث خطأ ما"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "تغييرات من دون اتصال بالإنترنت"; @@ -10332,6 +10383,39 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "عرض"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "نقل إلى سلة المهملات"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "هل تريد بالتأكيد وضع \"%@\" في سلة المهملات؟ ستُفقد أي تغييرات لم يسبق إرسالها إلى الخادم."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "إلغاء الرفع"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "إغلاق"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "لا توجد عمليات رفع معلَّقة"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@ من %2$@"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "إعادة محاولة عمليات الرفع"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "عمليات رفع الوسائط"; + +/* A generic error message title */ +"postNotice.errorTitle" = "حدث خطأ ما"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "يرفع التطبيق التغييرات التي سبق إجراؤها على الخادم. يرجى المحاولة مرة أخرى لاحقًا."; + +/* Button OK */ +"postNotice.ok" = "موافق"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "تم رفع مسودة الصفحة"; @@ -10365,6 +10449,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "عرض"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "تم تعديل المحتوى على جهاز آخر"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "تم حذف \"%@\" بشكل دائم، ويتعذر تحديثه بعد الآن"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "من دون عنوان"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "تجاهل"; @@ -10377,6 +10470,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "خيارات الصور المتميزة"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "في انتظار المراجعة"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "لا تتوافر ميزة المشاركة التلقائية في تويتر بعد الآن"; @@ -10386,6 +10482,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "فشل تحديث إعدادات التدوينة"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "غير مرئية إلا لمسؤولي المواقع والمحررين"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "خاص"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "مرئية للجميع لكن يلزم كلمة مرور"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "محمية بكلمة مرور"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "مرئية للجميع."; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "عام"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "إلغاء"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "إمكانية الرؤية"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "كلمة المرور"; + +/* Button save */ +"postVisibilityPicker.save" = "حفظ"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "الترويج مع الإبراز"; @@ -10410,6 +10536,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "معاينة"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "نشر"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "إعادة المحاولة"; @@ -10476,6 +10605,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "وسائل التواصل الاجتماعي الخاصة بJetpack"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "فشل رفع %@ من العناصر"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "العنوان"; @@ -10485,6 +10617,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "تاريخ النشر"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "فورًا"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "نشر إلى"; @@ -10549,6 +10684,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "الوسوم"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "يتبقى %@ من العناصر"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "يتبقى %@ من العناصر"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "رفع الوسائط"; @@ -10558,6 +10699,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "عرض كل الردود"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "تاريخ النشر"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "التاريخ في منطقتك الزمنية الحالية:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "فورًا"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "إزالة تاريخ النشر"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "تحديد تاريخ النشر"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "المنطقة الزمنية"; + /* Post publish date picker */ "publishDatePicker.title" = "تاريخ النشر"; @@ -10759,6 +10918,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "إعدادات القارئ"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "إضافة مدونة"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "اكتشاف المدونات"; @@ -10783,9 +10945,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "الانتقال إلى الاشتراكات"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "إضافة مدونة"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "يتعذر حظر المدونة"; @@ -11031,6 +11190,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "إضافة وسم"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "إعجاب"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "أعجبني"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "اكتشاف مزيد من الوسوم"; @@ -11133,6 +11298,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "التدوينات ذات الصلة"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "جهاز آخر"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "الجهاز الحالي"; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "إلغاء"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "حفظ"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "نسخ الرابط"; @@ -11310,6 +11487,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "غير معروف"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "جميع المواقع"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "المواقع المثبَّتة"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "المواقع الأحدث"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "إضافة موقع"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "إلغاء"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "تم"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "تحرير"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "اختيار موقع"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "التذكيرات"; @@ -11346,6 +11547,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "لا تتوافر ميزة المشاركة التلقائية على تويتر بعد الآن بسبب التغييرات التي أجرتها تويتر في الشروط والأسعار."; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "المشتركون"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "حركة المرور"; @@ -11442,18 +11646,45 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "لا يوجد نشاط كافٍ. تحقَّق من هذا مرة أخرى لاحقًا عندما يكون لدى موقعك مزيد من الزائرين!"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "المشتركون"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "حصلت تدوينتك الأخيرة %1$@ على %2$@ من الإعجابات."; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "حصلت تدوينتك الأخيرة %1$@ على %2$@ من الإعجابات."; +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "إجمالي المشتركين"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "اليوم"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "مشترك منذ"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "الاسم"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "المشتركون"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "نقرات"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "رسائل البريد الإلكتروني"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "المشتركون"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "جدول يعرض %1$@ و%2$@ و%3$@"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "رسم بياني شريطي يوضّح التعليقات للفترة المحددة."; @@ -11466,6 +11697,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "رسم بياني شريطي يوضّح الزائرين للفترة المحددة."; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "الأيام"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "أشهر"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "الأسابيع"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "سنوات"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "تجاهُل"; @@ -11475,9 +11718,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "ابحث للعثور على صور مجانية لإضافتها إلى مكتبة الوسائط الخاصة بك!"; +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "لم يتم إدخال أي بريد إلكتروني"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "لم يتم إدخال اسم المستخدم"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "تم"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "شكرًا لملاحظاتك"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "إرسال"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "جار الإرسال"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "ملاحظات"; @@ -11649,6 +11907,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "غير مقروء"; +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "المشترك بالموقع"; + +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "مشتركي البريد الإلكتروني"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "المشتركون"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "زيارة صفحة الوثائق الخاصة بنا"; diff --git a/WordPress/Resources/de.lproj/Localizable.strings b/WordPress/Resources/de.lproj/Localizable.strings index 5afa7fa0afe2..712e26e118b8 100644 --- a/WordPress/Resources/de.lproj/Localizable.strings +++ b/WordPress/Resources/de.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-30 07:28:34+0000 */ +/* Translation-Revision-Date: 2024-05-02 17:48:29+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: de */ @@ -10924,6 +10924,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "Reader-Einstellungen"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "Einen Blog hinzufügen"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "Blogs entdecken"; @@ -10949,7 +10952,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "Gehe zu Abonnements"; /* No Tags View Button Label */ -"reader.no.tags.title" = "Einen Blog hinzufügen"; +"reader.no.tags.title" = "Ein Schlagwort hinzufügen"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Der Blog kann nicht blockiert werden"; @@ -11667,6 +11670,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Dein neuester Beitrag %1$@ hat %2$@ Like erhalten."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "WordPress.com-Abonnenten insgesamt: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "E-Mail-Abonnenten insgesamt: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Abonnenten insgesamt"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; @@ -11751,10 +11763,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Übermitteln"; -/* Notice informing user that their feedback is being submitted anonymously. - Notice informing user that their feedback is being submitted. */ +/* Notice informing user that their feedback is being submitted. */ "submit.feedback.submit.loading" = "Wird gesendet"; +/* Notice informing user that their feedback is being submitted anonymously. */ +"submit.feedback.submitAnonymously.loading" = "Wird anonym gesendet"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Feedback"; @@ -11938,8 +11952,10 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Site's Viewers Profile. Displayed when the name is empty! */ "user.details.title.viewer" = "Besucher der Website"; -/* Site Email Subscribers - Site Subscribers */ +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "E-Mail-Abonnenten"; + +/* Site Subscribers */ "users.list.title.subscribers" = "Abonnenten"; /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ diff --git a/WordPress/Resources/en-CA.lproj/Localizable.strings b/WordPress/Resources/en-CA.lproj/Localizable.strings index 6287a0d94fee..c028a12fa635 100644 --- a/WordPress/Resources/en-CA.lproj/Localizable.strings +++ b/WordPress/Resources/en-CA.lproj/Localizable.strings @@ -10681,9 +10681,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "Go to Subscriptions"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "Add a blog"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Unable to block blog"; diff --git a/WordPress/Resources/en-GB.lproj/Localizable.strings b/WordPress/Resources/en-GB.lproj/Localizable.strings index 1293f91c2998..0a6cd649fdc6 100644 --- a/WordPress/Resources/en-GB.lproj/Localizable.strings +++ b/WordPress/Resources/en-GB.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-30 08:19:40+0000 */ +/* Translation-Revision-Date: 2024-05-01 20:22:33+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: en_GB */ @@ -10924,6 +10924,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "Reader Settings"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "Add a blog"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "Discover Blogs"; @@ -10949,7 +10952,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "Go to Subscriptions"; /* No Tags View Button Label */ -"reader.no.tags.title" = "Add a blog"; +"reader.no.tags.title" = "Add a tag"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Unable to block blog"; @@ -11667,6 +11670,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Your latest post %1$@ has received %2$@ like."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Total WordPress.com subscribers: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Total email subscribers: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Total Subscribers"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; @@ -11751,10 +11763,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Submit"; -/* Notice informing user that their feedback is being submitted anonymously. - Notice informing user that their feedback is being submitted. */ +/* Notice informing user that their feedback is being submitted. */ "submit.feedback.submit.loading" = "Sending"; +/* Notice informing user that their feedback is being submitted anonymously. */ +"submit.feedback.submitAnonymously.loading" = "Sending anonymously"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Feedback"; @@ -11938,8 +11952,10 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Site's Viewers Profile. Displayed when the name is empty! */ "user.details.title.viewer" = "Site's Viewer"; -/* Site Email Subscribers - Site Subscribers */ +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "Email Subscribers"; + +/* Site Subscribers */ "users.list.title.subscribers" = "Subscribers"; /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ diff --git a/WordPress/Resources/es.lproj/Localizable.strings b/WordPress/Resources/es.lproj/Localizable.strings index 8027c364ec96..ef92676b4584 100644 --- a/WordPress/Resources/es.lproj/Localizable.strings +++ b/WordPress/Resources/es.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-30 07:03:04+0000 */ +/* Translation-Revision-Date: 2024-05-02 09:00:33+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: es */ @@ -10924,6 +10924,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "Configuración del lector"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "Añadir un blog"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "Descubrir blogs"; @@ -10949,7 +10952,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "Ir a suscripciones"; /* No Tags View Button Label */ -"reader.no.tags.title" = "Añadir un blog"; +"reader.no.tags.title" = "Añadir una etiqueta"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "No se puede bloquear el blog"; @@ -11667,6 +11670,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Tu última entrada %1$@ ha recibido %2$@ me gusta."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Suscriptores totales de WordPress.com: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Suscriptores totales por correo electrónico: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Suscriptores totales"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; @@ -11751,10 +11763,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Enviar"; -/* Notice informing user that their feedback is being submitted anonymously. - Notice informing user that their feedback is being submitted. */ +/* Notice informing user that their feedback is being submitted. */ "submit.feedback.submit.loading" = "Enviando"; +/* Notice informing user that their feedback is being submitted anonymously. */ +"submit.feedback.submitAnonymously.loading" = "Enviando de manera anónima"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Sugerencias"; @@ -11938,8 +11952,10 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Site's Viewers Profile. Displayed when the name is empty! */ "user.details.title.viewer" = "Visor del sitio"; -/* Site Email Subscribers - Site Subscribers */ +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "Suscriptores por correo electrónico"; + +/* Site Subscribers */ "users.list.title.subscribers" = "Suscriptores"; /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ diff --git a/WordPress/Resources/fr.lproj/Localizable.strings b/WordPress/Resources/fr.lproj/Localizable.strings index edc171c60cfc..2a3f172fc806 100644 --- a/WordPress/Resources/fr.lproj/Localizable.strings +++ b/WordPress/Resources/fr.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-17 15:44:22+0000 */ +/* Translation-Revision-Date: 2024-05-02 09:54:07+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: fr */ @@ -8826,6 +8826,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "fichier audio"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "Annuler"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "Vous avez apporté des modifications non enregistrées à cet article depuis un autre appareil. Modifié : %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "Enregistrement automatique disponible"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "Afficher les modifications"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "Échec du chargement de l’image. Veuillez en choisir une autre ou réessayer."; @@ -10260,6 +10272,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "Déplacement de l‘article dans la corbeille…"; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "Abandonner les modifications"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "Abandonner le brouillon"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "Poursuivre l’édition"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "Enregistrer le brouillon"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "Création de la sauvegarde automatique…"; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "Révision chargée"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "Enregistrement du brouillon…"; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "Par %@."; @@ -10269,6 +10302,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a sticky post in the post list. */ "postList.a11y.sticky" = "Épinglé."; +/* Badge for post cells */ +"postList.badgePendingReview" = "En attente de relecture"; + +/* Cancels an Action */ +"postList.cancel" = "Annuler"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "Supprimer définitivement"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "Voulez-vous vraiment supprimer définitivement « %@ » ?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "L’application charge les modifications effectuées précédemment sur le serveur. Veuillez réessayer plus tard."; + +/* A generic error message title */ +"postList.genericErrorMessage" = "Un problème est survenu"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "Changements hors ligne"; @@ -10281,6 +10332,39 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "Voir"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "Envoyer dans la corbeille"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "Voulez-vous vraiment envoyer « %@ » dans la corbeille ? Les modifications qui n’ont pas été envoyées précédemment sur le serveur seront perdues."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "Annuler le chargement"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "Fermer"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "Aucun chargement en attente"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@ sur %2$@"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "Retenter le chargement"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "Retenter les chargements"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "Chargements de contenu multimédia"; + +/* A generic error message title */ +"postNotice.errorTitle" = "Une erreur s’est produite"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "L’application charge les modifications effectuées précédemment sur le serveur. Veuillez réessayer plus tard."; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "Brouillon de la page mis en ligne"; @@ -10314,6 +10398,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "Voir"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "Le contenu a été modifié sur un autre appareil"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "« %@ » a été supprimé de manière permanente et ne peut plus être mis à jour"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "Sans titre"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "Ignorer"; @@ -10326,6 +10419,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "Options de l’image mise en avant"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "En attente de relecture"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Le partage automatique sur Twitter n’est plus disponible"; @@ -10335,6 +10431,33 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "Impossible de mettre à jour les réglages de l‘article"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "Uniquement visible pour les administrateurs et les éditeurs du site"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "Privé"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "Visible pour tous mais nécessite un mot de passe"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "Protégé par un mot de passe"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "Visible pour tout le monde"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "Annuler"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "Visibilité"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "Mot de passe"; + +/* Button save */ +"postVisibilityPicker.save" = "Enregistrer"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "Promouvoir avec Blaze"; @@ -10359,6 +10482,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "Aperçu"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "Publier"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "Réessayer"; @@ -10422,6 +10548,9 @@ Example: Reply to Pamela Nguyen */ /* Voiceover accessibility label informing the user that this button dismiss the current view */ "prepublishing.close" = "Fermer"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "Échec du chargement de %@ éléments"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "Titre"; @@ -10431,6 +10560,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "Date de publication"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "Immédiatement"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "Publication sur"; @@ -10495,6 +10627,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "Étiquettes"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "%@ éléments restants"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "%@ élément restant"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "Média en cours de chargement"; @@ -10504,6 +10642,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "Afficher toutes les réponses"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "Date de publication"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "La date dans votre fuseau horaire actuel :"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "Immédiatement"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "Supprimer la date de publication"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "Sélectionner la date de publication"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "Fuseau horaire"; + /* Post publish date picker */ "publishDatePicker.title" = "Date de publication"; @@ -10729,9 +10885,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "Accéder aux abonnements"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "Ajouter un blog"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Impossible de bloquer le blog"; @@ -10968,6 +11121,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "Ajouter une étiquette"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "J’aime"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "J’aime"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "Découvrir d’autres étiquettes"; @@ -11070,6 +11229,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "Articles similaires"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "Autre appareil"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "Appareil actuel"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "L’article a été modifié sur un autre appareil. Veuillez indiquer quelle version de l’article conserver."; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "Annuler"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "Enregistrer"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "Résoudre le conflit"; + +/* Title for the \"Copy Link\" action in Share Sheet. */ +"share.sheet.copy.link.title" = "Copier le lien"; + /* User action to dismiss media options. */ "shareExtension.editor.attachmentActions.dismiss" = "Ignorer"; @@ -11226,6 +11406,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "Inconnu"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "Tous les sites"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "Sites épinglés"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "Sites récents"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "Ajouter un site"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "Annuler"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "Terminé"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "Modifier"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "Choisir un site"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "Rappels"; @@ -11262,6 +11466,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "Le partage automatique sur Twitter n’est plus disponible en raison de modifications des conditions générales et tarifaires de Twitter."; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "Abonnés"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "Trafic"; @@ -11358,18 +11565,57 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "Pas assez d’activité. Revenez lorsque votre site aura plus de visiteurs !"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "Abonnés"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "Votre dernier article %1$@ a reçu %2$@ mentions J’aime."; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Votre dernier article %1$@ a reçu %2$@ mentions J’aime."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Total des abonnés WordPress.com : %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Total des abonnés par e-mail : %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Total des abonnés"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "Aujourd’hui"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "Abonné depuis"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "Nom"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "Abonnés"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "Clics"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "Ouvertures"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "Derniers e-mails"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "E-mails"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "Abonnés"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "Le tableau affiche %1$@, %2$@ et %3$@"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "Graphique à barres représentant les commentaires pour la période sélectionnée."; @@ -11382,6 +11628,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "Graphique à barres représentant les visiteurs pour la période sélectionnée."; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "Jours"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "Mois"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "Semaines"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "Années"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Ignorer"; @@ -11391,9 +11649,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "Recherchez des photos gratuites pour les ajouter à votre bibliothèque de médias !"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "Vous pouvez inclure votre adresse e-mail et votre identifiant pour nous aider à comprendre votre expérience (facultatif)."; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "aucune adresse e-mail saisie"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "aucun identifiant saisi"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "Terminé"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "Merci pour vos retours"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Envoyer"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "Envoi en cours"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Commentaire"; @@ -11565,6 +11841,21 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "non lues"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "Abonné par e-mail du site"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "Abonné du site"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "Utilisateur du site"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "Lecteur du site"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "Abonnés"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "visiter notre page de documentation"; diff --git a/WordPress/Resources/he.lproj/Localizable.strings b/WordPress/Resources/he.lproj/Localizable.strings index 4cb8ebfaeef1..7dac13101a0e 100644 --- a/WordPress/Resources/he.lproj/Localizable.strings +++ b/WordPress/Resources/he.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-24 14:54:09+0000 */ +/* Translation-Revision-Date: 2024-05-02 15:54:08+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: he_IL */ @@ -8850,6 +8850,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "קובץ אודיו"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "ביטול"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "ביצעת שינויים שלא נשמרו בפוסט הזה ממכשיר שונה. נערך: %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "שמירה אוטומטית זמינה"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "להציג את השינויים"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "לא ניתן לטעון את התמונה. יש לספק תמונה אחרת או לנסות שוב מאוחר יותר."; @@ -10299,6 +10311,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "מעביר לפח..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "לבטל את השינויים"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "לבטל את הטיוטה"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "להמשיך לערוך"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "לשמור טיוטה"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "יוצר עותק לשמירה אוטומטית..."; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "הגרסה העדכנית הוטענה"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "שומר טיוטה..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "מאת %@."; @@ -10311,6 +10344,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@, %2$@."; +/* Badge for post cells */ +"postList.badgePendingReview" = "בהמתנה לבדיקה"; + +/* Cancels an Action */ +"postList.cancel" = "ביטול"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "למחוק לצמיתות"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "האם ברצונך למחוק לצמיתות את \"%@\"?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "האפליקציה מעלה שינויים שבוצעו בעבר בשרת. יש לנסות שוב מאוחר יותר."; + +/* A generic error message title */ +"postList.genericErrorMessage" = "משהו השתבש"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "שינויים שבוצעו באופן לא מקוון"; @@ -10326,6 +10377,42 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "להציג"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "להעביר לפח"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "האם ברצונך להעביר את \"%@\" לפח? אם שינויים לא נשמרו בעבר בשרת, הם יאבדו."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "לבטל את ההעלאה"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "לסגור"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "אין העלאות בהמתנה"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@ מתוך %2$@"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "לנסות להעלות שוב"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "לנסות להעלות שוב"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "העלאות מדיה"; + +/* A generic error message title */ +"postNotice.errorTitle" = "אירעה שגיאה"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "האפליקציה מעלה שינויים שבוצעו בעבר בשרת. יש לנסות שוב מאוחר יותר."; + +/* Button OK */ +"postNotice.ok" = "אישור"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "הטיוטה של העמוד הועלתה"; @@ -10359,6 +10446,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "להציג"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "בוצעו בתוכן שינויים ממכשיר אחר"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "הפריט \"%@\" נמחק לצמיתות ולא ניתן לעדכן אותו עוד"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "ללא כותרת"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "ביטול"; @@ -10371,6 +10467,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "אפשרויות לתמונה המרכזית"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "בהמתנה לבדיקה"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "האפשרות לשיתוף אוטומטי בטוויטר כבר לא זמינה"; @@ -10380,6 +10479,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "העדכון של הגדרות ההודעה נכשל."; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "זמין רק למנהלי האתר ולעורכים"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "פרטי"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "זמין לכולם אבל נדרשת סיסמה"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "מוגן בסיסמה"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "זמין לכולם"; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "ציבורי"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "ביטול"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "נראות"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "סיסמה"; + +/* Button save */ +"postVisibilityPicker.save" = "לשמור"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "לקדם עם Blaze"; @@ -10404,6 +10533,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "תצוגה מקדימה"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "לפרסם"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "לנסות שוב"; @@ -10470,6 +10602,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "Jetpack Social"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "ההעלאה של %@ פריטים נכשלה"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "כותרת"; @@ -10479,6 +10614,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "תאריך פרסום"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "מייד"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "מפרסם בעמוד"; @@ -10543,6 +10681,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "תגיות"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "נותרו %@ פריטים"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "נותר פריט %@"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "מעלה מדיה"; @@ -10552,6 +10696,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "להציג את כל התשובות"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "תאריך פרסום"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "התאריך באזור הזמן הנוכחי שלך:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "מייד"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "להסיר את 'תאריך פרסום'"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "לבחור 'תאריך פרסום'"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "אזור זמן"; + /* Post publish date picker */ "publishDatePicker.title" = "תאריך פרסום"; @@ -10777,9 +10939,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "לעבור למינויים"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "להוסיף בלוג"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "אין אפשרות לחסום את הבלוג"; @@ -11019,6 +11178,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "להוסיף תגית"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "לייק"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "קיבל לייק"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "לגלות תגיות נוספות"; @@ -11121,6 +11286,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "פוסטים קשורים"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "מכשיר אחר"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "מכשיר נוכחי"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "בוצעו בפוסט שינויים ממכשיר אחר. יש לבחור את הגרסה של הפוסט שברצונך לשמור."; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "ביטול"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "לשמור"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "לפתור התנגשות"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "להעתיק את הקישור"; @@ -11298,6 +11481,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "לא ידוע"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "כל האתרים"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "אתרים נעוצים"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "אתרים מהתקופה האחרונה"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "להוסיף אתר"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "ביטול"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "בוצע"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "לערוך"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "לבחור אתר"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "תזכורות"; @@ -11334,6 +11541,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "האפשרות לשיתוף אוטומטי בטוויטר כבר לא זמינה עקב השינויים בתנאים ובתמחור של טוויטר."; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "מנויים"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "תעבורה"; @@ -11430,18 +11640,57 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "אין מספיק פעילות. כדאי לחזור לכאן מאוחד יותר לאחר שעוד מבקרים נכנסו לאתר!"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "מנויים"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "הפוסט האחרון שלך %1$@ קיבל %2$@ לייקים."; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "הפוסט האחרון שלך %1$@ קיבל לייק %2$@."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "סך כל המנויים ב-WordPress.com: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "סך כל המנויים באימייל: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "סך כל המנויים"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "היום"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "מנוי מאז"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "שם"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "מנויים"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "קליקים"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "לחיצות לפתיחה"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "האימיילים האחרונים"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "הודעות אימייל"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "מנויים"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "הטבלה מציגה את %1$@,‏ %2$@ ואת %3$@"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "תרשים עמודות שמשקף את כמות התגובות בתקופה שבחרת."; @@ -11454,6 +11703,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "תרשים עמודות שמשקף את כמות המבקרים בתקופה שבחרת."; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "ימים"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "חודשים"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "שבועות"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "שנים"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "ביטול"; @@ -11463,9 +11724,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "אפשר לחפש תמונות חינמיות ולהוסיף אותן לספריית המדיה שלך!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "יש לך אפשרות לבחור אם להוסיף את האימייל ואת שם המשתמש כדי לעזור לנו להבין את החוויה שלך."; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "לא הזנת כתובת אימייל"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "לא הזנת שם משתמש"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "בוצע"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "תודה על המשוב"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "לשלוח"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "שולח"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "משוב"; @@ -11637,6 +11916,21 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "לא נקרא"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "מנוי של האתר באימייל"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "מנוי של האתר"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "משתמש של האתר"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "צופה של האתר"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "מנויים"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "היכנס לעמוד המסמכים שלנו"; diff --git a/WordPress/Resources/id.lproj/Localizable.strings b/WordPress/Resources/id.lproj/Localizable.strings index 3bce1b53c5d4..500321fb1379 100644 --- a/WordPress/Resources/id.lproj/Localizable.strings +++ b/WordPress/Resources/id.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-23 09:54:09+0000 */ +/* Translation-Revision-Date: 2024-05-02 09:54:08+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: id */ @@ -8853,6 +8853,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "file audio"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "Batal"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "Anda telah membuat perubahan yang tidak tersimpan pada pos ini dari perangkat lain. Diedit: %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "Simpan Otomatis Tersedia"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "Lihat Perubahan"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "Tidak dapat memuat gambar. Pilih yang lainnya atau coba lagi nanti."; @@ -10305,6 +10317,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "Memindahkan ke tempat sampah..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "Buang Perubahan"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "Buang Draf"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "Lanjutkan Mengedit"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "Simpan Draf"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "Membuat draf tersimpan otomatis..."; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "Revisi dimuat"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "Menyimpan draf..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "Oleh %@"; @@ -10317,6 +10350,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@, %2$@."; +/* Badge for post cells */ +"postList.badgePendingReview" = "Menunggu peninjauan"; + +/* Cancels an Action */ +"postList.cancel" = "Batal"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "Hapus Secara Permanen"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "Anda yakin ingin menghapus \"%@\" secara permanen?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "Aplikasi sedang mengunggah perubahan yang dibuat sebelumnya ke server. Silakan coba lagi nanti."; + +/* A generic error message title */ +"postList.genericErrorMessage" = "Terjadi kesalahan"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "Perubahan offline"; @@ -10332,6 +10383,42 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "Lihat"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "Pindahkan ke Tempat Sampah"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "Anda yakin ingin memindahkan \"%@\" ke tempat sampah? Perubahan yang tidak dikirimkan ke server akan hilang."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "Batalkan Unggahan"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "Tutup"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "Tidak ada unggahan tertunda"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@ dari %2$@"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "Coba Unggah Lagi"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "Coba Unggah Lagi"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "Unggahan Media"; + +/* A generic error message title */ +"postNotice.errorTitle" = "Terjadi error"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "Aplikasi sedang mengunggah perubahan yang dibuat sebelumnya ke server. Silakan coba lagi nanti."; + +/* Button OK */ +"postNotice.ok" = "OKE"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "Konsep halaman diunggah"; @@ -10365,6 +10452,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "Lihat"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "Konten diubah di perangkat lain"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "\"%@\" dihapus permanen dan tidak dapat diperbarui lagi"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "Tanpa Judul"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "Tutup"; @@ -10377,6 +10473,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "Pilihan Gambar Andalan"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "Menunggu peninjauan"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Fitur Berbagi Otomatis ke Twitter Tidak Lagi Tersedia"; @@ -10386,6 +10485,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "Gagal memperbarui pengaturan pos"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "Hanya dapat dilihat oleh admin dan editor situs."; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "Privat"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "Dapat dilihat semua orang dengan memasukkan kata sandi."; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "Dilindungi kata sandi"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "Dapat dilihat oleh semua orang."; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "Publik"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "Batal"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "Visibilitas"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "Kata sandi"; + +/* Button save */ +"postVisibilityPicker.save" = "Simpan"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "Promosikan dengan Blaze"; @@ -10410,6 +10539,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "Pratinjau"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "Publikasikan"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "Coba ulang"; @@ -10476,6 +10608,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "Jetpack Social"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "%@ item gagal diunggah"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "Judul"; @@ -10485,6 +10620,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "Tanggal Terbit"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "Segera"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "Memublikasikan ke"; @@ -10549,6 +10687,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "Tag"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "%@ item tersisa"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "%@ item tersisa"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "Mengunggah media"; @@ -10558,6 +10702,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "Lihat semua tanggapan"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "Tanggal Publikasi"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "Tanggal dalam zona waktu Anda saat ini:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "Segera"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "Hapus Tanggal Publikasi"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "Pilih Tanggal Publikasi"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "Zona Waktu"; + /* Post publish date picker */ "publishDatePicker.title" = "Tanggal Terbit"; @@ -10783,9 +10945,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "Lihat Langganan"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "Tambahkan blog"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Tidak dapat memblokir blog"; @@ -11025,6 +11184,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "Tambahkan Tag"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "Suka"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "Disukai"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "Temukan tag selengkapnya"; @@ -11127,6 +11292,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "Pos Terkait"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "Perangkat lain"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "Perangkat saat ini"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "Pos ini diubah di perangkat lain. Pilih versi pos yang ingin disimpan."; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "Batal"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "Simpan"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "Selesaikan Konflik"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "Salin Tautan"; @@ -11304,6 +11487,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "Tidak diketahui"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "Semua situs"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "Situs yang disematkan"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "Situs terbaru"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "Tambahkan situs"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "Batal"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "Selesai"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "Pilih sebuah situs"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "Pengingat"; @@ -11340,6 +11544,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "Fitur berbagi otomatis ke Twitter tidak lagi tersedia karena adanya perubahan ketentuan dan harga dari Twitter."; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "Pengikut"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "Lalu lintas"; @@ -11436,18 +11643,57 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "Aktivitas tidak cukup. Periksa lagi nanti saat situs Anda lebih ramai pengunjung!"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "Pengikut"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "Pos terbaru Anda %1$@ disukai %2$@ kali."; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Pos terbaru Anda %1$@ disukai %2$@ kali."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Total Pengikut WordPress.com: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Total Pengikut Email: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Total Pengikut"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "Hari ini"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "Pengikut sejak"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "Nama"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "Pengikut"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "Klik"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "Buka"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "Email terbaru"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "Email"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "Pengikut"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "Tabel menunjukkan %1$@, %2$@, dan %3$@"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "Diagram Batang Jumlah Komentar untuk periode terpilih."; @@ -11460,6 +11706,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "Diagram Batang Jumlah Pengunjung untuk periode terpilih."; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "Hari"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "Bulan"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "Minggu"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "Tahun"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Tutup"; @@ -11469,9 +11727,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "Cari foto gratis untuk ditambahkan ke Pustaka Media Anda!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "Anda dapat memilih untuk menyertakan email dan nama pengguna guna membantu kami memahami pengalaman Anda."; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "email tidak dimasukkan"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "nama pengguna tidak dimasukkan"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "Selesai"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "Terima kasih atas masukan Anda"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Kirim"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "Mengirim"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Masukan"; @@ -11643,6 +11919,21 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "belum dibaca"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "Pengikut Email Situs"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "Pengikut Situs"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "Pengguna Situs"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "Pembaca Situs"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "Pengikut"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "kunjungi halaman dokumentasi kami"; diff --git a/WordPress/Resources/it.lproj/Localizable.strings b/WordPress/Resources/it.lproj/Localizable.strings index 6fe724b64bb2..844ec16f0d33 100644 --- a/WordPress/Resources/it.lproj/Localizable.strings +++ b/WordPress/Resources/it.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-23 11:54:09+0000 */ +/* Translation-Revision-Date: 2024-05-03 12:54:13+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: it */ @@ -8844,6 +8844,12 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "file audio"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "Hai apportato modifiche non salvate a questo articolo da un dispositivo diverso. Modificato: %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "Visualizza modifiche"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "Impossibile caricare l'immagine. Scegline una differente o prova più tardi."; @@ -10290,6 +10296,18 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "Spostamento dell'articolo nel cestino in corso..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "Annulla modifiche"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "Annulla bozza"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "Creazione del salvataggio automatico in corso..."; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "Salvataggio bozza in corso..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "Di %@."; @@ -10299,6 +10317,15 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a sticky post in the post list. */ "postList.a11y.sticky" = "In evidenza."; +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "Elimina in modo permanente"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "Desideri eliminare in modo permanente \"%@\"?"; + +/* A generic error message title */ +"postList.genericErrorMessage" = "Si è verificato un problema"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "Modifiche offline"; @@ -10314,6 +10341,30 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "Visualizza"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "Sposta nel cestino"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "Desideri spostare \"%@\" nel cestino? Tutte le modifiche non inviate in precedenza al server andranno perse."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "Annulla caricamento"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "Nessun caricamento in sospeso"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@ di %2$@"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "Riprova i caricamenti"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "Caricamenti di elementi multimediali"; + +/* Button OK */ +"postNotice.ok" = "OK"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "Bozza pagina caricata"; @@ -10347,6 +10398,9 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "Visualizza"; +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "\"%@\" è stato eliminato in modo permanente e non può più essere aggiornato"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "Ignora"; @@ -10368,6 +10422,15 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "Impossibile aggiornare le impostazioni dell'articolo"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "Visibile solo agli amministratori e agli editori del sito"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "Protetto da password"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "Visibilità"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "Promuovi con la Blaze"; @@ -10392,6 +10455,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "Anteprima"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "Pubblicazione"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "Riprova"; @@ -10452,6 +10518,9 @@ Example: Reply to Pamela Nguyen */ /* Voiceover accessibility label informing the user that this button dismiss the current view */ "prepublishing.close" = "Chiudi"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "Impossibile caricare %@ articoli"; + /* Primary button label in the pre-publishing sheet */ "prepublishing.publish" = "Pubblicazione"; @@ -10516,12 +10585,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* The navigation title for the pre-publishing social accounts screen. */ "prepublishing.socialAccounts.navigationTitle" = "Social"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "%@ articolo rimanente"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "Caricamento del media"; /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "Visualizza tutte le risposte"; +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "La data nel tuo fuso orario attuale:"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "Rimuovi data di pubblicazione"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "Seleziona data di pubblicazione"; + /* Button label that dismisses the qr log in flow and returns the user back to the previous screen */ "qrLoginVerifyAuthorization.completedInstructions.dismiss" = "Ignora"; @@ -10744,9 +10825,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "Vai a Abbonamenti"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "Aggiungi un blog"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Non è possibile bloccare il blog"; @@ -10983,6 +11061,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "Aggiungi un tag"; +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "Con Mi piace"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "Scopri di più sui tag"; @@ -11085,6 +11166,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "Articoli correlati"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "Altro dispositivo"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "Dispositivo corrente"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "L'articolo è stato modificato su un altro dispositivo. Seleziona la versione dell'articolo da mantenere."; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "Annulla"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "Salva"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "Risolvi il conflitto"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "Copia link"; @@ -11259,6 +11358,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "Sconosciuto"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "Tutti i siti"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "Siti fissati"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "Fatto"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "Promemoria"; @@ -11397,12 +11505,36 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Il tuo ultimo articolo %1$@ ha ricevuto %2$@ mi piace."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Abbonati totali di WordPress.com: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Totale abbonati e-mail: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Totale abbonati"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "Oggi"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "Abbonato dal"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "Aperture"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "Ultime e-mail"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "E-mail"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "La tabella mostra %1$@, %2$@ e %3$@"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "Barra del grafico che indica i Commenti per il periodo selezionato."; @@ -11424,9 +11556,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "Cerca per trovare foto gratuite da aggiungere alla tua Libreria multimediale!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "Opzionalmente, puoi includere l'e-mail e il nome utente per aiutarci a comprendere la tua esperienza."; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "nessuna e-mail inserita"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "nessun nome utente inserito"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "Fatto"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "Grazie per il feedback"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Invia"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "Invio in corso"; + /* Section title for prominent suggestions */ "suggestions.section.prominent" = "In questa conversazione"; @@ -11592,6 +11742,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "non lette"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "Abbonati e-mail del sito"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "Abbonati del sito"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "Utenti del sito"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "Visitatori del sito"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "visita la pagina della documentazione"; diff --git a/WordPress/Resources/ja.lproj/Localizable.strings b/WordPress/Resources/ja.lproj/Localizable.strings index 00d9fbf40d8f..91bd273fb89b 100644 --- a/WordPress/Resources/ja.lproj/Localizable.strings +++ b/WordPress/Resources/ja.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-23 10:54:08+0000 */ +/* Translation-Revision-Date: 2024-05-02 09:54:08+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.1 */ /* Language: ja_JP */ @@ -8850,6 +8850,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "音声ファイル"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "キャンセル"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "別の端末からこの投稿に加えられた未保存の変更があります。 編集済み: %@。"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "自動保存が利用可能"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "変更を表示"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "画像をアップロードできません。 別の画像を選択するか、後でもう一度お試しください。"; @@ -10293,6 +10305,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "投稿をゴミ箱に移動中..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "変更を破棄"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "下書きを破棄"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "編集を続行"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "下書きを保存"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "自動保存を作成中..."; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "変更履歴の読み取りが完了しました"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "下書きを保存中..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "作成者: %@。"; @@ -10305,6 +10338,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@、%2$@。"; +/* Badge for post cells */ +"postList.badgePendingReview" = "レビュー待ち"; + +/* Cancels an Action */ +"postList.cancel" = "キャンセル"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "完全に削除"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "「%@」を完全に削除してもよいですか ?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "アプリが以前に行われた変更をサーバーにアップロードしています。 後でもう一度お試しください。"; + +/* A generic error message title */ +"postList.genericErrorMessage" = "エラーが発生しました"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "オフラインの変更"; @@ -10320,6 +10371,39 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "表示"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "ゴミ箱に移動"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "「%@」をゴミ箱に移動してもよいですか ? 以前サーバーに送信されなかった変更はすべて失われます。"; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "アップロードをキャンセル"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "閉じる"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "保留中のアップロードはありません"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@ \/ %2$@"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "アップロードを再試行"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "アップロードを再試行"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "メディアのアップロード"; + +/* A generic error message title */ +"postNotice.errorTitle" = "エラーが発生しました"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "アプリが以前に行われた変更をサーバーにアップロードしています。 後でもう一度お試しください。"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "ページの下書きをアップロードしました"; @@ -10353,6 +10437,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "表示"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "コンテンツが別の端末で編集されました"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "「%@」は完全に削除され、更新できなくなりました"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "無題"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "閉じる"; @@ -10365,6 +10458,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "アイキャッチ画像のオプション"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "レビュー待ち"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Twitter の自動共有機能は利用できなくなりました"; @@ -10374,6 +10470,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "投稿設定を更新できませんでした"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "サイト管理者と編集者にだけ表示されます"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "非公開"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "誰でも見ることができますが、パスワードが必要です"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "パスワード保護"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "すべての人に表示"; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "公開"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "キャンセル"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "表示設定"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "パスワード"; + +/* Button save */ +"postVisibilityPicker.save" = "保存"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "Blaze を使って宣伝"; @@ -10398,6 +10524,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "プレビュー"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "公開"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "再試行"; @@ -10464,6 +10593,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "Jetpack ソーシャル"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "%@件のアイテムのアップロードに失敗しました"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "タイトル"; @@ -10473,6 +10605,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "公開日"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "すぐに"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "公開先"; @@ -10537,6 +10672,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "タグ"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "残り%@件のアイテム"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "残り%@件のアイテム"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "メディアをアップロード中"; @@ -10546,6 +10687,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "すべての回答を表示"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "公開日"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "現在お使いのタイムゾーンの日付:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "すぐに"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "公開日を削除"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "公開日を選択"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "タイムゾーン"; + /* Post publish date picker */ "publishDatePicker.title" = "公開日"; @@ -10771,9 +10930,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "サブスクリプションに移動"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "ブログを追加"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "ブログをブロックできませんでした"; @@ -11010,6 +11166,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "タグを追加"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "いいね"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "いいね済み"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "その他のタグを見る"; @@ -11109,6 +11271,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "関連記事"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "別の端末"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "現在の端末"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "投稿が別の端末で編集されました。 保持する投稿のバージョンを選択してください。"; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "キャンセル"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "保存"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "競合を解決"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "リンクをコピー"; @@ -11286,6 +11466,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "不明"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "すべてのサイト"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "固定済みサイト"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "最近のサイト"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "サイトを追加"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "キャンセル"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "完了"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "編集"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "サイトを選択"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "リマインダー"; @@ -11322,6 +11526,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "Twitter の自動共有機能は Twitter の規約と価格に変更があったため利用できなくなりました。"; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "購読者"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "トラフィック"; @@ -11418,15 +11625,54 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "アクティビティが足りません。 サイトの訪問者数が増えたら後でもう一度確認してください。"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "購読者"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "最新の投稿 %1$@ が%2$@回「いいね」されました。"; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "最新の投稿 %1$@ が%2$@回「いいね」されました。"; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "WordPress.com 購読者総数: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "メール購読者総数: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "購読者総数"; + /* Stats 'Today' header */ "stats.period.todayCard.title" = "今日"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "購読者になった日時"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "名前"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "購読者"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "クリック数"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "開いた数"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "最新のメール"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "メール"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "購読者"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "%1$@、%2$@、%3$@ を示すテーブル"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "選択した期間のコメントを表す棒グラフ。"; @@ -11439,6 +11685,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "選択した期間の訪問者を表す棒グラフ。"; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "日"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "月"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "週"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "年"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "削除"; @@ -11448,9 +11706,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "無料の写真を検索して、メディアライブラリに追加してください。"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "お客様の状況をこちらで把握できるよう、メールアドレスとユーザー名を含めていただくことも可能です。"; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "入力されたメールアドレスがありません"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "入力されたユーザー名がありません"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "完了"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "フィードバックありがとうございます"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "送信"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "送信中"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "フィードバック"; @@ -11622,6 +11898,21 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "未読"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "サイトのメール購読者"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "サイトの購読者"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "サイトのユーザー"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "サイトの読者"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "購読者"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "ドキュメントページに移動する"; diff --git a/WordPress/Resources/ko.lproj/Localizable.strings b/WordPress/Resources/ko.lproj/Localizable.strings index 8aee55525f7b..e3ff03ef9c53 100644 --- a/WordPress/Resources/ko.lproj/Localizable.strings +++ b/WordPress/Resources/ko.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-30 02:15:15+0000 */ +/* Translation-Revision-Date: 2024-05-06 03:03:50+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.1 */ /* Language: ko_KR */ @@ -10389,6 +10389,9 @@ Example: Reply to Pamela Nguyen */ /* Message of the trash post or page confirmation alert. */ "postList.trash.alertMessage" = "\"%@\"을 휴지통에 버리시겠습니까? 이전에 서버로 전송되지 않은 변경 내용은 모두 손실됩니다."; +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "업로드 취소"; + /* Close button in postMediaUploadStatusView */ "postMediaUploadStatusView.close" = "닫기"; @@ -10398,6 +10401,9 @@ Example: Reply to Pamela Nguyen */ /* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ "postMediaUploadStatusView.progress" = "%1$@\/%2$@"; +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "업로드 재시도"; + /* Retry upload button in postMediaUploadStatusView */ "postMediaUploadStatusView.retryUploads" = "업로드 재시도"; @@ -10943,7 +10949,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "구독으로 이동"; /* No Tags View Button Label */ -"reader.no.tags.title" = "블로그 추가"; +"reader.no.tags.title" = "태그 추가"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "블로그를 차단할 수 없음"; @@ -11661,6 +11667,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "최신 글 %1$@에 %2$@개의 좋아요가 있습니다."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "총 워드프레스닷컴 구독자: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "총 이메일 구독자: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "구독자 합계"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@(%3$@)"; @@ -11745,8 +11760,7 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "제출"; -/* Notice informing user that their feedback is being submitted anonymously. - Notice informing user that their feedback is being submitted. */ +/* Notice informing user that their feedback is being submitted. */ "submit.feedback.submit.loading" = "전송 중…"; /* The title for the the In-App Feedback screen */ @@ -11932,8 +11946,7 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Site's Viewers Profile. Displayed when the name is empty! */ "user.details.title.viewer" = "블로그 방문자"; -/* Site Email Subscribers - Site Subscribers */ +/* Site Subscribers */ "users.list.title.subscribers" = "구독자"; /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ diff --git a/WordPress/Resources/nl.lproj/Localizable.strings b/WordPress/Resources/nl.lproj/Localizable.strings index ba10d54dc72b..29d56eb96efc 100644 --- a/WordPress/Resources/nl.lproj/Localizable.strings +++ b/WordPress/Resources/nl.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-30 11:11:41+0000 */ +/* Translation-Revision-Date: 2024-05-01 19:21:12+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: nl */ @@ -10924,6 +10924,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "Reader-instellingen"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "Een blog toevoegen"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "Ontdek blogs"; @@ -10949,7 +10952,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "Ga naar Abonnementen"; /* No Tags View Button Label */ -"reader.no.tags.title" = "Een blog toevoegen"; +"reader.no.tags.title" = "Een tag toevoegen"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Kan blog niet blokkeren"; @@ -11760,10 +11763,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Verzenden"; -/* Notice informing user that their feedback is being submitted anonymously. - Notice informing user that their feedback is being submitted. */ +/* Notice informing user that their feedback is being submitted. */ "submit.feedback.submit.loading" = "Aan het verzenden"; +/* Notice informing user that their feedback is being submitted anonymously. */ +"submit.feedback.submitAnonymously.loading" = "Anoniem aan het verzenden"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Feedback"; @@ -11947,8 +11952,10 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Site's Viewers Profile. Displayed when the name is empty! */ "user.details.title.viewer" = "Site kijker"; -/* Site Email Subscribers - Site Subscribers */ +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "E-mailabonnees"; + +/* Site Subscribers */ "users.list.title.subscribers" = "Abonnees"; /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ diff --git a/WordPress/Resources/pt-BR.lproj/Localizable.strings b/WordPress/Resources/pt-BR.lproj/Localizable.strings index 40161624dda4..778523868b4c 100644 --- a/WordPress/Resources/pt-BR.lproj/Localizable.strings +++ b/WordPress/Resources/pt-BR.lproj/Localizable.strings @@ -9521,9 +9521,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "Ir para Assinaturas"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "Adicionar um blog"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Não foi possível bloquear o blog"; diff --git a/WordPress/Resources/ro.lproj/Localizable.strings b/WordPress/Resources/ro.lproj/Localizable.strings index 632251085263..56b57fe7f58d 100644 --- a/WordPress/Resources/ro.lproj/Localizable.strings +++ b/WordPress/Resources/ro.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-30 06:00:52+0000 */ +/* Translation-Revision-Date: 2024-05-06 19:05:26+0000 */ /* Plural-Forms: nplurals=3; plural=(n == 1) ? 0 : ((n == 0 || n % 100 >= 2 && n % 100 <= 19) ? 1 : 2); */ /* Generator: GlotPress/4.0.1 */ /* Language: ro */ @@ -5405,7 +5405,7 @@ translators: %s: Select control button label e.g. \"Button width\" */ /* Link to privacy settings page Privacy Settings Title */ -"Privacy Settings" = "Setări confidențialitate"; +"Privacy Settings" = "Setări pentru confidențialitate"; /* No comment provided by engineer. */ "Privacy and Rating" = "Confidențialitate și evaluare"; @@ -7052,7 +7052,7 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ "The video could not be added to the Media Library." = "Videoul nu a putut fi adăugat în Biblioteca media."; /* Title of alert when theme activation succeeds */ -"Theme Activated" = "Tema a fost activată"; +"Theme Activated" = "Tema este activată"; /* Noun. Name of the Themes feature Themes option in the blog details @@ -10924,6 +10924,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "Setări Cititor"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "Adaugă un blog"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "Descoperă bloguri"; @@ -10949,7 +10952,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "Mergi la Abonamente"; /* No Tags View Button Label */ -"reader.no.tags.title" = "Adaugă un blog"; +"reader.no.tags.title" = "Adaugă o etichetă"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Nu pot să blochez blogul"; @@ -11667,6 +11670,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Ultimul tău articol %1$@ a avut %2$@ apreciere."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Total abonați WordPress.com: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Total abonați prin email: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Total abonați"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; @@ -11751,10 +11763,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Trimite"; -/* Notice informing user that their feedback is being submitted anonymously. - Notice informing user that their feedback is being submitted. */ +/* Notice informing user that their feedback is being submitted. */ "submit.feedback.submit.loading" = "Trimitere"; +/* Notice informing user that their feedback is being submitted anonymously. */ +"submit.feedback.submitAnonymously.loading" = "Trimitere anonimă"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Impresii"; @@ -11938,8 +11952,10 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Site's Viewers Profile. Displayed when the name is empty! */ "user.details.title.viewer" = "Vizitator pe site"; -/* Site Email Subscribers - Site Subscribers */ +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "Abonați prin email"; + +/* Site Subscribers */ "users.list.title.subscribers" = "Abonați"; /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ diff --git a/WordPress/Resources/ru.lproj/Localizable.strings b/WordPress/Resources/ru.lproj/Localizable.strings index f54bd8191637..5ed7b711f0ac 100644 --- a/WordPress/Resources/ru.lproj/Localizable.strings +++ b/WordPress/Resources/ru.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-29 21:08:36+0000 */ +/* Translation-Revision-Date: 2024-05-02 15:54:07+0000 */ /* Plural-Forms: nplurals=3; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : 2); */ /* Generator: GlotPress/4.0.1 */ /* Language: ru */ @@ -8853,6 +8853,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "аудио файл"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "Отмена"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "Вы внесли изменения в эту запись с другого устройства, но не сохранили их. Изменено: %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "Доступно автоматическое сохранение"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "Просмотреть изменения"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "Не удалось загрузить изображение. Выберите другое или повторите попытку."; @@ -10305,6 +10317,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "Удаление записи в корзину..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "Отменить изменения"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "Удалить черновик"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "Продолжить редактирование"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "Сохранить черновик"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "Автосохранение..."; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "Редакция загружена"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "Сохранение черновика..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "Автор: %@."; @@ -10317,6 +10350,21 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@, %2$@."; +/* Badge for post cells */ +"postList.badgePendingReview" = "На рассмотрении"; + +/* Cancels an Action */ +"postList.cancel" = "Отмена"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "Удалить навсегда"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "Приложение загружает на сервер внесённые ранее изменения Повторите попытку позже."; + +/* A generic error message title */ +"postList.genericErrorMessage" = "Что-то пошло не так"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "Изменения внесены офлайн"; @@ -10332,6 +10380,42 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "Просмотр"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "Отправить в корзину"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "Вы действительно хотите отправить \"%@\" в корзину? Изменения, не загруженные на сервер, будут утрачены."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "Отменить загрузку"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "Закрыть"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "Нет загрузок в очереди"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@ из %2$@"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "Повторить загрузку"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "Повторить загрузки"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "Загруженные медиафайлы"; + +/* A generic error message title */ +"postNotice.errorTitle" = "Произошла ошибка"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "Приложение загружает на сервер внесённые ранее изменения. Повторите попытку позже."; + +/* Button OK */ +"postNotice.ok" = "ОК"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "Черновик страницы загружен"; @@ -10365,6 +10449,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "Просмотр"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "Запись была изменена на другом устройстве"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "\"%@\" безвозвратно удалён и не может быть восстановлен."; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "Без названия"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "Закрыть"; @@ -10377,6 +10470,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "Настройки изображения записи"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "На рассмотрении"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Автоматическая публикация в Twitter больше не доступна"; @@ -10386,6 +10482,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "Не удалось обновить настройки записи"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "Видна только администраторам и редакторам"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "Закрытая"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "Общедоступная, но требует ввода пароля"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "Защищено паролем"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "Видна всем"; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "Публичная"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "Отмена"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "Видимость"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "Пароль"; + +/* Button save */ +"postVisibilityPicker.save" = "Сохранить"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "Продвигайте содержимое с помощью Blaze"; @@ -10479,6 +10605,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "Jetpack Social"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "Не удалось загрузить элементы (%@)"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "Заголовок"; @@ -10488,6 +10617,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "Дата публикации"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "Немедленно"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "Публикация на"; @@ -10552,6 +10684,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "Теги"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "Осталось элементов: %@"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "Осталось элементов: %@"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "Загрузка медиафайлов"; @@ -10561,6 +10699,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "Посмотреть все ответы"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "Дата публикации"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "Дата в вашем часовом поясе:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "Немедленно"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "Удалить дату публикации"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "Выбрать дату публикации"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "Часовой пояс"; + /* Post publish date picker */ "publishDatePicker.title" = "Дата публикации"; @@ -10786,9 +10942,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "Перейти в подписки"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "Добавьте блог"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Не удалось заблокировать блог"; @@ -11028,6 +11181,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "Добавьте тег"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "Оценка «Нравится»"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "Понравилось"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "Другие теги"; @@ -11130,6 +11289,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "Похожие записи"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "Другое устройство"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "Текущее устройство"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "Запись была изменена на другом устройстве. Выберите, какую версию записи вы хотите сохранить."; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "Отмена"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "Сохранить"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "Устранить конфликт"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "Копировать ссылку"; @@ -11307,6 +11484,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "Неизвестно"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "Все сайты"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "Закреплённые сайты"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "Последние сайты"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "Добавить сайт"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "Отмена"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "Готово"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "Редактировать"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "Выберите сайт"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "Напоминания"; @@ -11343,6 +11544,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "Автоматическая публикация в Twitter больше не доступна из-за изменения условий использования и цен Twitter."; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "Подписчики"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "Посещаемость"; @@ -11439,18 +11643,57 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "Недостаточно активности. Вернитесь позже, когда на сайте будет больше посетителей!"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "Подписчики"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "Ваша последняя запись %1$@ получила %2$@ отметок \"нравится\"."; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Ваша последняя запись %1$@ получила %2$@ отметку \"нравится\"."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Всего подписчиков WordPress.com: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Всего подписчиков в рассылке: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Всего подписчиков"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "Сегодня"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "Подписчик с"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "Имя"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "Подписчики"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "Переходы"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "Посещения"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "Недавние рассылки"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "Рассылки"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "Подписчики"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "Таблица, где показаны %1$@, %2$@ и %3$@"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "Столбчатая диаграмма, представляющая комментарии за выбранный период."; @@ -11463,6 +11706,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "Столбчатая диаграмма, представляющая посетителей за выбранный период."; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "Дни"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "Месяцы"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "Недели"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "Годы"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Закрыть"; @@ -11472,9 +11727,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "Найдите бесплатные фотографии и добавьте их в вашу Библиотеку файлов!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "По желанию вы можете указать свой адрес электронной почты и имя пользователя, чтобы помочь нам понять ситуацию."; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "адрес эл. почты не указан"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "имя пользователя не указано"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "Готово"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "Спасибо за ваш отзыв!"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Отправить"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "Отправка"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Обратная связь"; @@ -11646,6 +11919,21 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "непрочитано"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "Подписчик на рассылку сайта по эл. почте"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "Подписчик сайта"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "Пользователь сайта"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "Посетитель сайта"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "Подписчики"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "посетите нашу страницу документации"; diff --git a/WordPress/Resources/sq.lproj/Localizable.strings b/WordPress/Resources/sq.lproj/Localizable.strings index edcfbba614fd..54dc21dcb8d9 100644 --- a/WordPress/Resources/sq.lproj/Localizable.strings +++ b/WordPress/Resources/sq.lproj/Localizable.strings @@ -10753,9 +10753,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "Kaloni te Pajtime"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "Shtoni një blog"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "S’arrihet të bllokohet blogu"; diff --git a/WordPress/Resources/sv.lproj/Localizable.strings b/WordPress/Resources/sv.lproj/Localizable.strings index 195895803aea..dd1c007ad120 100644 --- a/WordPress/Resources/sv.lproj/Localizable.strings +++ b/WordPress/Resources/sv.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-29 12:22:41+0000 */ +/* Translation-Revision-Date: 2024-05-04 14:07:56+0000 */ /* Plural-Forms: nplurals=2; plural=n != 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: sv_SE */ @@ -8856,6 +8856,12 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* An alert suggesting to load autosaved revision for a published post */ "autosaveAlert.cancel" = "Avbryt"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "Du har ej sparade ändringar i det här inlägget som har gjorts med en annan enhet. Redigerade: %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "Autosparning tillgänglig"; + /* An alert suggesting to load autosaved revision for a published post */ "autosaveAlert.viewChanges" = "Visa ändringar"; @@ -10323,6 +10329,12 @@ Example: Reply to Pamela Nguyen */ /* Button in an alert confirming saving a new draft */ "postEditor.closeConfirmationAlert.saveDraft" = "Spara utkast"; +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "Skapar autosparning …"; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "Versionen har lästs in"; + /* Saving draft to generate a preview (status message */ "postEditor.savingDraftForPreview" = "Sparar utkast …"; @@ -10338,12 +10350,21 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@, %2$@."; +/* Badge for post cells */ +"postList.badgePendingReview" = "Inväntar granskning"; + /* Cancels an Action */ "postList.cancel" = "Avbryt"; /* Delete option in the confirmation alert when deleting a page from the trash. */ "postList.deletePermanently.actionTitle" = "Ta bort permanent"; +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "Är du säker på att du vill ta bort ”%@” permanent?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "Appen laddar upp tidigare gjorda ändringar till servern. Försök igen senare."; + /* A generic error message title */ "postList.genericErrorMessage" = "Något gick fel"; @@ -10365,6 +10386,12 @@ Example: Reply to Pamela Nguyen */ /* Trash option in the trash post or page confirmation alert. */ "postList.trash.actionTitle" = "Flytta till papperskorgen"; +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "Är du säker på att du vill lägga %@ i papperskorgen? Ändringar som inte skickats till servern tidigare kommer att gå förlorade."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "Avbryt uppladdning"; + /* Close button in postMediaUploadStatusView */ "postMediaUploadStatusView.close" = "Stäng"; @@ -10374,6 +10401,9 @@ Example: Reply to Pamela Nguyen */ /* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ "postMediaUploadStatusView.progress" = "%1$@ av %2$@"; +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "Försök ladda upp igen"; + /* Retry upload button in postMediaUploadStatusView */ "postMediaUploadStatusView.retryUploads" = "Försök ladda upp igen"; @@ -10383,6 +10413,9 @@ Example: Reply to Pamela Nguyen */ /* A generic error message title */ "postNotice.errorTitle" = "Ett fel uppstod"; +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "Appen laddar upp tidigare gjorda ändringar till servern. Försök igen senare."; + /* Button OK */ "postNotice.ok" = "OK"; @@ -10419,6 +10452,12 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "Visa"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "Innehållet ändrades på annan enhet"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "”%@” togs bort permanent och kan inte längre uppdateras"; + /* A default value for an post without a title */ "postSaveErrorMessage.postUntitled" = "Utan rubrik"; @@ -10434,6 +10473,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "Alternativ för utvald bild"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "Inväntar granskning"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Automatisk delning på Twitter är inte längre tillgängligt"; @@ -10443,9 +10485,15 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "Det gick inte att uppdatera inläggsinställningarna"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "Endast synlig för webbplatsadministratörer och redigerare."; + /* Title for a 'Private' privacy setting */ "postVisibility.private.title" = "Privat"; +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "Synlig för alla men kräver ett lösenord"; + /* Title for a 'Password Protected' privacy setting */ "postVisibility.protected.title" = "Lösenordsskyddat"; @@ -10560,6 +10608,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "Jetpack Social"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "%@ objekt kunde inte laddas upp"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.mediaUploadFailedTitle" = "Misslyckades att ladda upp media"; @@ -10639,6 +10690,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "Etiketter"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "%@ objekt återstår"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "%@ objekt återstår"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "Laddar upp media"; @@ -10651,6 +10708,9 @@ Tapping on this row allows the user to edit the sharing message. */ /* Post publish date picker title for date cell */ "publishDatePicker.date" = "Publiceringsdatum"; +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "Datumet i din nuvarande tidszon:"; + /* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ "publishDatePicker.immediately" = "Omedelbart"; @@ -10864,6 +10924,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "Läsarinställningar"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "Lägg till en blogg"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "Upptäck bloggar"; @@ -10889,7 +10952,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "Gå till prenumerationer"; /* No Tags View Button Label */ -"reader.no.tags.title" = "Lägg till en blogg"; +"reader.no.tags.title" = "Lägg till en etikett"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Kan inte blockera blogg"; @@ -11136,6 +11199,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "Lägg till en etikett"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "Gilla"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "Gillat"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "Upptäck fler etiketter"; @@ -11436,6 +11505,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* All sites section title for site switcher. */ "site_switcher.all_sites_section.title" = "Alla webbplatser"; +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "Fastnålade webbplatser"; + /* Recents section title for site switcher. */ "site_switcher.recents_section.title" = "Senaste webbplatser"; @@ -11598,6 +11670,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Ditt senaste inlägg %1$@ har fått %2$@ gillamarkering."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Totalt antal WordPress.com-prenumeranter: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Totalt antal e-postprenumeranter: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Totalt antal prenumeranter"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; @@ -11616,6 +11697,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A title for table's column that shows a number of times a post was opened from an email */ "stats.subscribers.emailsSummary.column.clicks" = "Klick"; +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "Öppnade"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "Senaste e-postmeddelanden"; + /* Stats 'Emails' card header */ "stats.subscribers.emailsSummaryCard.title" = "E-post"; @@ -11658,9 +11745,15 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "Sök efter gratis foton att lägga till i ditt mediabibliotek!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "Du kan valfritt inkludera din e-post och ditt användarnamn för att hjälpa oss att förstå din upplevelse."; + /* Label we show on an email input field */ "submit.feedback.alert.empty.email" = "ingen e-post angiven"; +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "inget användarnamn angivet"; + /* Alert submit option for users to accept sharing their email and name when submitting feedback. */ "submit.feedback.alert.submit" = "Klar"; @@ -11670,10 +11763,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Skicka"; -/* Notice informing user that their feedback is being submitted anonymously. - Notice informing user that their feedback is being submitted. */ +/* Notice informing user that their feedback is being submitted. */ "submit.feedback.submit.loading" = "Skickar"; +/* Notice informing user that their feedback is being submitted anonymously. */ +"submit.feedback.submitAnonymously.loading" = "Skickar anonymt"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Feedback"; @@ -11845,8 +11940,22 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "ej läst"; -/* Site Email Subscribers - Site Subscribers */ +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "Webbplatsens e-postprenumerant"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "Webbplatsens prenumerant"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "Webbplatsens användare"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "Webbplatsens tittare"; + +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "E-postprenumeranter"; + +/* Site Subscribers */ "users.list.title.subscribers" = "Prenumeranter"; /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ diff --git a/WordPress/Resources/tr.lproj/Localizable.strings b/WordPress/Resources/tr.lproj/Localizable.strings index 4e40025ad6a4..801fcf6d0cd4 100644 --- a/WordPress/Resources/tr.lproj/Localizable.strings +++ b/WordPress/Resources/tr.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-23 15:54:08+0000 */ +/* Translation-Revision-Date: 2024-05-07 13:54:08+0000 */ /* Plural-Forms: nplurals=2; plural=n > 1; */ /* Generator: GlotPress/4.0.1 */ /* Language: tr */ @@ -8853,6 +8853,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "ses dosyası"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "İptal Et"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "Farklı bir cihazdan bu gönderide kaydedilmeyen değişiklikler yaptınız. Düzenlendi: %@."; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "Otomatik Kaydetme Kullanılabilir"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "Değişiklikleri Görüntüleyin"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "Görsel yüklenemedi. Lütfen başka bir tane seçin ya da daha sonra yeniden deneyin."; @@ -10305,6 +10317,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "Yazı çöpe atılıyor..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "Değişiklikleri İptal Et"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "Taslaktan Vazgeç"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "Düzenlemeye Devam Et"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "Taslağı Kaydet"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "Otomatik kayıt oluşturuluyor..."; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "Revizyon yüklendi"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "Taslak kaydediliyor..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "%@ tarafından."; @@ -10317,6 +10350,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@, %2$@."; +/* Badge for post cells */ +"postList.badgePendingReview" = "İnceleme bekliyor"; + +/* Cancels an Action */ +"postList.cancel" = "İptal Et"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "Kalıcı Olarak Sil"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "\"%@\" sayfasını kalıcı olarak silmek istediğinizden emin misiniz?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "Uygulama, sunucuda önceden yapılan değişiklikleri yüklüyor. Lütfen daha sonra tekrar deneyin."; + +/* A generic error message title */ +"postList.genericErrorMessage" = "Bir sorun oluştu"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "Çevrimdışı değişiklikler"; @@ -10332,6 +10383,42 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "Görüntüle"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "Çöp Kutusuna Taşı"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "\"%@\" öğesini çöp kutusuna atmak istediğinizden emin misiniz? Daha önceden sunucuya gönderilmeyen tüm değişiklikler kaybolur."; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "Yüklemeyi İptal Et"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "Kapat"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "Bekleyen yükleme yok"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "%1$@\/%2$@"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "Yüklemeyi Yeniden Dene"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "Yüklemeleri Yeniden Dene"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "Ortam Yüklemeleri"; + +/* A generic error message title */ +"postNotice.errorTitle" = "Bir hata oluştu"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "Uygulama, sunucuda önceden yapılan değişiklikleri yüklüyor. Lütfen daha sonra tekrar deneyin."; + +/* Button OK */ +"postNotice.ok" = "Tamam"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "Sayfa taslağı karşıya yüklendi"; @@ -10365,6 +10452,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "Görüntüle"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "Bu içerik başka bir cihazda değiştirilmiş"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "\"%@\" kalıcı olarak silindi ve artık güncellenemez"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "Başlıksız"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "Kapat"; @@ -10377,6 +10473,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "Öne çıkarılmış görsel seçenekleri"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "İnceleme bekliyor"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "X otomatik paylaşım özelliği artık kullanılamıyor"; @@ -10386,6 +10485,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "Yazı ayarları güncellenemedi"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "Yalnızca site yöneticileri ve düzenleyicileri tarafından görülebilir"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "Gizli"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "Herkes tarafından görülebilir ancak şifre gerektirir"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "Şifre korumalı"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "Herkes tarafından görülebilir"; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "Herkese açık"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "İptal Et"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "Görünürlük"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "Şifre"; + +/* Button save */ +"postVisibilityPicker.save" = "Kaydet"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "Blaze ile tanıt"; @@ -10410,6 +10539,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "Ön izleme"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "Yayımla"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "Yeniden dene"; @@ -10476,6 +10608,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "Jetpack Social"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "%@ öğe karşıya yüklenemedi"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "Başlık"; @@ -10485,6 +10620,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "Yayınlanma tarihi"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "Hemen"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "Şuraya yayınlanıyor"; @@ -10549,6 +10687,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "Etiketler"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "%@ öğe kaldı"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "%@ öğe kaldı"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "Ortam dosyası yükleniyor"; @@ -10558,6 +10702,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "Tüm yanıtları görüntüle"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "Yayımlama Tarihi"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "Mevcut saat diliminizdeki tarih:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "Hemen"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "Yayımlama Tarihini Kaldır"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "Yayımlama Tarihi Seç"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "Saat Dilimi"; + /* Post publish date picker */ "publishDatePicker.title" = "Yayınlanma tarihi"; @@ -10759,6 +10921,9 @@ with the filter chip button. */ /* Reader settings button accessibility label. */ "reader.navigation.settings.button.label" = "Okuyucu ayarları"; +/* No Tags View Button Label */ +"reader.no.blog.title" = "Blog ekle"; + /* Title for button on the no followed blogs result screen */ "reader.no.blogs.button" = "Blogları keşfedin"; @@ -10784,7 +10949,7 @@ with the filter chip button. */ "reader.no.results.subscriptions.button" = "Aboneliklere git"; /* No Tags View Button Label */ -"reader.no.tags.title" = "Blog ekle"; +"reader.no.tags.title" = "Etiket ekle"; /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "Blog engellenemedi"; @@ -11025,6 +11190,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "Etiket ekle"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "Beğen"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "Beğenildi"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "Daha fazla etiket keşfet"; @@ -11127,6 +11298,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "İlgili yazılar"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "Başka cihaz"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "Mevcut cihaz"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "Bu gönderi başka bir cihazda değiştirilmiş. Lütfen gönderinin hangi versiyonunu korumak istediğinizi seçin."; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "İptal Et"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "Kaydet"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "Çakışmayı Çözün"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "Bağlantıyı Kopyala"; @@ -11304,6 +11493,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "Bilinmiyor"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "Tüm siteler"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "Sabitlenen siteler"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "Son siteler"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "Site ekleyin"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "İptal Et"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "Tamam"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "Düzenle"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "Bir site seçin"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "Hatırlatıcılar"; @@ -11340,6 +11553,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "X otomatik paylaşım özelliği, X koşullarındaki ve fiyatlarındaki değişiklikler nedeniyle artık kullanılmıyor."; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "Aboneler"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "Trafik"; @@ -11436,18 +11652,57 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "Yeterli etkinlik yok. Bir süre sonra sitenizin daha fazla ziyaretçisi olduğunda yeniden bakın!"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "Aboneler"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "Son yazınız %1$@ %2$@ beğeni aldı."; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "Son yazınız %1$@ %2$@ beğeni aldı."; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "Toplam WordPress.com Abonesi: %@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "Toplam E-posta Abonesi: %@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "Toplam Abone"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "Bugün"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "Şu tarihten beri abone"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "Ad"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "Aboneler"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "Tıklamalar"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "Açmalar"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "En son e-postalar"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "E-postalar"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "Aboneler"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "%1$@, %2$@ ve %3$@ gösteren tablo"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "Seçilmiş dönemdeki yorumları gösteren çubuk grafik."; @@ -11460,6 +11715,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "Seçilmiş dönemdeki ziyaretçileri gösteren çubuk grafik."; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "Gün"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "Ay"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "Hafta"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "Yıl"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "Kapat"; @@ -11469,9 +11736,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "Ortam kitaplığınıza eklemek için ücretsiz fotoğraflar arayın!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "Deneyiminizi anlamamıza yardımcı olmak için isteğe bağlı olarak e-posta adresinizi veya kullanıcı adınızı ekleyebilirsiniz."; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "e-posta adresi girilmedi"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "kullanıcı adı girilmedi"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "Tamam"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "Geri bildiriminiz için teşekkürler"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "Gönder"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "Gönderiliyor"; + +/* Notice informing user that their feedback is being submitted anonymously. */ +"submit.feedback.submitAnonymously.loading" = "Anonim olarak gönderiliyor"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "Geri bildirim"; @@ -11643,6 +11931,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "okunmamış"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "Sitenin E-posta Abonesi"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "Sitenin Abonesi"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "Sitenin Kullanıcısı"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "Sitenin Görüntüleyicileri"; + +/* Site Email Subscribers */ +"users.list.title.emailSubscribers" = "E-posta Aboneleri"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "Aboneler"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "belgeler sayfamıza bakın"; diff --git a/WordPress/Resources/zh-Hans.lproj/Localizable.strings b/WordPress/Resources/zh-Hans.lproj/Localizable.strings index 37328e38de77..53d7ed3b1d14 100644 --- a/WordPress/Resources/zh-Hans.lproj/Localizable.strings +++ b/WordPress/Resources/zh-Hans.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-23 09:54:08+0000 */ +/* Translation-Revision-Date: 2024-05-02 13:54:07+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.1 */ /* Language: zh_CN */ @@ -8850,6 +8850,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "音频文件"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "取消"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "您在其他设备上对该文章进行的更改未保存。 已编辑:%@。"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "支持自动保存"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "查看更改"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "无法加载图片。 请选择其他图片或稍后重试。"; @@ -10287,6 +10299,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "正在将文章移至回收站..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "放弃更改"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "放弃草稿"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "继续编辑"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "保存草稿"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "正在创建自动保存……"; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "修订内容已加载"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "正在保存草稿……"; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "作者是 %@。"; @@ -10299,6 +10332,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@,%2$@。"; +/* Badge for post cells */ +"postList.badgePendingReview" = "待审核"; + +/* Cancels an Action */ +"postList.cancel" = "取消"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "永久删除"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "是否确定要永久删除此 \"%@?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "该应用程序正在将此前所做的更改上传至服务器。 请稍后重试。"; + +/* A generic error message title */ +"postList.genericErrorMessage" = "出错了"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "离线更改"; @@ -10314,6 +10365,42 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "查看"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "移至回收站"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "是否确定要将 \"%@\" 移至回收站? 此前未发送到服务器的任何更改都将丢失。"; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "取消上传"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "关闭"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "无待处理的上传"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "第 %1$@ 个,共 %2$@ 个"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "重试上传"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "重试上传"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "媒体上传"; + +/* A generic error message title */ +"postNotice.errorTitle" = "出错了"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "该应用程序正在将此前所做的更改上传至服务器。 请稍后重试。"; + +/* Button OK */ +"postNotice.ok" = "确定"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "页面草稿已上传"; @@ -10347,6 +10434,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "查看"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "在其他设备上修改过此内容。"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "\"%@\" 已被永久删除,无法再更新"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "无标题"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "忽略"; @@ -10359,6 +10455,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "特色图片选项"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "待审核"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Twitter 自动共享功能已无法使用"; @@ -10368,6 +10467,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "更新文章设置失败"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "仅站点管理员和编辑可见"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "私人"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "所有人可见,但需要密码"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "受密码保护"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "所有人可见"; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "公开"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "取消"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "可见性"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "密码"; + +/* Button save */ +"postVisibilityPicker.save" = "保存"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "大力宣传并推广"; @@ -10392,6 +10521,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "预览"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "发布"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "重试"; @@ -10458,6 +10590,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.jetpackSocial" = "Jetpack Social"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "%@ 个项目上传失败"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "标题"; @@ -10467,6 +10602,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "发布日期"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "立即"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "正在发布到"; @@ -10531,6 +10669,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "标签"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "剩余 %@ 个项目"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "剩余 %@ 个项目"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "上传媒体"; @@ -10540,6 +10684,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "查看所有回复"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "发布日期"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "您当前时区的日期:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "立即"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "删除发布日期"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "选择发布日期"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "时区"; + /* Post publish date picker */ "publishDatePicker.title" = "发布日期"; @@ -10765,9 +10927,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "前往“订阅”"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "添加博客"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "无法阻止博客"; @@ -11007,6 +11166,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "添加标签"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "点赞"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "已点赞"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "发现更多标签"; @@ -11109,6 +11274,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "相关文章"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "其他设备"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "当前设备"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "在其他设备上修改过此文章。 请选择要保留的文章版本。"; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "取消"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "保存"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "解决冲突"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "复制链接"; @@ -11286,6 +11469,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "未知"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "所有站点"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "固定站点"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "最近访问的站点"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "添加站点"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "取消"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "完成"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "编辑"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "选择站点"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "提醒"; @@ -11322,6 +11529,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "由于 Twitter 更改了条款和定价,Twitter 自动共享功能已无法使用。"; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "订阅者"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "流量"; @@ -11418,15 +11628,54 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "动态数量不足。 请在您的站点拥有更多访客时回来查看!"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "订阅者"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "您的最新文章 %1$@ 收到了 %2$@ 个赞。"; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "您的最新文章 %1$@ 收到了 %2$@ 个赞。"; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "WordPress.com 订阅者总数:%@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "电子邮件订阅者总数:%@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "订阅者总数"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "成为订阅者的时间"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "名称"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "订阅者"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "点击次数"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "打开次数"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "最近的电子邮件"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "电子邮件"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "订阅者"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "显示 %1$@、%2$@ 和 %3$@ 的表格"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "描绘所选时间段内评论数的条形图。"; @@ -11439,6 +11688,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "描绘所选时间段内访客数的条形图。"; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "日"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "月"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "周"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "年"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "忽略"; @@ -11448,9 +11709,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "搜索查找要添加到您的媒体库中的免费照片!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "您可以选填您的电子邮件地址和用户名,以帮助我们了解您的体验。"; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "未输入电子邮件地址"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "未输入用户名"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "完成"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "感谢您的反馈"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "提交"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "正在发送"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "反馈"; @@ -11622,6 +11901,21 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "未读"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "站点的电子邮件订阅者"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "站点订阅者"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "站点用户"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "站点访客"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "订阅者"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "访问我们的文档页面"; diff --git a/WordPress/Resources/zh-Hant.lproj/Localizable.strings b/WordPress/Resources/zh-Hant.lproj/Localizable.strings index 885f2efd73d1..4658089d6afc 100644 --- a/WordPress/Resources/zh-Hant.lproj/Localizable.strings +++ b/WordPress/Resources/zh-Hant.lproj/Localizable.strings @@ -1,4 +1,4 @@ -/* Translation-Revision-Date: 2024-04-23 09:54:08+0000 */ +/* Translation-Revision-Date: 2024-05-02 13:54:07+0000 */ /* Plural-Forms: nplurals=1; plural=0; */ /* Generator: GlotPress/4.0.1 */ /* Language: zh_TW */ @@ -8847,6 +8847,18 @@ translators: %s: Select control option value e.g: \"Auto, 25%\". */ /* translators: displays audio file extension. e.g. MP3 audio file */ "audio file" = "音訊檔案"; +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.cancel" = "取消"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.message" = "這篇文章在不同裝置有未儲存的變更。 已編輯:%@。"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.title" = "可使用自動儲存"; + +/* An alert suggesting to load autosaved revision for a published post */ +"autosaveAlert.viewChanges" = "檢視變更"; + /* Alert message when something goes wrong with the selected image. */ "avatarMenu.failedToSetAvatarAlertMessage" = "無法載入圖片。 請選擇其他圖片,或稍後再試。"; @@ -10296,6 +10308,27 @@ Example: Reply to Pamela Nguyen */ /* Status mesasge for post cells */ "post.movingToTrashStatusMessage" = "正在將文章移至垃圾桶..."; +/* Button in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.discardChanges" = "捨棄變更"; + +/* Button in an alert confirming discaring a new draft */ +"postEditor.closeConfirmationAlert.discardDraft" = "捨棄草稿"; + +/* Button to keep the changes in an alert confirming discaring changes */ +"postEditor.closeConfirmationAlert.keepEditing" = "繼續編輯"; + +/* Button in an alert confirming saving a new draft */ +"postEditor.closeConfirmationAlert.saveDraft" = "儲存草稿"; + +/* Creating autosave to generate a preview (status message */ +"postEditor.creatingAutosaveForPreview" = "正在建立自動儲存..."; + +/* Title for a snackbar */ +"postEditor.revisionLoaded" = "修訂版本已經載入"; + +/* Saving draft to generate a preview (status message */ +"postEditor.savingDraftForPreview" = "正在儲存草稿..."; + /* Accessibility label for the post author in the post list. The parameter is the author name. For example, \"By Elsa.\" */ "postList.a11y.authorChunkFormat" = "作者:%@。"; @@ -10308,6 +10341,24 @@ Example: Reply to Pamela Nguyen */ /* Accessibility label for a post in the post list. The first placeholder is the post title. The second placeholder is the date. */ "postList.a11y.titleAndDateChunkFormat" = "%1$@,%2$@。"; +/* Badge for post cells */ +"postList.badgePendingReview" = "待審中"; + +/* Cancels an Action */ +"postList.cancel" = "取消"; + +/* Delete option in the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.actionTitle" = "永久刪除"; + +/* Message of the confirmation alert when deleting a page from the trash. */ +"postList.deletePermanently.alertMessage" = "你確定要永久刪除「%@」?"; + +/* An error message */ +"postList.errorUnsyncedChangesMessage" = "應用程式正在將先前的變更上傳至伺服器。 請稍後再試。"; + +/* A generic error message title */ +"postList.genericErrorMessage" = "發生錯誤"; + /* Label for a post in the post list. Indicates that the post has offline changes. */ "postList.offlineChanges" = "離線變更"; @@ -10323,6 +10374,42 @@ Example: Reply to Pamela Nguyen */ /* Title for the 'View' post list row swipe action */ "postList.swipeActionView" = "檢視"; +/* Trash option in the trash post or page confirmation alert. */ +"postList.trash.actionTitle" = "移至垃圾桶"; + +/* Message of the trash post or page confirmation alert. */ +"postList.trash.alertMessage" = "你確定要將「%@」移至垃圾桶? 將遺失所有先前未傳送至伺服器的變更。"; + +/* Cancel (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.cancelUpload" = "取消上傳"; + +/* Close button in postMediaUploadStatusView */ +"postMediaUploadStatusView.close" = "關閉"; + +/* Placeholder text in postMediaUploadStatusView when no uploads remain */ +"postMediaUploadStatusView.noPendingUploads" = "沒有待上傳內容"; + +/* Shows the upload progress with two preformatted parameters: %1$@ is the placeholder for completed bytes, and %2$@ is the placeholder for total bytes */ +"postMediaUploadStatusView.progress" = "已完成 %1$@,共 %2$@"; + +/* Retry (single) upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUpload" = "重試上傳"; + +/* Retry upload button in postMediaUploadStatusView */ +"postMediaUploadStatusView.retryUploads" = "重試上傳"; + +/* Title for post media upload status view */ +"postMediaUploadStatusView.title" = "媒體上傳內容"; + +/* A generic error message title */ +"postNotice.errorTitle" = "發生錯誤"; + +/* An error message */ +"postNotice.errorUnsyncedChangesMessage" = "應用程式正在將先前的變更上傳至伺服器。 請稍後再試。"; + +/* Button OK */ +"postNotice.ok" = "確定"; + /* Title of notification displayed when a page has been successfully saved as a draft. */ "postNotice.pageDraftCreated" = "已上傳頁面草稿"; @@ -10356,6 +10443,15 @@ Example: Reply to Pamela Nguyen */ /* Button title. Displays a summary / sharing screen for a specific post. */ "postNotice.view" = "檢視"; +/* Error message: content was modified on another device */ +"postSaveErrorMessage.conflict" = "此內容已在其他裝置修改"; + +/* Error message: item permanently deleted */ +"postSaveErrorMessage.deleted" = "已永久刪除「%@」且無法更新"; + +/* A default value for an post without a title */ +"postSaveErrorMessage.postUntitled" = "無標題"; + /* User action to dismiss featured media options. */ "postSettings.featuredImageUploadActionSheet.dismiss" = "關閉"; @@ -10368,6 +10464,9 @@ Example: Reply to Pamela Nguyen */ /* Title for action sheet with featured media options. */ "postSettings.featuredImageUploadActionSheet.title" = "精選圖片選項"; +/* The 'Pending Review' setting of the post */ +"postSettings.pendingReview" = "待審中"; + /* Section title for the disabled Twitter service in the Post Settings screen */ "postSettings.section.disabledTwitter.header" = "Twitter 自動分享功能已無法使用"; @@ -10377,6 +10476,36 @@ Example: Reply to Pamela Nguyen */ /* Error message on post/page settings screen */ "postSettings.updateFailedMessage" = "無法更新文章設定"; +/* Details for a 'Private' privacy setting */ +"postVisibility.private.details" = "僅網站管理員及編輯者可檢視"; + +/* Title for a 'Private' privacy setting */ +"postVisibility.private.title" = "私人"; + +/* Details for a 'Password Protected' privacy setting */ +"postVisibility.protected.details" = "所有人都可檢視但需要密碼"; + +/* Title for a 'Password Protected' privacy setting */ +"postVisibility.protected.title" = "受密碼保護"; + +/* Details for a 'Public' (default) privacy setting */ +"postVisibility.public.details" = "所有人都能檢視"; + +/* Title for a 'Public' (default) privacy setting */ +"postVisibility.public.title" = "公開"; + +/* Button cancel */ +"postVisibilityPicker.cancel" = "取消"; + +/* Navigation bar title for the Post Visibility picker */ +"postVisibilityPicker.navigationTitle" = "可見度"; + +/* Password placeholder text */ +"postVisibilityPicker.password" = "密碼"; + +/* Button save */ +"postVisibilityPicker.save" = "儲存"; + /* Promote the post with Blaze. */ "posts.blaze.actionTitle" = "使用 Blaze 進行宣傳"; @@ -10401,6 +10530,9 @@ Example: Reply to Pamela Nguyen */ /* Label for the preview post button. Tapping displays the post as it appears on the web. */ "posts.preview.actionTitle" = "預覽"; +/* Label for the publish post button. */ +"posts.publish.actionTitle" = "發表"; + /* Retry uploading the post. */ "posts.retry.actionTitle" = "重試"; @@ -10464,6 +10596,9 @@ Example: Reply to Pamela Nguyen */ /* Voiceover accessibility label informing the user that this button dismiss the current view */ "prepublishing.close" = "關閉"; +/* Details for a publish button state in the pre-publishing sheet; count as a parameter */ +"prepublishing.mediaUploadFailedDetails" = "無法上傳 %@ 個項目"; + /* Placeholder for a cell in the pre-publishing sheet */ "prepublishing.postTitle" = "標題"; @@ -10473,6 +10608,9 @@ Example: Reply to Pamela Nguyen */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.publishDate" = "發表日期"; +/* Placeholder value for a publishing date in the prepublishing sheet when the date is not selected */ +"prepublishing.publishDateImmediately" = "立即"; + /* Label in the header in the pre-publishing sheet */ "prepublishing.publishingTo" = "發表至"; @@ -10537,6 +10675,12 @@ Tapping on this row allows the user to edit the sharing message. */ /* Label for a cell in the pre-publishing sheet */ "prepublishing.tags" = "標籤"; +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaManyItemsRemaining" = "剩下 %@ 個項目"; + +/* Details label for a publish button state in the pre-publishing sheet */ +"prepublishing.uploadMediaOneItemRemaining" = "剩下 %@ 個項目"; + /* Title for a publish button state in the pre-publishing sheet */ "prepublishing.uploadingMedia" = "正在上傳媒體"; @@ -10546,6 +10690,24 @@ Tapping on this row allows the user to edit the sharing message. */ /* Title for a tappable string that opens the reader with a prompts tag */ "prompts.card.viewprompts.title" = "檢視所有回應"; +/* Post publish date picker title for date cell */ +"publishDatePicker.date" = "發表日期"; + +/* Post publish date picker footer view when the selected date time zone is different from your current time zone; followed by the time in the current time zone. */ +"publishDatePicker.footerCurrentTimezone" = "你目前時區的日期:"; + +/* Post publish date picker: selected value placeholder when no date is selected and the post will be published immediately */ +"publishDatePicker.immediately" = "立即"; + +/* Title for button in publish date picker */ +"publishDatePicker.removePublishDate" = "移除發表日期"; + +/* Title for button in publish date picker */ +"publishDatePicker.selectPublishDate" = "選取發表日期"; + +/* Post publish time zone cell title */ +"publishDatePicker.timeZone" = "時區"; + /* Post publish date picker */ "publishDatePicker.title" = "發表日期"; @@ -10771,9 +10933,6 @@ with the filter chip button. */ /* Button title. Tapping lets the user view the blogs they're subscribed to. */ "reader.no.results.subscriptions.button" = "前往「訂閱」"; -/* No Tags View Button Label */ -"reader.no.tags.title" = "新增網誌"; - /* Notice title when blocking a blog fails. */ "reader.notice.blog.blocked.failure" = "無法封鎖網誌"; @@ -11010,6 +11169,12 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title of a feature to add a new tag to the tags subscribed by the user. */ "reader.tags.add.tag.title" = "新增標籤"; +/* Text for the 'Like' button on the reader tag cell. */ +"reader.tags.button.like" = "讚"; + +/* Text for the 'Liked' button on the reader tag cell. */ +"reader.tags.button.liked" = "已按讚"; + /* Button title. Tapping shows the Subscribe to Tags screen. */ "reader.tags.discover.more.tags" = "探索更多標籤"; @@ -11112,6 +11277,24 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for screen that allows configuration of your blog/site related posts settings. */ "relatedPostsSettings.title" = "相關文章"; +/* A version of the post on another device. */ +"resolveConflict.anotherDevice" = "其他裝置"; + +/* A version of the post on the current device. */ +"resolveConflict.currentDevice" = "目前裝置"; + +/* Description for the Resolve Conflict screen. */ +"resolveConflict.description" = "此文章已在其他裝置修改。 請選取要保存的文章版本。"; + +/* Title for the cancel button on the Resolve Conflict screen. */ +"resolveConflict.navigation.cancel" = "取消"; + +/* Title for the save button on the Resolve Conflict screen. */ +"resolveConflict.navigation.save" = "儲存"; + +/* Title for the Resolve Conflict screen. */ +"resolveConflict.navigation.title" = "解決衝突"; + /* Title for the \"Copy Link\" action in Share Sheet. */ "share.sheet.copy.link.title" = "複製連結"; @@ -11289,6 +11472,30 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Text for unknown privacy setting */ "siteVisibility.unknown.title" = "未知"; +/* All sites section title for site switcher. */ +"site_switcher.all_sites_section.title" = "所有網站"; + +/* Pinned section title for site switcher. */ +"site_switcher.pinned_section.title" = "已釘選的網站"; + +/* Recents section title for site switcher. */ +"site_switcher.recents_section.title" = "近期網站"; + +/* CTA title for the site switcher screen. */ +"site_switcher_cta_title" = "新增網站"; + +/* Dismiss button title above the search. */ +"site_switcher_dismiss_button_title" = "取消"; + +/* Done button title above the search. */ +"site_switcher_done_button_title" = "完成"; + +/* Edit button title above the search. */ +"site_switcher_edit_button_title" = "編輯"; + +/* Title for site switcher screen. */ +"site_switcher_title" = "選擇網站"; + /* Label for the blogging reminders setting */ "sitesettings.reminders.title" = "提醒"; @@ -11325,6 +11532,9 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* A smallprint that hints the reason behind why Twitter is deprecated. */ "social.twitterDeprecation.text" = "由於 Twitter 變更條款和定價,Twitter 自動分享功能已無法使用。"; +/* Title of Subscribers stats tab. */ +"stats.dashboard.tab.subscribers" = "訂閱者"; + /* Title of Traffic stats tab. */ "stats.dashboard.tab.traffic" = "流量"; @@ -11421,18 +11631,57 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Hint displayed on the 'Most Popular Time' stats card when a user's site hasn't yet received enough traffic. */ "stats.insights.mostPopularTime.noData" = "沒有足夠的活動。 有更多訪客造訪網站的時候,請再回來查看!"; +/* Insights 'Subscribers' header */ +"stats.insights.subscribers.title" = "訂閱者"; + /* A hint shown to the user in stats informing the user how many likes one of their posts has received. The %1$@ placeholder will be replaced with the title of a post, the %2$@ with the number of likes. */ "stats.insights.totalLikes.guideText.plural" = "你的最新文章 %1$@ 已獲得 %2$@ 次按讚。"; /* A hint shown to the user in stats informing the user that one of their posts has received a like. The %1$@ placeholder will be replaced with the title of a post, and the %2$@ will be replaced by the numeral one. */ "stats.insights.totalLikes.guideText.singular" = "你的最新文章 %1$@ 已獲得 %2$@ 次按讚。"; +/* Label displaying total number of WordPress.com subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.dotcomCount" = "WordPress.com 訂閱者總人數:%@"; + +/* Label displaying total number of email subscribers. %@ is the total. */ +"stats.insights.totalSubscribers.emailCount" = "電子郵件訂閱者總人數:%@"; + +/* Insights 'Total Subscribers' header */ +"stats.insights.totalSubscribers.title" = "訂閱者總人數"; + /* Text for the Stats Traffic Overview stat difference label. Shows the change from the previous period, including the percentage value. E.g.: +12.3K (5%). %1$@ is the placeholder for the change sign ('-', '+', or none). %2$@ is the placeholder for the change numerical value. %3$@ is the placeholder for the change percentage value. */ "stats.overview.differenceLabelWithNumber" = "%1$@%2$@ (%3$@)"; /* Stats 'Today' header */ "stats.period.todayCard.title" = "今天"; +/* Table column title that shows the date since the user became a subscriber. */ +"stats.section.dataSubtitles.subscriberSince" = "從以下時間開始訂閱:"; + +/* Table column title that shows the names of subscribers. */ +"stats.section.itemSubtitles.subscriber" = "名稱"; + +/* Stats 'Subscribers' card header, contains chart */ +"stats.subscribers.chart.title" = "訂閱者"; + +/* A title for table's column that shows a number of times a post was opened from an email */ +"stats.subscribers.emailsSummary.column.clicks" = "點擊數"; + +/* A title for table's column that shows a number of email openings */ +"stats.subscribers.emailsSummary.column.opens" = "開啟數"; + +/* A title for table's column that shows a name of an email */ +"stats.subscribers.emailsSummary.column.title" = "最新電子郵件"; + +/* Stats 'Emails' card header */ +"stats.subscribers.emailsSummaryCard.title" = "電子郵件"; + +/* Stats 'Subscribers' card header */ +"stats.subscribers.subscribersListCard.title" = "訂閱者"; + +/* Accessibility of stats table. Placeholders will be populated with names of data shown in table. */ +"stats.topTotalsCell.voiceOverDescription" = "顯示 %1$@、%2$@ 和 %3$@ 的表格"; + /* This description is used to set the accessibility label for the Stats Traffic chart, with Comments selected. */ "stats.traffic.accessibilityLabel.comments" = "顯示所選期間留言情況的長條圖。"; @@ -11445,6 +11694,18 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* This description is used to set the accessibility label for the Stats Traffic chart, with Visitors selected. */ "stats.traffic.accessibilityLabel.visitors" = "顯示所選期間訪客人數的長條圖。"; +/* The label for the option to show Stats Traffic chart for Days. */ +"stats.traffic.days" = "天"; + +/* The label for the option to show Stats Traffic chart for Months. */ +"stats.traffic.months" = "月"; + +/* The label for the option to show Stats Traffic chart for Weeks. */ +"stats.traffic.weeks" = "週"; + +/* The label for the option to show Stats Traffic chart for Years. */ +"stats.traffic.years" = "年"; + /* Dismiss the AlertView */ "stockPhotos.strings.dismiss" = "關閉"; @@ -11454,9 +11715,27 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Title for placeholder in Free Photos */ "stockPhotos.title" = "搜尋免費相片以新增至你的媒體庫!"; +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.description" = "你可以選擇納入電子郵件地址與使用者名稱,協助我們了解你的體驗。"; + +/* Label we show on an email input field */ +"submit.feedback.alert.empty.email" = "未輸入電子郵件地址"; + +/* Label we show on an name input field */ +"submit.feedback.alert.empty.username" = "未輸入使用者名稱"; + +/* Alert submit option for users to accept sharing their email and name when submitting feedback. */ +"submit.feedback.alert.submit" = "完成"; + +/* Alert users are shown when submtiting their feedback. */ +"submit.feedback.alert.title" = "感謝你的意見回饋"; + /* The button title for the Submit button in the In-App Feedback screen */ "submit.feedback.submit.button" = "提交"; +/* Notice informing user that their feedback is being submitted. */ +"submit.feedback.submit.loading" = "正在傳送"; + /* The title for the the In-App Feedback screen */ "submit.feedback.title" = "意見回饋"; @@ -11628,6 +11907,21 @@ Refer to: `reader.preferences.preview.body.feedback.format` */ /* Displayed in the confirmation alert when marking unread notifications as read. */ "unread" = "未讀"; +/* Site's Email Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.emailSubscriber" = "網站的電子郵件訂閱者"; + +/* Site's Subscriber Profile. Displayed when the name is empty! */ +"user.details.title.subscriber" = "網站的訂閱者"; + +/* Sites's User Profile. Displayed when the name is empty! */ +"user.details.title.user" = "網站的使用者"; + +/* Site's Viewers Profile. Displayed when the name is empty! */ +"user.details.title.viewer" = "網站的檢視者"; + +/* Site Subscribers */ +"users.list.title.subscribers" = "訂閱者"; + /* Portion of a message for Jetpack users that have multisite WP installation, thus Restore is not available. This part is a link, colored with a different color. */ "visit our documentation page" = "請造訪我們的文件頁面"; diff --git a/WordPress/UITests/Tests/PagesTests.swift b/WordPress/UITests/Tests/PagesTests.swift index f001b05dc546..30151f691033 100644 --- a/WordPress/UITests/Tests/PagesTests.swift +++ b/WordPress/UITests/Tests/PagesTests.swift @@ -25,8 +25,9 @@ class PageTests: XCTestCase { .enterTextInTitle(text: postTitle, postType: .page) .post(action: .publish, postType: .page) - try MySiteScreen() - .scrollToPagesCard() - .verifyPagePublished(title: postTitle) + // TODO: reimplement this part of the test (flaky) +// try MySiteScreen() +// .scrollToPagesCard() +// .verifyPagePublished(title: postTitle) } } diff --git a/WordPress/UITestsFoundation/Screens/PeopleScreen.swift b/WordPress/UITestsFoundation/Screens/PeopleScreen.swift index 77edfdac39e2..a63f9e5190df 100644 --- a/WordPress/UITestsFoundation/Screens/PeopleScreen.swift +++ b/WordPress/UITestsFoundation/Screens/PeopleScreen.swift @@ -3,10 +3,6 @@ import XCTest public class PeopleScreen: ScreenObject { - private let emailFilterButtonGetter: (XCUIApplication) -> XCUIElement = { - $0.buttons["email"] - } - private let followersFilterButtonGetter: (XCUIApplication) -> XCUIElement = { $0.buttons["followers"] } @@ -15,14 +11,12 @@ public class PeopleScreen: ScreenObject { $0.buttons["users"] } - var emailFilterButton: XCUIElement { emailFilterButtonGetter(app) } var followersFilterButton: XCUIElement { followersFilterButtonGetter(app) } var userFilterButton: XCUIElement { userFilterButtonGetter(app) } init(app: XCUIApplication = XCUIApplication()) throws { try super.init( expectedElementGetters: [ - emailFilterButtonGetter, followersFilterButtonGetter, userFilterButtonGetter ], diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index a05de5132526..a143b32e4373 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -202,6 +202,8 @@ 0189AF062ACAD89700F63393 /* ShoppingCartService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0189AF042ACAD89700F63393 /* ShoppingCartService.swift */; }; 018FF1352AE6771A00F301C3 /* LockScreenVerticalCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018FF1342AE6771A00F301C3 /* LockScreenVerticalCard.swift */; }; 018FF1372AE67C2600F301C3 /* LockScreenFlexibleCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018FF1362AE67C2600F301C3 /* LockScreenFlexibleCard.swift */; }; + 019105862BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019105852BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift */; }; + 019105872BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019105852BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift */; }; 019C5B8D2BD6570D00A69DB0 /* StatsSubscribersStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019C5B8C2BD6570D00A69DB0 /* StatsSubscribersStore.swift */; }; 019C5B8E2BD6570D00A69DB0 /* StatsSubscribersStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019C5B8C2BD6570D00A69DB0 /* StatsSubscribersStore.swift */; }; 019C5B942BD6917600A69DB0 /* StatsSubscribersCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 019C5B932BD6917600A69DB0 /* StatsSubscribersCache.swift */; }; @@ -222,6 +224,10 @@ 01B7590B2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B7590A2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift */; }; 01B7590C2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B7590A2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift */; }; 01B7590E2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B7590D2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift */; }; + 01C4472C2BE4F8560006F787 /* StatsGhostLineChartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C4472B2BE4F8560006F787 /* StatsGhostLineChartCell.swift */; }; + 01C4472D2BE4F8560006F787 /* StatsGhostLineChartCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C4472B2BE4F8560006F787 /* StatsGhostLineChartCell.swift */; }; + 01C591452BDBD63D0071515C /* StatsGhostTopCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C591442BDBD63D0071515C /* StatsGhostTopCell.swift */; }; + 01C591462BDBD63D0071515C /* StatsGhostTopCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C591442BDBD63D0071515C /* StatsGhostTopCell.swift */; }; 01CE5007290A889F00A9C2E0 /* TracksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */; }; 01CE5008290A88BD00A9C2E0 /* TracksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */; }; 01CE500E290A88C100A9C2E0 /* TracksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */; }; @@ -438,6 +444,8 @@ 0A9687BC28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */; }; 0C01A6EA2AB37F0F009F7145 /* SiteMediaCollectionCellSelectionOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C01A6E92AB37F0F009F7145 /* SiteMediaCollectionCellSelectionOverlayView.swift */; }; 0C01A6EB2AB37F0F009F7145 /* SiteMediaCollectionCellSelectionOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C01A6E92AB37F0F009F7145 /* SiteMediaCollectionCellSelectionOverlayView.swift */; }; + 0C02E6C52BE3B6E30055F0F6 /* PostTrashedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C02E6C42BE3B6E30055F0F6 /* PostTrashedOverlayView.swift */; }; + 0C02E6C62BE3B6E30055F0F6 /* PostTrashedOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C02E6C42BE3B6E30055F0F6 /* PostTrashedOverlayView.swift */; }; 0C03AECA2B7D995F00B64A25 /* PublishButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C03AEC92B7D995F00B64A25 /* PublishButton.swift */; }; 0C03AECB2B7D995F00B64A25 /* PublishButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C03AEC92B7D995F00B64A25 /* PublishButton.swift */; }; 0C0453282AC73343003079C8 /* SiteMediaVideoDurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C0453272AC73343003079C8 /* SiteMediaVideoDurationView.swift */; }; @@ -1494,7 +1502,6 @@ 59ECF87B1CB7061D00E68F25 /* PostSharingControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59ECF87A1CB7061D00E68F25 /* PostSharingControllerTests.swift */; }; 59FBD5621B5684F300734466 /* ThemeServiceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 59FBD5611B5684F300734466 /* ThemeServiceTests.m */; }; 5D1181E71B4D6DEB003F3084 /* WPStyleGuide+Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1181E61B4D6DEB003F3084 /* WPStyleGuide+Reader.swift */; }; - 5D13FA571AF99C2100F06492 /* PageListSectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5D13FA561AF99C2100F06492 /* PageListSectionHeaderView.xib */; }; 5D146EBB189857ED0068FDC6 /* FeaturedImageViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D146EBA189857ED0068FDC6 /* FeaturedImageViewController.m */; }; 5D1D04751B7A50B100CDE646 /* Reader.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5D1D04731B7A50B100CDE646 /* Reader.storyboard */; }; 5D1D04761B7A50B100CDE646 /* ReaderStreamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D1D04741B7A50B100CDE646 /* ReaderStreamViewController.swift */; }; @@ -2395,7 +2402,6 @@ 8B92D69627CD51FA001F5371 /* DashboardGhostCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B92D69527CD51FA001F5371 /* DashboardGhostCardCell.swift */; }; 8B92D69727CD51FA001F5371 /* DashboardGhostCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B92D69527CD51FA001F5371 /* DashboardGhostCardCell.swift */; }; 8B93412F257029F60097D0AC /* FilterChipButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B93412E257029F50097D0AC /* FilterChipButton.swift */; }; - 8B93856E22DC08060010BF02 /* PageListSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */; }; 8BA125EB27D8F5E4008B779F /* UIView+PinSubviewPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA125EA27D8F5E4008B779F /* UIView+PinSubviewPriority.swift */; }; 8BA125EC27D8F5E4008B779F /* UIView+PinSubviewPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BA125EA27D8F5E4008B779F /* UIView+PinSubviewPriority.swift */; }; 8BA77BCB2482C52A00E1EBBF /* ReaderCardDiscoverAttributionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8BA77BCA2482C52A00E1EBBF /* ReaderCardDiscoverAttributionView.xib */; }; @@ -2782,7 +2788,6 @@ 9A9E3FA3230D5F0A00909BC4 /* StatsStackViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9E3FA2230D5F0A00909BC4 /* StatsStackViewCell.swift */; }; 9A9E3FAD230E9DD000909BC4 /* StatsGhostCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9E3FAB230E9DD000909BC4 /* StatsGhostCells.swift */; }; 9A9E3FAE230E9DD000909BC4 /* StatsGhostTwoColumnCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A9E3FAC230E9DD000909BC4 /* StatsGhostTwoColumnCell.xib */; }; - 9A9E3FB0230EA7A300909BC4 /* StatsGhostTopCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A9E3FAF230EA7A300909BC4 /* StatsGhostTopCell.xib */; }; 9A9E3FB2230EB74300909BC4 /* StatsGhostTabbedCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A9E3FB1230EB74300909BC4 /* StatsGhostTabbedCell.xib */; }; 9A9E3FB4230EC4F700909BC4 /* StatsGhostPostingActivityCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A9E3FB3230EC4F700909BC4 /* StatsGhostPostingActivityCell.xib */; }; 9AA0ADB1235F11700027AB5D /* AsyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA0ADB0235F116F0027AB5D /* AsyncOperation.swift */; }; @@ -2824,6 +2829,10 @@ B038A81F2BD70FCA00763731 /* StatsSubscribersChartCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B038A81B2BD70FCA00763731 /* StatsSubscribersChartCell.xib */; }; B038A8212BD7164F00763731 /* StatsSubscribersLineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = B038A8202BD7164F00763731 /* StatsSubscribersLineChart.swift */; }; B038A8222BD7164F00763731 /* StatsSubscribersLineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = B038A8202BD7164F00763731 /* StatsSubscribersLineChart.swift */; }; + B038BD152BE1A37D009FD7E9 /* SubscribersChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B038BD142BE1A37D009FD7E9 /* SubscribersChartMarker.swift */; }; + B038BD162BE1A37D009FD7E9 /* SubscribersChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B038BD142BE1A37D009FD7E9 /* SubscribersChartMarker.swift */; }; + B038BD182BE1A3CC009FD7E9 /* StatsChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B038BD172BE1A3CC009FD7E9 /* StatsChartMarker.swift */; }; + B038BD192BE1A3CC009FD7E9 /* StatsChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B038BD172BE1A3CC009FD7E9 /* StatsChartMarker.swift */; }; B03B9234250BC593000A40AF /* SuggestionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03B9233250BC593000A40AF /* SuggestionService.swift */; }; B03B9236250BC5FD000A40AF /* Suggestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = B03B9235250BC5FD000A40AF /* Suggestion.swift */; }; B0637527253E7CEC00FD45D2 /* SuggestionsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0637526253E7CEB00FD45D2 /* SuggestionsTableView.swift */; }; @@ -3465,7 +3474,6 @@ E1B912811BB00EFD003C25B9 /* People.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E1B912801BB00EFD003C25B9 /* People.storyboard */; }; E1B912831BB01047003C25B9 /* PeopleRoleBadgeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B912821BB01047003C25B9 /* PeopleRoleBadgeLabel.swift */; }; E1B912891BB01288003C25B9 /* PeopleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B912881BB01288003C25B9 /* PeopleViewController.swift */; }; - E1B9128B1BB0129C003C25B9 /* WPStyleGuide+People.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B9128A1BB0129C003C25B9 /* WPStyleGuide+People.swift */; }; E1B9128F1BB05B1D003C25B9 /* PeopleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B912841BB01266003C25B9 /* PeopleCell.swift */; }; E1B921BC1C0ED5A3003EA3CB /* MediaSizeSliderCellTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B921BB1C0ED5A3003EA3CB /* MediaSizeSliderCellTest.swift */; }; E1BB92321FDAAFFA00F2D817 /* TextWithAccessoryButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BB92311FDAAFFA00F2D817 /* TextWithAccessoryButtonCell.swift */; }; @@ -4193,7 +4201,6 @@ FABB1FD22602FC2C00C8785C /* RestoreStatusView.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA1A55FE25A6F07F0033967D /* RestoreStatusView.xib */; }; FABB1FD32602FC2C00C8785C /* PluginDetailViewHeaderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4058F4191FF40EE1000D5559 /* PluginDetailViewHeaderCell.xib */; }; FABB1FD42602FC2C00C8785C /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 406A0EEF224D39C50016AD6A /* Flags.xcassets */; }; - FABB1FD52602FC2C00C8785C /* StatsGhostTopCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9A9E3FAF230EA7A300909BC4 /* StatsGhostTopCell.xib */; }; FABB1FD62602FC2C00C8785C /* CustomizeInsightsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 985793C722F23D7000643DBF /* CustomizeInsightsCell.xib */; }; FABB1FD92602FC2C00C8785C /* oswald_upper.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F5A34D0625DF2F7700C9654B /* oswald_upper.ttf */; }; FABB1FDB2602FC2C00C8785C /* xhtmlValidatorTemplate.xhtml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAE97080E33B21600CA8540 /* xhtmlValidatorTemplate.xhtml */; }; @@ -4233,7 +4240,6 @@ FABB201D2602FC2C00C8785C /* PostStatsTitleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98FCFC222231DF43006ECDD4 /* PostStatsTitleCell.xib */; }; FABB20212602FC2C00C8785C /* ReaderDetailToolbar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8BA77BCC248340CE00E1EBBF /* ReaderDetailToolbar.xib */; }; FABB20222602FC2C00C8785C /* RewindStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4070D75B20E5F55A007CEBDA /* RewindStatusTableViewCell.xib */; }; - FABB20232602FC2C00C8785C /* PageListSectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5D13FA561AF99C2100F06492 /* PageListSectionHeaderView.xib */; }; FABB20242602FC2C00C8785C /* JetpackScanThreatDetailsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA7AA4A625BFE0A9005E7200 /* JetpackScanThreatDetailsViewController.xib */; }; FABB20252602FC2C00C8785C /* CollabsableHeaderFilterCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 469EB16424D8B12700C764CB /* CollabsableHeaderFilterCollectionViewCell.xib */; }; FABB20262602FC2C00C8785C /* ReplyTextView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54E1DEF1A0A7BAA00807537 /* ReplyTextView.xib */; }; @@ -4515,7 +4521,6 @@ FABB21982602FC2C00C8785C /* UIViewController+Notice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3EFCA2208E308900268758 /* UIViewController+Notice.swift */; }; FABB21992602FC2C00C8785C /* GutenbergFileUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0462152566938300EB98EF /* GutenbergFileUploadProcessor.swift */; }; FABB219A2602FC2C00C8785C /* EventLoggingDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F913BB0F24B3C5CE00C19032 /* EventLoggingDataProvider.swift */; }; - FABB219B2602FC2C00C8785C /* PageListSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */; }; FABB219C2602FC2C00C8785C /* InviteLinks+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = E690F6ED25E05D170015A777 /* InviteLinks+CoreDataClass.swift */; }; FABB219D2602FC2C00C8785C /* Page+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59E1D46D1CEF77B500126697 /* Page+CoreDataProperties.swift */; }; FABB219F2602FC2C00C8785C /* RegisterDomainSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D74AD520FB5AD5004AD934 /* RegisterDomainSectionHeaderView.swift */; }; @@ -4846,7 +4851,6 @@ FABB23192602FC2C00C8785C /* Media+Extensions.m in Sources */ = {isa = PBXBuildFile; fileRef = 08C388691ED78EE70057BE49 /* Media+Extensions.m */; }; FABB231A2602FC2C00C8785C /* GutenbergImgUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2EC3BF2209A144006176E1 /* GutenbergImgUploadProcessor.swift */; }; FABB231B2602FC2C00C8785C /* PluginListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E151C0C71F388A2000710A83 /* PluginListViewModel.swift */; }; - FABB231C2602FC2C00C8785C /* WPStyleGuide+People.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B9128A1BB0129C003C25B9 /* WPStyleGuide+People.swift */; }; FABB231D2602FC2C00C8785C /* PlanGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6F2787B21BC1A48008B4DB5 /* PlanGroup.swift */; }; FABB231E2602FC2C00C8785C /* MenuItemPostsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 08216FB91CDBF96000304BA7 /* MenuItemPostsViewController.m */; }; FABB231F2602FC2C00C8785C /* SignupUsernameTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4322A20C203E1885004EA740 /* SignupUsernameTableViewController.swift */; }; @@ -5668,6 +5672,8 @@ FE23EB4A26E7C91F005A1698 /* richCommentTemplate.html in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */; }; FE23EB4B26E7C91F005A1698 /* richCommentStyle.css in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4826E7C91F005A1698 /* richCommentStyle.css */; }; FE23EB4C26E7C91F005A1698 /* richCommentStyle.css in Resources */ = {isa = PBXBuildFile; fileRef = FE23EB4826E7C91F005A1698 /* richCommentStyle.css */; }; + FE2590992BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2590982BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift */; }; + FE25909A2BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2590982BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift */; }; FE25C235271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */; }; FE25C236271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */; }; FE29EFCD29A91160007CE034 /* WPAdminConvertibleRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE29EFCC29A91160007CE034 /* WPAdminConvertibleRouter.swift */; }; @@ -5749,6 +5755,8 @@ FEAA6F79298CE4A600ADB44C /* PluginJetpackProxyServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAA6F78298CE4A600ADB44C /* PluginJetpackProxyServiceTests.swift */; }; FEAC916E28001FC4005026E7 /* AvatarTrainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAC916D28001FC4005026E7 /* AvatarTrainView.swift */; }; FEAC916F28001FC4005026E7 /* AvatarTrainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEAC916D28001FC4005026E7 /* AvatarTrainView.swift */; }; + FEC1B0CE2BE41A7400CB4A3D /* AdaptiveCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC1B0CD2BE41A7400CB4A3D /* AdaptiveCollectionViewFlowLayout.swift */; }; + FEC1B0CF2BE41E1C00CB4A3D /* AdaptiveCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC1B0CD2BE41A7400CB4A3D /* AdaptiveCollectionViewFlowLayout.swift */; }; FEC26030283FBA1A003D886A /* BloggingPromptCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC2602F283FBA1A003D886A /* BloggingPromptCoordinator.swift */; }; FEC26031283FBA1A003D886A /* BloggingPromptCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC2602F283FBA1A003D886A /* BloggingPromptCoordinator.swift */; }; FEC26033283FC902003D886A /* RootViewCoordinator+BloggingPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEC26032283FC902003D886A /* RootViewCoordinator+BloggingPrompt.swift */; }; @@ -6057,6 +6065,7 @@ 0189AF042ACAD89700F63393 /* ShoppingCartService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShoppingCartService.swift; sourceTree = ""; }; 018FF1342AE6771A00F301C3 /* LockScreenVerticalCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenVerticalCard.swift; sourceTree = ""; }; 018FF1362AE67C2600F301C3 /* LockScreenFlexibleCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenFlexibleCard.swift; sourceTree = ""; }; + 019105852BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsGhostSingleValueCell.swift; sourceTree = ""; }; 019C5B8C2BD6570D00A69DB0 /* StatsSubscribersStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSubscribersStore.swift; sourceTree = ""; }; 019C5B932BD6917600A69DB0 /* StatsSubscribersCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSubscribersCache.swift; sourceTree = ""; }; 019C5B962BD6A49900A69DB0 /* StatsSubscribersViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSubscribersViewModelTests.swift; sourceTree = ""; }; @@ -6068,6 +6077,8 @@ 01B759072B3ECAF300179AE6 /* DomainsStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainsStateView.swift; sourceTree = ""; }; 01B7590A2B3ED63B00179AE6 /* DomainDetailsWebViewControllerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainDetailsWebViewControllerWrapper.swift; sourceTree = ""; }; 01B7590D2B3EEEA400179AE6 /* SiteDomainsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteDomainsViewModelTests.swift; sourceTree = ""; }; + 01C4472B2BE4F8560006F787 /* StatsGhostLineChartCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsGhostLineChartCell.swift; sourceTree = ""; }; + 01C591442BDBD63D0071515C /* StatsGhostTopCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsGhostTopCell.swift; sourceTree = ""; }; 01CE5006290A889F00A9C2E0 /* TracksConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracksConfiguration.swift; sourceTree = ""; }; 01CE5010290A890300A9C2E0 /* TracksConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracksConfiguration.swift; sourceTree = ""; }; 01D2FF5D2AA733690038E040 /* LockScreenFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenFieldView.swift; sourceTree = ""; }; @@ -6308,6 +6319,7 @@ 0A9610F828B2E56300076EBA /* UserSuggestion+Comparable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserSuggestion+Comparable.swift"; sourceTree = ""; }; 0A9687BB28B40771009DCD2F /* FullScreenCommentReplyViewModelMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCommentReplyViewModelMock.swift; sourceTree = ""; }; 0C01A6E92AB37F0F009F7145 /* SiteMediaCollectionCellSelectionOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteMediaCollectionCellSelectionOverlayView.swift; sourceTree = ""; }; + 0C02E6C42BE3B6E30055F0F6 /* PostTrashedOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTrashedOverlayView.swift; sourceTree = ""; }; 0C03AEC92B7D995F00B64A25 /* PublishButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishButton.swift; sourceTree = ""; }; 0C0453272AC73343003079C8 /* SiteMediaVideoDurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteMediaVideoDurationView.swift; sourceTree = ""; }; 0C04532A2AC77245003079C8 /* SiteMediaDocumentInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteMediaDocumentInfoView.swift; sourceTree = ""; }; @@ -7212,7 +7224,6 @@ 59FBD5611B5684F300734466 /* ThemeServiceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThemeServiceTests.m; sourceTree = ""; }; 5C1CEB34870A8BA1ED1E502B /* Pods-WordPressUITests.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressUITests.release-alpha.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressUITests/Pods-WordPressUITests.release-alpha.xcconfig"; sourceTree = ""; }; 5D1181E61B4D6DEB003F3084 /* WPStyleGuide+Reader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WPStyleGuide+Reader.swift"; sourceTree = ""; }; - 5D13FA561AF99C2100F06492 /* PageListSectionHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PageListSectionHeaderView.xib; sourceTree = ""; }; 5D146EB9189857ED0068FDC6 /* FeaturedImageViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeaturedImageViewController.h; sourceTree = ""; usesTabs = 0; }; 5D146EBA189857ED0068FDC6 /* FeaturedImageViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeaturedImageViewController.m; sourceTree = ""; usesTabs = 0; }; 5D1D04731B7A50B100CDE646 /* Reader.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Reader.storyboard; sourceTree = ""; }; @@ -7811,7 +7822,6 @@ 8B8FE8562343952B00F9AD2E /* PostAutoUploadMessages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostAutoUploadMessages.swift; sourceTree = ""; }; 8B92D69527CD51FA001F5371 /* DashboardGhostCardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardGhostCardCell.swift; sourceTree = ""; }; 8B93412E257029F50097D0AC /* FilterChipButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterChipButton.swift; sourceTree = ""; }; - 8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageListSectionHeaderView.swift; sourceTree = ""; }; 8B9E15DAF3E1A369E9BE3407 /* Pods-WordPressUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WordPressUITests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-WordPressUITests/Pods-WordPressUITests.release.xcconfig"; sourceTree = ""; }; 8BA125EA27D8F5E4008B779F /* UIView+PinSubviewPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+PinSubviewPriority.swift"; sourceTree = ""; }; 8BA77BCA2482C52A00E1EBBF /* ReaderCardDiscoverAttributionView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReaderCardDiscoverAttributionView.xib; sourceTree = ""; }; @@ -8185,7 +8195,6 @@ 9A9E3FA2230D5F0A00909BC4 /* StatsStackViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsStackViewCell.swift; sourceTree = ""; }; 9A9E3FAB230E9DD000909BC4 /* StatsGhostCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsGhostCells.swift; sourceTree = ""; }; 9A9E3FAC230E9DD000909BC4 /* StatsGhostTwoColumnCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatsGhostTwoColumnCell.xib; sourceTree = ""; }; - 9A9E3FAF230EA7A300909BC4 /* StatsGhostTopCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatsGhostTopCell.xib; sourceTree = ""; }; 9A9E3FB1230EB74300909BC4 /* StatsGhostTabbedCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatsGhostTabbedCell.xib; sourceTree = ""; }; 9A9E3FB3230EC4F700909BC4 /* StatsGhostPostingActivityCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatsGhostPostingActivityCell.xib; sourceTree = ""; }; 9AA0ADB0235F116F0027AB5D /* AsyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncOperation.swift; sourceTree = ""; }; @@ -8232,6 +8241,8 @@ B038A81A2BD70FCA00763731 /* StatsSubscribersChartCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSubscribersChartCell.swift; sourceTree = ""; }; B038A81B2BD70FCA00763731 /* StatsSubscribersChartCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatsSubscribersChartCell.xib; sourceTree = ""; }; B038A8202BD7164F00763731 /* StatsSubscribersLineChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsSubscribersLineChart.swift; sourceTree = ""; }; + B038BD142BE1A37D009FD7E9 /* SubscribersChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribersChartMarker.swift; sourceTree = ""; }; + B038BD172BE1A3CC009FD7E9 /* StatsChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsChartMarker.swift; sourceTree = ""; }; B03B9233250BC593000A40AF /* SuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionService.swift; sourceTree = ""; }; B03B9235250BC5FD000A40AF /* Suggestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Suggestion.swift; sourceTree = ""; }; B0637526253E7CEB00FD45D2 /* SuggestionsTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SuggestionsTableView.swift; path = Suggestions/SuggestionsTableView.swift; sourceTree = ""; }; @@ -8910,7 +8921,6 @@ E1B912821BB01047003C25B9 /* PeopleRoleBadgeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeopleRoleBadgeLabel.swift; sourceTree = ""; }; E1B912841BB01266003C25B9 /* PeopleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeopleCell.swift; sourceTree = ""; }; E1B912881BB01288003C25B9 /* PeopleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeopleViewController.swift; sourceTree = ""; }; - E1B9128A1BB0129C003C25B9 /* WPStyleGuide+People.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WPStyleGuide+People.swift"; sourceTree = ""; }; E1B921BB1C0ED5A3003EA3CB /* MediaSizeSliderCellTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaSizeSliderCellTest.swift; sourceTree = ""; }; E1BB92311FDAAFFA00F2D817 /* TextWithAccessoryButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextWithAccessoryButtonCell.swift; sourceTree = ""; }; E1BCFBC51C0626C5004BDADF /* WordPress 43.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 43.xcdatamodel"; sourceTree = ""; }; @@ -9607,6 +9617,7 @@ FE1E201D2A49D59400CE7C90 /* JetpackSocialServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackSocialServiceTests.swift; sourceTree = ""; }; FE23EB4726E7C91F005A1698 /* richCommentTemplate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = richCommentTemplate.html; path = Resources/HTML/richCommentTemplate.html; sourceTree = ""; }; FE23EB4826E7C91F005A1698 /* richCommentStyle.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = richCommentStyle.css; path = Resources/HTML/richCommentStyle.css; sourceTree = ""; }; + FE2590982BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderTagCardEmptyCell.swift; sourceTree = ""; }; FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsNotificationSheetViewController.swift; sourceTree = ""; }; FE29EFCC29A91160007CE034 /* WPAdminConvertibleRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WPAdminConvertibleRouter.swift; sourceTree = ""; }; FE2E3728281C839C00A1E82A /* BloggingPromptsServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloggingPromptsServiceTests.swift; sourceTree = ""; }; @@ -9660,6 +9671,7 @@ FEAA6F78298CE4A600ADB44C /* PluginJetpackProxyServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginJetpackProxyServiceTests.swift; sourceTree = ""; }; FEAC916D28001FC4005026E7 /* AvatarTrainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarTrainView.swift; sourceTree = ""; }; FEB7A8922718852A00A8CF85 /* WordPress 134.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 134.xcdatamodel"; sourceTree = ""; }; + FEC1B0CD2BE41A7400CB4A3D /* AdaptiveCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCollectionViewFlowLayout.swift; sourceTree = ""; }; FEC2602F283FBA1A003D886A /* BloggingPromptCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloggingPromptCoordinator.swift; sourceTree = ""; }; FEC26032283FC902003D886A /* RootViewCoordinator+BloggingPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RootViewCoordinator+BloggingPrompt.swift"; sourceTree = ""; }; FECA442E28350B7800D01F15 /* PromptRemindersScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptRemindersScheduler.swift; sourceTree = ""; }; @@ -9992,6 +10004,7 @@ B038A81A2BD70FCA00763731 /* StatsSubscribersChartCell.swift */, B038A81B2BD70FCA00763731 /* StatsSubscribersChartCell.xib */, B038A8202BD7164F00763731 /* StatsSubscribersLineChart.swift */, + B038BD142BE1A37D009FD7E9 /* SubscribersChartMarker.swift */, ); path = Subscribers; sourceTree = ""; @@ -12837,6 +12850,7 @@ 83BF48BD2BD6FA3000C0E1A1 /* ReaderTagCellViewModel.swift */, 83A8A2922BDC557E001F9133 /* ReaderTagFooterView.swift */, 83A8A2932BDC557E001F9133 /* ReaderTagFooterView.xib */, + FE2590982BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift */, ); name = Cards; sourceTree = ""; @@ -13013,8 +13027,6 @@ children = ( 0C700B852AE1E1300085C2EE /* PageListCell.swift */, 0C700B882AE1E1940085C2EE /* PageListItemViewModel.swift */, - 8B93856D22DC08060010BF02 /* PageListSectionHeaderView.swift */, - 5D13FA561AF99C2100F06492 /* PageListSectionHeaderView.xib */, 834A49D12A0C23A90042ED3D /* TemplatePageTableViewCell.swift */, ); name = Views; @@ -13241,6 +13253,7 @@ 73D86968223AF4040064920F /* StatsChartLegendView.swift */, 1756F1DE2822BB6F00CD0915 /* SparklineView.swift */, 1756DBDE28328B76006E6DB9 /* DonutChartView.swift */, + B038BD172BE1A3CC009FD7E9 /* StatsChartMarker.swift */, ); path = Charts; sourceTree = ""; @@ -14070,6 +14083,7 @@ 8584FDB4192437160019C02E /* Utility */ = { isa = PBXGroup; children = ( + FEC1B0CC2BE41A5F00CB4A3D /* CollectionView */, F4FF50E42B4D7D590076DB0C /* In-App Feedback */, FE6BB14129322798001E5F7A /* Migration */, 8B85AED8259230C500ADBEC9 /* AB Testing */, @@ -15014,15 +15028,17 @@ children = ( 9A09F91D230C4C0200F42AB7 /* StatsGhostTableViewRows.swift */, 9A9E3FAB230E9DD000909BC4 /* StatsGhostCells.swift */, + 01C591442BDBD63D0071515C /* StatsGhostTopCell.swift */, 9A9E3FB3230EC4F700909BC4 /* StatsGhostPostingActivityCell.xib */, 9A9E3FB1230EB74300909BC4 /* StatsGhostTabbedCell.xib */, - 9A9E3FAF230EA7A300909BC4 /* StatsGhostTopCell.xib */, 9A19D440236C7C7500D393E5 /* StatsGhostTopHeaderCell.xib */, 9A9E3FAC230E9DD000909BC4 /* StatsGhostTwoColumnCell.xib */, 9AC3C69A231543C2007933CD /* StatsGhostChartCell.xib */, 9A73CB072350DE4C002EF20C /* StatsGhostSingleRowCell.xib */, 9AB36B83236B25D900FAD72A /* StatsGhostTitleCell.xib */, FA347AF126EB7A420096604B /* StatsGhostGrowAudienceCell.xib */, + 01C4472B2BE4F8560006F787 /* StatsGhostLineChartCell.swift */, + 019105852BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift */, ); path = GhostViews; sourceTree = ""; @@ -15302,6 +15318,7 @@ 8B260D7D2444FC9D0010F756 /* PostVisibilitySelectorViewController.swift */, 593F26601CAB00CA00F14073 /* PostSharingController.swift */, E155EC711E9B7DCE009D7F63 /* PostTagPickerViewController.swift */, + 0C02E6C42BE3B6E30055F0F6 /* PostTrashedOverlayView.swift */, ); path = Post; sourceTree = ""; @@ -15844,14 +15861,6 @@ name = Notifications; sourceTree = ""; }; - B59B18761CC7FC070055EB7C /* Style */ = { - isa = PBXGroup; - children = ( - E1B9128A1BB0129C003C25B9 /* WPStyleGuide+People.swift */, - ); - name = Style; - sourceTree = ""; - }; B59B18771CC7FC190055EB7C /* Controllers */ = { isa = PBXGroup; children = ( @@ -17276,7 +17285,6 @@ isa = PBXGroup; children = ( B59B18771CC7FC190055EB7C /* Controllers */, - B59B18761CC7FC070055EB7C /* Style */, B59B18781CC7FC230055EB7C /* Views */, B59B18791CC7FC330055EB7C /* ViewModels */, E1B912801BB00EFD003C25B9 /* People.storyboard */, @@ -18948,6 +18956,14 @@ name = Detail; sourceTree = ""; }; + FEC1B0CC2BE41A5F00CB4A3D /* CollectionView */ = { + isa = PBXGroup; + children = ( + FEC1B0CD2BE41A7400CB4A3D /* AdaptiveCollectionViewFlowLayout.swift */, + ); + path = CollectionView; + sourceTree = ""; + }; FEC2602D283FB9D4003D886A /* Blogging Prompts */ = { isa = PBXGroup; children = ( @@ -19707,7 +19723,6 @@ 4058F41A1FF40EE1000D5559 /* PluginDetailViewHeaderCell.xib in Resources */, 1761F17226209AEE000815EF /* open-source-dark-icon-app-60x60@3x.png in Resources */, 406A0EF0224D39C50016AD6A /* Flags.xcassets in Resources */, - 9A9E3FB0230EA7A300909BC4 /* StatsGhostTopCell.xib in Resources */, 985793C922F23D7000643DBF /* CustomizeInsightsCell.xib in Resources */, 1761F17926209AEE000815EF /* wordpress-dark-icon-app-60x60@2x.png in Resources */, 83A8A2962BDC557F001F9133 /* ReaderTagFooterView.xib in Resources */, @@ -19786,7 +19801,6 @@ FE015BB12ADA002400F50D7F /* ReaderTopicsNewCardCell.xib in Resources */, 8BA77BCD248340CE00E1EBBF /* ReaderDetailToolbar.xib in Resources */, 4070D75C20E5F55A007CEBDA /* RewindStatusTableViewCell.xib in Resources */, - 5D13FA571AF99C2100F06492 /* PageListSectionHeaderView.xib in Resources */, 1761F18D26209AEE000815EF /* hot-pink-icon-app-76x76.png in Resources */, FA7AA4A725BFE0A9005E7200 /* JetpackScanThreatDetailsViewController.xib in Resources */, 469EB16624D8B12700C764CB /* CollabsableHeaderFilterCollectionViewCell.xib in Resources */, @@ -20258,7 +20272,6 @@ FABB1FD32602FC2C00C8785C /* PluginDetailViewHeaderCell.xib in Resources */, FABB1FD42602FC2C00C8785C /* Flags.xcassets in Resources */, FE3E427826A868E300C596CE /* ListTableViewCell.xib in Resources */, - FABB1FD52602FC2C00C8785C /* StatsGhostTopCell.xib in Resources */, C7124E4E2638528F00929318 /* JetpackPrologueViewController.xib in Resources */, F465978628E65E1800D5F49A /* blue-on-white-icon-app-83.5@2x.png in Resources */, FABB1FD62602FC2C00C8785C /* CustomizeInsightsCell.xib in Resources */, @@ -20324,7 +20337,6 @@ F46597FD28E66A1100D5F49A /* white-on-black-icon-app-60@2x.png in Resources */, FABB20212602FC2C00C8785C /* ReaderDetailToolbar.xib in Resources */, FABB20222602FC2C00C8785C /* RewindStatusTableViewCell.xib in Resources */, - FABB20232602FC2C00C8785C /* PageListSectionHeaderView.xib in Resources */, 8091019629078CFE00FCB4EA /* JetpackFullscreenOverlayViewController.xib in Resources */, 98DCF4A8275945E00008630F /* ReaderDetailNoCommentCell.xib in Resources */, FABB20242602FC2C00C8785C /* JetpackScanThreatDetailsViewController.xib in Resources */, @@ -21716,6 +21728,7 @@ 019C5B8D2BD6570D00A69DB0 /* StatsSubscribersStore.swift in Sources */, 74FA4BE51FBFA0660031EAAD /* Extensions.xcdatamodeld in Sources */, 986CC4D220E1B2F6004F300E /* CustomLogFormatter.swift in Sources */, + B038BD152BE1A37D009FD7E9 /* SubscribersChartMarker.swift in Sources */, 3236F77524ABB7770088E8F3 /* ReaderInterestsCollectionViewCell.swift in Sources */, 0C7E09202A4286A00052324C /* PostMetaButton.m in Sources */, F47E154A29E84A9300B6E426 /* SiteCreationPurchasingWebFlowController.swift in Sources */, @@ -21841,7 +21854,6 @@ C3B554512965C32A00A04753 /* MenusViewController+JetpackBannerViewController.swift in Sources */, 1E0462162566938300EB98EF /* GutenbergFileUploadProcessor.swift in Sources */, F913BB1024B3C5CE00C19032 /* EventLoggingDataProvider.swift in Sources */, - 8B93856E22DC08060010BF02 /* PageListSectionHeaderView.swift in Sources */, E690F6EF25E05D180015A777 /* InviteLinks+CoreDataClass.swift in Sources */, 59E1D46F1CEF77B500126697 /* Page+CoreDataProperties.swift in Sources */, C373D6E728045281008F8C26 /* SiteIntentData.swift in Sources */, @@ -22180,6 +22192,8 @@ E166FA1B1BB0656B00374B5B /* PeopleCellViewModel.swift in Sources */, 436D56292117312700CEAA33 /* RegisterDomainDetailsViewController.swift in Sources */, F4EDAA4C29A516EA00622D3D /* ReaderPostService.swift in Sources */, + 01C4472C2BE4F8560006F787 /* StatsGhostLineChartCell.swift in Sources */, + 019105862BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift in Sources */, F5E29036243E4F5F00C19CA5 /* FilterProvider.swift in Sources */, F4FE743429C3767300AC2729 /* AddressTableViewCell+ViewModel.swift in Sources */, 027AC51D227896540033E56E /* DomainCreditEligibilityChecker.swift in Sources */, @@ -22395,7 +22409,6 @@ 08C3886A1ED78EE70057BE49 /* Media+Extensions.m in Sources */, FF2EC3C02209A144006176E1 /* GutenbergImgUploadProcessor.swift in Sources */, E151C0C81F388A2000710A83 /* PluginListViewModel.swift in Sources */, - E1B9128B1BB0129C003C25B9 /* WPStyleGuide+People.swift in Sources */, E6F2788121BC1A4A008B4DB5 /* PlanGroup.swift in Sources */, 08216FCE1CDBF96000304BA7 /* MenuItemPostsViewController.m in Sources */, 4322A20D203E1885004EA740 /* SignupUsernameTableViewController.swift in Sources */, @@ -22674,6 +22687,7 @@ E62CE58E26B1D14200C9D147 /* AccountService+Cookies.swift in Sources */, 3F4A4C232AD3FA2E00DE5DF8 /* MySiteViewModel.swift in Sources */, FACF66D02ADD6CD8008C3E13 /* PostListItemViewModel.swift in Sources */, + FE2590992BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift in Sources */, B0F2EFBF259378E600C7EB6D /* SiteSuggestionService.swift in Sources */, 4388FF0020A4E19C00783948 /* NotificationsViewController+PushPrimer.swift in Sources */, 800035BD291DD0D7007D2D26 /* JetpackFullscreenOverlayGeneralViewModel+Analytics.swift in Sources */, @@ -22697,6 +22711,7 @@ E1BEEC631C4E35A8000B4FA0 /* Animator.swift in Sources */, 981C3494218388CA00FC2683 /* SiteStatsDashboardViewController.swift in Sources */, E1C5457E1C6B962D001CEB0E /* MediaSettings.swift in Sources */, + FEC1B0CE2BE41A7400CB4A3D /* AdaptiveCollectionViewFlowLayout.swift in Sources */, 02BF30532271D7F000616558 /* DomainCreditRedemptionSuccessViewController.swift in Sources */, 93C4864F181043D700A24725 /* ActivityLogDetailViewController.m in Sources */, B57B99DE19A2DBF200506504 /* NSObject+Helpers.m in Sources */, @@ -22933,6 +22948,7 @@ FAB8004925AEDC2300D5D54A /* JetpackBackupCompleteViewController.swift in Sources */, 9A8ECE0C2254A3260043C8DA /* JetpackLoginViewController.swift in Sources */, 46F583A92624CE790010A723 /* BlockEditorSettings+CoreDataClass.swift in Sources */, + 0C02E6C52BE3B6E30055F0F6 /* PostTrashedOverlayView.swift in Sources */, B50C0C641EF42B3A00372C65 /* Header+WordPress.swift in Sources */, FAA5C90F2B7FC74C002E073B /* PostSyncStateViewModel.swift in Sources */, F504D2B125D60C5900A2764C /* StoryMediaLoader.swift in Sources */, @@ -23181,6 +23197,7 @@ C7BB601F2863B9E800748FD9 /* QRLoginCameraSession.swift in Sources */, FFCB9F4B22A125BD0080A45F /* WPException.m in Sources */, 9A9E3FAD230E9DD000909BC4 /* StatsGhostCells.swift in Sources */, + B038BD182BE1A3CC009FD7E9 /* StatsChartMarker.swift in Sources */, E14DF70620C922F200959BA9 /* NotificationCenter+ObserveOnce.swift in Sources */, E125F1E41E8E595E00320B67 /* SharePost.swift in Sources */, B56FEB791CD8E13C00E621F9 /* RoleViewController.swift in Sources */, @@ -23439,6 +23456,7 @@ E6FACB1E1EC675E300284AC7 /* GravatarProfile.swift in Sources */, D8212CB720AA7703008E8AE8 /* ReaderShareAction.swift in Sources */, C7BB60192863AF9700748FD9 /* QRLoginProtocols.swift in Sources */, + 01C591452BDBD63D0071515C /* StatsGhostTopCell.swift in Sources */, 9826AE8A21B5CC7300C851FA /* PostingActivityMonth.swift in Sources */, 08F531FE2B7E94F20061BD0E /* CachedAsyncImage.swift in Sources */, 3F09CCAE24292EFD00D00A8C /* ReaderTabItem.swift in Sources */, @@ -24502,6 +24520,7 @@ 8BD66ED52787530C00CCD95A /* PostsCardViewModel.swift in Sources */, FABB20F22602FC2C00C8785C /* PlanService.swift in Sources */, FABB20F32602FC2C00C8785C /* Routes+Mbar.swift in Sources */, + B038BD162BE1A37D009FD7E9 /* SubscribersChartMarker.swift in Sources */, FEFA264026C5AE9E009CCB7E /* ShareAppTextActivityItemSource.swift in Sources */, FABB20F42602FC2C00C8785C /* PostListEditorPresenter.swift in Sources */, C7BB60202863B9E800748FD9 /* QRLoginCameraSession.swift in Sources */, @@ -24701,6 +24720,7 @@ FABB21762602FC2C00C8785C /* CameraHandler.swift in Sources */, F4C1FC642A44831300AD7CB0 /* PrivacySettingsAnalyticsTracker.swift in Sources */, FA73D7EA27987BA500DF24B3 /* SitePickerViewController+SiteIcon.swift in Sources */, + 01C591462BDBD63D0071515C /* StatsGhostTopCell.swift in Sources */, FABB21772602FC2C00C8785C /* JetpackConnectionViewController.swift in Sources */, 803BB97A2959543D00B3F6D6 /* RootViewCoordinator.swift in Sources */, FABB21782602FC2C00C8785C /* NotificationActionsService.swift in Sources */, @@ -24757,7 +24777,6 @@ F4CBE3D7292597E3004FFBB6 /* SupportTableViewControllerConfiguration.swift in Sources */, FABB21992602FC2C00C8785C /* GutenbergFileUploadProcessor.swift in Sources */, FABB219A2602FC2C00C8785C /* EventLoggingDataProvider.swift in Sources */, - FABB219B2602FC2C00C8785C /* PageListSectionHeaderView.swift in Sources */, FABB219C2602FC2C00C8785C /* InviteLinks+CoreDataClass.swift in Sources */, 809101992908DE8500FCB4EA /* JetpackFullscreenOverlayViewModel.swift in Sources */, FABB219D2602FC2C00C8785C /* Page+CoreDataProperties.swift in Sources */, @@ -25120,6 +25139,7 @@ FABB22A72602FC2C00C8785C /* BlogListViewController+Activity.swift in Sources */, 3F810A5B2616870C00ADDCC2 /* UnifiedPrologueIntroContentView.swift in Sources */, FABB22A82602FC2C00C8785C /* TableDataCoordinator.swift in Sources */, + 0C02E6C62BE3B6E30055F0F6 /* PostTrashedOverlayView.swift in Sources */, FABB22A92602FC2C00C8785C /* SettingsMultiTextViewController.m in Sources */, FABB22AA2602FC2C00C8785C /* FilterSheetViewController.swift in Sources */, FABB22AB2602FC2C00C8785C /* Plan.swift in Sources */, @@ -25248,6 +25268,7 @@ FABB22FB2602FC2C00C8785C /* ReaderFollowAction.swift in Sources */, FABB22FC2602FC2C00C8785C /* SheetActions.swift in Sources */, FABB22FD2602FC2C00C8785C /* ReaderTracker.swift in Sources */, + FEC1B0CF2BE41E1C00CB4A3D /* AdaptiveCollectionViewFlowLayout.swift in Sources */, FABB22FE2602FC2C00C8785C /* PlanFeature.swift in Sources */, 17C1D7DD26735631006C8970 /* EmojiRenderer.swift in Sources */, FABB22FF2602FC2C00C8785C /* Spotlightable.swift in Sources */, @@ -25297,7 +25318,6 @@ FABB23192602FC2C00C8785C /* Media+Extensions.m in Sources */, FABB231A2602FC2C00C8785C /* GutenbergImgUploadProcessor.swift in Sources */, FABB231B2602FC2C00C8785C /* PluginListViewModel.swift in Sources */, - FABB231C2602FC2C00C8785C /* WPStyleGuide+People.swift in Sources */, B09879772B572E1F0048256D /* StatsTrafficDatePickerViewModel.swift in Sources */, FA98B61729A3B76A0071AAE8 /* DashboardBlazeCardCell.swift in Sources */, FA3A281C2A39C8FF00206D74 /* BlazeCampaignSingleStatView.swift in Sources */, @@ -25345,6 +25365,7 @@ 8B55FAAD2614FC87007D618E /* Text+BoldSubString.swift in Sources */, 01616C8C2B5AA6E50023C123 /* StatsTrafficBarChartTabs.swift in Sources */, FABB23382602FC2C00C8785C /* WordPress-22-23.xcmappingmodel in Sources */, + 01C4472D2BE4F8560006F787 /* StatsGhostLineChartCell.swift in Sources */, FED65D79293511E4008071BF /* SharedDataIssueSolver.swift in Sources */, FABB23392602FC2C00C8785C /* CountriesMap.swift in Sources */, F45EB50C2B883E6E004E9053 /* CommentNotification.swift in Sources */, @@ -25466,6 +25487,7 @@ F41E32FF287B47A500F89082 /* SuggestionsListViewModel.swift in Sources */, F4EDAA5129A795C600622D3D /* BlockedAuthor.swift in Sources */, FABB238E2602FC2C00C8785C /* NotificationSettingsService.swift in Sources */, + 019105872BE8BD6000CDFB16 /* StatsGhostSingleValueCell.swift in Sources */, 8091019429078CFE00FCB4EA /* JetpackFullscreenOverlayViewController.swift in Sources */, FA347AEE26EB6E300096604B /* GrowAudienceCell.swift in Sources */, FABB23922602FC2C00C8785C /* UIImage+Assets.swift in Sources */, @@ -25803,11 +25825,13 @@ 0CED200D2B68425A00E6DD52 /* WebKitView.swift in Sources */, FABB247F2602FC2C00C8785C /* StockPhotosPageable.swift in Sources */, FABB24802602FC2C00C8785C /* JetpackRestoreStatusViewController.swift in Sources */, + FE25909A2BE3F4E2005690E9 /* ReaderTagCardEmptyCell.swift in Sources */, B038A81E2BD70FCA00763731 /* StatsSubscribersChartCell.swift in Sources */, FABB24812602FC2C00C8785C /* BindableTapGestureRecognizer.swift in Sources */, FABB24822602FC2C00C8785C /* ReaderSearchSuggestion.swift in Sources */, DCF892CA282FA37100BB71E1 /* SiteStatsBaseTableViewController.swift in Sources */, FABB24832602FC2C00C8785C /* ReaderSearchViewController.swift in Sources */, + B038BD192BE1A3CC009FD7E9 /* StatsChartMarker.swift in Sources */, FABB24842602FC2C00C8785C /* SiteStatsTableHeaderView.swift in Sources */, FABB24852602FC2C00C8785C /* MediaSizeSliderCell.swift in Sources */, 80B016D22803AB9F00D15566 /* DashboardPostsListCardCell.swift in Sources */, diff --git a/WordPress/WordPressTest/StatsSubscribersViewModelTests.swift b/WordPress/WordPressTest/StatsSubscribersViewModelTests.swift index b803c52f8380..521b91bafa35 100644 --- a/WordPress/WordPressTest/StatsSubscribersViewModelTests.swift +++ b/WordPress/WordPressTest/StatsSubscribersViewModelTests.swift @@ -18,7 +18,7 @@ final class StatsSubscribersViewModelTests: XCTestCase { let expectation = expectation(description: "First section should be loading") sut.tableViewSnapshot .sink(receiveValue: { snapshot in - if let _ = snapshot.itemIdentifiers.first?.immuTableRow as? StatsGhostTopImmutableRow { + if let _ = snapshot.itemIdentifiers.first?.immuTableRow as? (any StatsRowGhostable) { expectation.fulfill() } }) diff --git a/config/Version.internal.xcconfig b/config/Version.internal.xcconfig index 2b2b88fc0c89..8115973780ac 100644 --- a/config/Version.internal.xcconfig +++ b/config/Version.internal.xcconfig @@ -1,2 +1,2 @@ -VERSION_LONG = 24.8.0.20240430 +VERSION_LONG = 24.8.0.20240507 VERSION_SHORT = 24.8 diff --git a/config/Version.public.xcconfig b/config/Version.public.xcconfig index 510cabe182bf..13b1a450eee5 100644 --- a/config/Version.public.xcconfig +++ b/config/Version.public.xcconfig @@ -1,2 +1,2 @@ -VERSION_LONG = 24.8.0.2 +VERSION_LONG = 24.8.0.3 VERSION_SHORT = 24.8 diff --git a/fastlane/jetpack_metadata/default/release_notes.txt b/fastlane/jetpack_metadata/default/release_notes.txt index a051f43082d3..33f1301d9f19 100644 --- a/fastlane/jetpack_metadata/default/release_notes.txt +++ b/fastlane/jetpack_metadata/default/release_notes.txt @@ -1,4 +1 @@ -* [*] [internal] Update Reachability. [#23030] -* [*] Move "Settings" context menu action in "Pages" from the submenu to a separate section to make it easily discoverable and make it available for unpublished posts [#23065] -* [*] Add "Stats" context menu action to "Pages" [#23065] - +We made some updates to the Pages menu. You’ll now find Settings in its own section, right underneath Stats (which we also moved). It’s available for both published and unpublished posts. Groovy. \ No newline at end of file diff --git a/fastlane/jetpack_metadata/pt-BR/release_notes.txt b/fastlane/jetpack_metadata/pt-BR/release_notes.txt new file mode 100644 index 000000000000..588aafef9cdf --- /dev/null +++ b/fastlane/jetpack_metadata/pt-BR/release_notes.txt @@ -0,0 +1 @@ +Fizemos algumas atualizações no menu Páginas. Agora as configurações têm uma seção própria, logo abaixo de Estatísticas (que também mudou de lugar). Essa seção está disponível para posts publicados ou não. Legal, não é? diff --git a/fastlane/jetpack_metadata/tr/release_notes.txt b/fastlane/jetpack_metadata/tr/release_notes.txt new file mode 100644 index 000000000000..58fa1d96ae81 --- /dev/null +++ b/fastlane/jetpack_metadata/tr/release_notes.txt @@ -0,0 +1 @@ +Sayfalar menüsünde bazı güncellemeler yaptık. Artık Ayarlar'ı İstatistikler'in altında (bunu da taşıdık) kendi bölümünde bulabilirsiniz. Bu, hem yayımlanmış hem de yayımlanmamış gönderilerde mevcuttur. Havalı. diff --git a/fastlane/metadata/de-DE/release_notes.txt b/fastlane/metadata/de-DE/release_notes.txt new file mode 100644 index 000000000000..4b579f7f3c19 --- /dev/null +++ b/fastlane/metadata/de-DE/release_notes.txt @@ -0,0 +1 @@ +Wir haben einige Aktualisierungen am Seitenmenü vorgenommen. Du findest die Einstellungen jetzt in einem eigenen Abschnitt, und sie sind sowohl für veröffentlichte als auch für unveröffentlichte Beiträge verfügbar. Das fetzt. diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index b947e8874d39..cbd493a03aa1 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,3 +1 @@ -* [*] [internal] Update Reachability. [#23030] -* [*] Move "Settings" context menu action in "Pages" from the submenu to a separate section to make it easily discoverable and make it available for unpublished posts [#23065] - +We made some updates to the Pages menu. You’ll now find Settings in its own section, and it’s available for both published and unpublished posts. Groovy. \ No newline at end of file diff --git a/fastlane/metadata/en-GB/release_notes.txt b/fastlane/metadata/en-GB/release_notes.txt new file mode 100644 index 000000000000..346d277faa34 --- /dev/null +++ b/fastlane/metadata/en-GB/release_notes.txt @@ -0,0 +1 @@ +We made some updates to the Pages menu. You’ll now find Settings in its own section, and it’s available for both published and unpublished posts. Groovy. diff --git a/fastlane/metadata/es-ES/release_notes.txt b/fastlane/metadata/es-ES/release_notes.txt new file mode 100644 index 000000000000..a2d6304d59d6 --- /dev/null +++ b/fastlane/metadata/es-ES/release_notes.txt @@ -0,0 +1 @@ +Hemos realizado algunas actualizaciones en el menú «Páginas». Ahora encontrarás lo ajustes en su propia sección, y está disponible tanto para las entradas publicadas como para las no publicadas. Genial. diff --git a/fastlane/metadata/nl-NL/release_notes.txt b/fastlane/metadata/nl-NL/release_notes.txt new file mode 100644 index 000000000000..c71937812a4d --- /dev/null +++ b/fastlane/metadata/nl-NL/release_notes.txt @@ -0,0 +1 @@ +We hebben enkele updates gemaakt aan het Pagina's menu. Je vindt nu instellingen in een eigen sectie, en het is beschikbaar voor zowel gepubliceerde als ongepubliceerde berichten. diff --git a/fastlane/metadata/sv/release_notes.txt b/fastlane/metadata/sv/release_notes.txt new file mode 100644 index 000000000000..acfc2ccdd025 --- /dev/null +++ b/fastlane/metadata/sv/release_notes.txt @@ -0,0 +1 @@ +Vi har gjort vissa uppdateringar i menyn för sidor. Nu finns alla inställningar i en egen sektion och de är alltid tillgängliga, oberoende av om ett inlägg är publicerat eller inte. Härligt. diff --git a/fastlane/metadata/tr/release_notes.txt b/fastlane/metadata/tr/release_notes.txt new file mode 100644 index 000000000000..a35959d9241b --- /dev/null +++ b/fastlane/metadata/tr/release_notes.txt @@ -0,0 +1 @@ +Sayfalar menüsünde bazı güncellemeler yaptık. Artık Ayarlar'ı kendi bölümünde bulabilirsiniz ve bu, hem yayımlanmış hem de yayımlanmamış gönderilerde mevcuttur. Havalı.