diff --git a/Account/Package.swift b/Account/Package.swift
index cafc3933c..b1b5a1d4f 100644
--- a/Account/Package.swift
+++ b/Account/Package.swift
@@ -26,6 +26,7 @@ dependencies.append(contentsOf: [
let package = Package(
name: "Account",
+ defaultLocalization: "en",
platforms: [.macOS(SupportedPlatform.MacOSVersion.v11), .iOS(SupportedPlatform.IOSVersion.v14)],
products: [
.library(
diff --git a/Account/Sources/Account/AccountError.swift b/Account/Sources/Account/AccountError.swift
index 75852b178..c66dc68cd 100644
--- a/Account/Sources/Account/AccountError.swift
+++ b/Account/Sources/Account/AccountError.swift
@@ -32,21 +32,34 @@ public enum AccountError: LocalizedError {
}
return false
}
+
+ public var errorTitle: String {
+ switch self {
+ case .createErrorNotFound:
+ return NSLocalizedString("error.title.feed-not-found", bundle: Bundle.module, comment: "Unable to Add Feed")
+ case .createErrorAlreadySubscribed:
+ return NSLocalizedString("error.title.already-subscribed", bundle: Bundle.module, comment: "Already Subscribed")
+ case .opmlImportInProgress:
+ return NSLocalizedString("error.title.ompl-import-in-progress", bundle: Bundle.module, comment: "OPML Import in Progress")
+ case .wrappedError(_, _):
+ return NSLocalizedString("error.title.error", bundle: Bundle.module, comment: "Error")
+ }
+ }
public var errorDescription: String? {
switch self {
case .createErrorNotFound:
- return NSLocalizedString("The feed couldn’t be found and can’t be added.", comment: "Not found")
+ return NSLocalizedString("error.message.feed-not-found", bundle: Bundle.module, comment: "Can’t add a feed because no feed was found.")
case .createErrorAlreadySubscribed:
- return NSLocalizedString("You are already subscribed to this feed and can’t add it again.", comment: "Already subscribed")
+ return NSLocalizedString("error.message.feed-already-subscribed", bundle: Bundle.module, comment: "You are already subscribed to this feed and can’t add it again.")
case .opmlImportInProgress:
- return NSLocalizedString("An OPML import for this account is already running.", comment: "Import running")
+ return NSLocalizedString("error.message.opml-import-in-progress", bundle: Bundle.module, comment: "An OPML import for this account is already running.")
case .wrappedError(let error, let account):
switch error {
case TransportError.httpError(let status):
if isCredentialsError(status: status) {
- let localizedText = NSLocalizedString("Your “%@” credentials are invalid or expired.", comment: "Invalid or expired")
- return NSString.localizedStringWithFormat(localizedText as NSString, account.nameForDisplay) as String
+ let localizedText = NSLocalizedString("error.message.credentials-expired.%@", bundle: Bundle.module, comment: "Your ”%@” credentials have expired.")
+ return String(format: localizedText, account.nameForDisplay)
} else {
return unknownError(error, account)
}
diff --git a/Account/Sources/Account/Resources/en-GB.lproj/Localizable.strings b/Account/Sources/Account/Resources/en-GB.lproj/Localizable.strings
new file mode 100644
index 000000000..852c08f67
--- /dev/null
+++ b/Account/Sources/Account/Resources/en-GB.lproj/Localizable.strings
@@ -0,0 +1,16 @@
+/*
+ Localizable.strings
+
+
+ Created by Stuart Breckenridge on 11/03/2023.
+
+*/
+
+"error.title.feed-not-found" = "Unable to Add Feed";
+"error.title.already-subscribed" = "Already Subscribed";
+"error.title.ompl-import-in-progress" = "OPML Import in Progress";
+"error.title.error" = "Error";
+
+"error.message.feed-not-found" = "Can’t add a feed because no feed was found.";
+"error.message.already-subscribed" = "You are already subscribed to this feed and can’t add it again.";
+"error.message.opml-import-in-progress" = "An OPML import for this account is already running.";
diff --git a/Account/Sources/Account/Resources/en.lproj/Localizable.strings b/Account/Sources/Account/Resources/en.lproj/Localizable.strings
new file mode 100644
index 000000000..852c08f67
--- /dev/null
+++ b/Account/Sources/Account/Resources/en.lproj/Localizable.strings
@@ -0,0 +1,16 @@
+/*
+ Localizable.strings
+
+
+ Created by Stuart Breckenridge on 11/03/2023.
+
+*/
+
+"error.title.feed-not-found" = "Unable to Add Feed";
+"error.title.already-subscribed" = "Already Subscribed";
+"error.title.ompl-import-in-progress" = "OPML Import in Progress";
+"error.title.error" = "Error";
+
+"error.message.feed-not-found" = "Can’t add a feed because no feed was found.";
+"error.message.already-subscribed" = "You are already subscribed to this feed and can’t add it again.";
+"error.message.opml-import-in-progress" = "An OPML import for this account is already running.";
diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift
index aa074e4f6..d732f28d8 100644
--- a/Mac/AppAssets.swift
+++ b/Mac/AppAssets.swift
@@ -179,7 +179,7 @@ struct AppAssets {
}()
static var privacyPolicyLink: NSAttributedString = {
- return NSAttributedString(linkText: NSLocalizedString("Privacy Policy", comment: "Privacy Policy"), linkURL: URL(string: "https://netnewswire.com/privacypolicy")!)
+ return NSAttributedString(linkText: NSLocalizedString("label.text.privacy-policy", comment: "Privacy Policy"), linkURL: URL(string: "https://netnewswire.com/privacypolicy")!)
}()
static var readClosedImage: RSImage = {
diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift
index b89a617ae..6cde701c4 100644
--- a/Mac/AppDelegate.swift
+++ b/Mac/AppDelegate.swift
@@ -353,13 +353,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
if let decodingError = error as? DecodingError {
switch decodingError {
case .typeMismatch(let type, _):
- let localizedError = NSLocalizedString("This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist", comment: "Type mismatch")
+ let localizedError = NSLocalizedString("alert.error.theme-type-mismatch.%@", comment: "This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, type as! CVarArg) as String
case .valueNotFound(let value, _):
- let localizedError = NSLocalizedString("This theme cannot be used because the the value—“%@”—is not found in the Info.plist.", comment: "Decoding value missing")
+ let localizedError = NSLocalizedString("alert.error.theme-value-missing.%@", comment: "This theme cannot be used because the the value—“%@”—is not found in the Info.plist.")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, value as! CVarArg) as String
case .keyNotFound(let codingKey, _):
- let localizedError = NSLocalizedString("This theme cannot be used because the the key—“%@”—is not found in the Info.plist.", comment: "Decoding key missing")
+ let localizedError = NSLocalizedString("alert.error.theme-key-not-found.%@", comment: "This theme cannot be used because the the key—“%@”—is not found in the Info.plist.")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, codingKey.stringValue) as String
case .dataCorrupted(let context):
guard let underlyingError = context.underlyingError as NSError?,
@@ -367,7 +367,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
informativeText = error.localizedDescription
break
}
- let localizedError = NSLocalizedString("This theme cannot be used because of data corruption in the Info.plist: %@.", comment: "Decoding key missing")
+ let localizedError = NSLocalizedString("alert.error.theme-data-corruption.%@", comment: "This theme cannot be used because of data corruption in the Info.plist: %@.")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, debugDescription) as String
default:
@@ -380,10 +380,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
DispatchQueue.main.async {
let alert = NSAlert()
alert.alertStyle = .warning
- alert.messageText = NSLocalizedString("Theme Error", comment: "Theme error")
+ alert.messageText = NSLocalizedString("alert.title.theme-error", comment: "Theme error")
alert.informativeText = informativeText
- alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK"))
-
+ alert.addButton(withTitle: NSLocalizedString("button.title.ok", comment: "OK"))
+
alert.buttons[0].keyEquivalent = "\r"
_ = alert.runModal()
@@ -608,8 +608,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
@MainActor @IBAction func showKeyboardShortcutsWindow(_ sender: Any?) {
if keyboardShortcutsWindowController == nil {
-
- keyboardShortcutsWindowController = WebViewWindowController(title: NSLocalizedString("Keyboard Shortcuts", comment: "window title"))
+
+ keyboardShortcutsWindowController = WebViewWindowController(title: NSLocalizedString("window.title.keyboard-shortcuts", comment: "Keyboard Shortcuts"))
let htmlFile = Bundle(for: type(of: self)).path(forResource: "KeyboardShortcuts", ofType: "html")!
keyboardShortcutsWindowController?.displayContents(of: htmlFile)
@@ -775,11 +775,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
@IBAction func debugClearImageCaches(_ sender: Any?) {
let alert = NSAlert()
alert.alertStyle = .warning
- alert.messageText = NSLocalizedString("Are you sure you want to clear the image caches? This will restart NetNewsWire to begin reloading the remote images.",
- comment: "Clear and restart confirmation message.")
- alert.addButton(withTitle: NSLocalizedString("Clear & Restart", comment: "Clear & Restart"))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel"))
-
+ alert.messageText = NSLocalizedString("alert.message.clear-image-cache-confirmation",
+ comment: "Are you sure you want to clear the image caches? This will restart NetNewsWire to begin reloading the remote images.")
+ alert.addButton(withTitle: NSLocalizedString("button.title.clear-and-restart", comment: "Clear & Restart"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel"))
+
let userChoice = alert.runModal()
if userChoice == .alertFirstButtonReturn {
CacheCleaner.purge()
@@ -872,7 +872,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
let alert = NSAlert()
alert.alertStyle = .informational
- let localizedMessageText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text")
+ let localizedMessageText = NSLocalizedString("alert.title.install-theme.%@.%@", comment: "Install theme “%@” by %@? — the order of the variables is theme name, author name")
alert.messageText = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name, theme.creatorName) as String
var attrs = [NSAttributedString.Key : Any]()
@@ -884,7 +884,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
attrs[.paragraphStyle] = titleParagraphStyle
let websiteText = NSMutableAttributedString()
- websiteText.append(NSAttributedString(string: NSLocalizedString("Author‘s website:", comment: "Author's Website"), attributes: attrs))
+ websiteText.append(NSAttributedString(string: NSLocalizedString("alert.title.authors-website", comment: "Author's website:"), attributes: attrs))
websiteText.append(NSAttributedString(string: "\n"))
@@ -899,10 +899,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
textView.drawsBackground = false
textView.textStorage?.setAttributedString(websiteText)
alert.accessoryView = textView
-
- alert.addButton(withTitle: NSLocalizedString("Install Theme", comment: "Install Theme"))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Theme"))
-
+
+ alert.addButton(withTitle: NSLocalizedString("button.title.install-theme", comment: "Install Theme"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel Install Theme"))
+
func importTheme() {
do {
try ArticleThemesManager.shared.importTheme(filename: filename)
@@ -920,12 +920,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
let alert = NSAlert()
alert.alertStyle = .warning
- let localizedMessageText = NSLocalizedString("The theme “%@” already exists. Overwrite it?", comment: "Overwrite theme")
+ let localizedMessageText = NSLocalizedString("alert.message.duplicate-theme.%@", comment: "The theme “%@” already exists. Overwrite it?")
alert.messageText = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name) as String
- alert.addButton(withTitle: NSLocalizedString("Overwrite", comment: "Overwrite"))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Theme"))
-
+ alert.addButton(withTitle: NSLocalizedString("button.title.overwrite", comment: "Overwrite"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel Install Theme"))
+
alert.beginSheetModal(for: window) { result in
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
importTheme()
@@ -946,12 +946,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
let alert = NSAlert()
alert.alertStyle = .informational
- alert.messageText = NSLocalizedString("Theme installed", comment: "Theme installed")
-
- let localizedInformativeText = NSLocalizedString("The theme “%@” has been installed.", comment: "Theme installed")
+ alert.messageText = NSLocalizedString("alert.title.theme-installed", comment: "Theme installed")
+
+ let localizedInformativeText = NSLocalizedString("alert.message.theme-installed.%@", comment: "The theme “%@” has been installed.")
alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, themeName) as String
-
- alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK"))
+
+ alert.addButton(withTitle: NSLocalizedString("button.title.ok", comment: "OK"))
alert.beginSheetModal(for: window)
}
@@ -975,9 +975,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
DispatchQueue.main.async {
let alert = NSAlert()
alert.alertStyle = .warning
- alert.messageText = NSLocalizedString("Twitter Integration Removed", comment: "Twitter Integration Removed")
- alert.informativeText = NSLocalizedString("Twitter has ended free access to the parts of the Twitter API that we need.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.", comment: "Twitter deprecation informative text.")
- alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK"))
+ alert.messageText = NSLocalizedString("alert.title.twitter-integration-removed", comment: "Twitter Integration Removed")
+ alert.informativeText = NSLocalizedString("alert.message.twitter-integration-removed", comment: "Twitter deprecation informative text.")
+ alert.addButton(withTitle: NSLocalizedString("button.title.ok", comment: "OK"))
alert.buttons[0].keyEquivalent = "\r"
alert.runModal()
}
diff --git a/Mac/Base.lproj/Preferences.storyboard b/Mac/Base.lproj/Preferences.storyboard
index ddf26b887..3c938cc9d 100644
--- a/Mac/Base.lproj/Preferences.storyboard
+++ b/Mac/Base.lproj/Preferences.storyboard
@@ -36,7 +36,7 @@
-
+
@@ -109,7 +109,7 @@
-
+
@@ -163,7 +163,7 @@
-
+
@@ -527,16 +527,16 @@
-
+
-
+
-
+
-
+
@@ -643,7 +643,7 @@
-
+
@@ -698,16 +698,16 @@
-
+
-
+
-
+
-
+
@@ -810,7 +810,7 @@
-
+
diff --git a/Mac/Browser.swift b/Mac/Browser.swift
index 7b0ad4d3f..68148ce8e 100644
--- a/Mac/Browser.swift
+++ b/Mac/Browser.swift
@@ -69,8 +69,8 @@ extension Browser {
let openInBackgroundPref = AppDefaults.shared.openInBrowserInBackground
return openInBackgroundPref ?
- NSLocalizedString("Open in Browser in Foreground", comment: "Open in Browser in Foreground menu item title") :
- NSLocalizedString("Open in Browser in Background", comment: "Open in Browser in Background menu item title")
+ NSLocalizedString("button.title.open-in-foreground", comment: "Open in Browser in Foreground") :
+ NSLocalizedString("button.title.open-in-background", comment: "Open in Browser in Background")
}
}
@@ -95,11 +95,11 @@ extension Browser {
if urlStrings.count > 20 {
let alert = NSAlert()
- let messageFormat = NSLocalizedString("Are you sure you want to open %ld articles in your browser?", comment: "Open in Browser confirmation alert message format")
+ let messageFormat = NSLocalizedString("alert.title.open-articles-in-browser.%ld", comment: "Are you sure you want to open %ld articles in your browser?")
alert.messageText = String.localizedStringWithFormat(messageFormat, urlStrings.count)
- let confirmButtonTitleFormat = NSLocalizedString("Open %ld Articles", comment: "Open URLs in Browser confirm button format")
+ let confirmButtonTitleFormat = NSLocalizedString("button.title.open-articles.%ld", comment: "Open %ld Articles")
alert.addButton(withTitle: String.localizedStringWithFormat(confirmButtonTitleFormat, urlStrings.count))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel button"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel button"))
if let window {
alert.beginSheetModal(for: window) { response in
diff --git a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift
index 72bca9099..884aed07c 100644
--- a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift
+++ b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift
@@ -27,7 +27,7 @@ import AppKit
updateSmartFeed()
}
}
- var windowTitle: String = NSLocalizedString("Smart Feed Inspector", comment: "Smart Feed Inspector window title")
+ var windowTitle: String = NSLocalizedString("window.title.smart-feed-inspector", comment: "Smart Feed Inspector")
func canInspect(_ objects: [Any]) -> Bool {
@@ -63,7 +63,7 @@ private extension BuiltinSmartFeedInspectorViewController {
func updateUI() {
nameTextField?.stringValue = smartFeed?.nameForDisplay ?? ""
- windowTitle = smartFeed?.nameForDisplay ?? NSLocalizedString("Smart Feed Inspector", comment: "Smart Feed Inspector window title")
+ windowTitle = smartFeed?.nameForDisplay ?? NSLocalizedString("window.title.smart-feed-inspector", comment: "Smart Feed Inspector")
smartFeedImageView?.image = smartFeed?.smallIcon?.image
}
}
diff --git a/Mac/Inspector/FolderInspectorViewController.swift b/Mac/Inspector/FolderInspectorViewController.swift
index 6e12f248d..8fa655396 100644
--- a/Mac/Inspector/FolderInspectorViewController.swift
+++ b/Mac/Inspector/FolderInspectorViewController.swift
@@ -32,7 +32,7 @@ import RSCore
updateFolder()
}
}
- var windowTitle: String = NSLocalizedString("Folder Inspector", comment: "Folder Inspector window title")
+ var windowTitle: String = NSLocalizedString("window.title.folder-inspector", comment: "Folder Inspector")
func canInspect(_ objects: [Any]) -> Bool {
@@ -102,7 +102,7 @@ private extension FolderInspectorViewController {
if nameTextField.stringValue != name {
nameTextField.stringValue = name
}
- windowTitle = folder?.nameForDisplay ?? NSLocalizedString("Folder Inspector", comment: "Folder Inspector window title")
+ windowTitle = folder?.nameForDisplay ?? NSLocalizedString("window.title.folder-inspector", comment: "Folder Inspector")
}
func renameFolderIfNecessary() {
diff --git a/Mac/Inspector/NothingInspectorViewController.swift b/Mac/Inspector/NothingInspectorViewController.swift
index 168513e5e..a4b6ae79c 100644
--- a/Mac/Inspector/NothingInspectorViewController.swift
+++ b/Mac/Inspector/NothingInspectorViewController.swift
@@ -19,7 +19,7 @@ import AppKit
updateTextFields()
}
}
- var windowTitle: String = NSLocalizedString("Inspector", comment: "Inspector window title")
+ var windowTitle: String = NSLocalizedString("window.title.inspector", comment: "Inspector")
func canInspect(_ objects: [Any]) -> Bool {
diff --git a/Mac/Inspector/WebFeedInspectorViewController.swift b/Mac/Inspector/WebFeedInspectorViewController.swift
index 123961828..fb8ceecd0 100644
--- a/Mac/Inspector/WebFeedInspectorViewController.swift
+++ b/Mac/Inspector/WebFeedInspectorViewController.swift
@@ -39,7 +39,7 @@ import UserNotifications
updateFeed()
}
}
- var windowTitle: String = NSLocalizedString("Feed Inspector", comment: "Feed Inspector window title")
+ var windowTitle: String = NSLocalizedString("window.title.feed-inspector", comment: "Feed Inspector")
func canInspect(_ objects: [Any]) -> Bool {
return objects.count == 1 && objects.first is WebFeed
@@ -138,7 +138,7 @@ private extension WebFeedInspectorViewController {
updateFeedURL()
updateNotifyAboutNewArticles()
updateIsReaderViewAlwaysOn()
- windowTitle = feed?.nameForDisplay ?? NSLocalizedString("Feed Inspector", comment: "Feed Inspector window title")
+ windowTitle = feed?.nameForDisplay ?? NSLocalizedString("window.title.feed-inspector", comment: "Feed Inspector")
view.needsLayout = true
if let webfeed = feed {
webfeed.isFeedProvider ? (isReaderViewAlwaysOnCheckBox?.isEnabled = false) : (isReaderViewAlwaysOnCheckBox?.isEnabled = true)
@@ -172,7 +172,7 @@ private extension WebFeedInspectorViewController {
}
func updateNotifyAboutNewArticles() {
- isNotifyAboutNewArticlesCheckBox?.title = feed?.notificationDisplayName ?? NSLocalizedString("Show notifications for new articles", comment: "Show notifications for new articles")
+ isNotifyAboutNewArticlesCheckBox?.title = feed?.notificationDisplayName ?? NSLocalizedString("checkbox.title.show-new-article-notifications", comment: "Show notifications for new articles")
isNotifyAboutNewArticlesCheckBox?.state = (feed?.isNotifyAboutNewArticles ?? false) ? .on : .off
}
@@ -194,10 +194,10 @@ private extension WebFeedInspectorViewController {
func showNotificationsDeniedError() {
let updateAlert = NSAlert()
updateAlert.alertStyle = .informational
- updateAlert.messageText = NSLocalizedString("Enable Notifications", comment: "Notifications")
- updateAlert.informativeText = NSLocalizedString("To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list.", comment: "To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list.")
- updateAlert.addButton(withTitle: NSLocalizedString("Open System Preferences", comment: "Open System Preferences"))
- updateAlert.addButton(withTitle: NSLocalizedString("Close", comment: "Close"))
+ updateAlert.messageText = NSLocalizedString("alert.title.enable-notifications", comment: "Enable Notifications")
+ updateAlert.informativeText = NSLocalizedString("alert.message.enable-notifications-in-system-settings", comment: "To enable notifications, open Notifications in System Settings, then find NetNewsWire in the list.")
+ updateAlert.addButton(withTitle: NSLocalizedString("button.title.open-system-settings", comment: "Open System Settings"))
+ updateAlert.addButton(withTitle: NSLocalizedString("button.title.close", comment: "Close"))
let modalResponse = updateAlert.runModal()
if modalResponse == .alertFirstButtonReturn {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.notifications")!)
diff --git a/Mac/MainWindow/About/AboutNetNewsWireView.swift b/Mac/MainWindow/About/AboutNetNewsWireView.swift
index 94937cc85..371dce693 100644
--- a/Mac/MainWindow/About/AboutNetNewsWireView.swift
+++ b/Mac/MainWindow/About/AboutNetNewsWireView.swift
@@ -20,17 +20,17 @@ struct AboutNetNewsWireView: View {
.resizable()
.frame(width: 75, height: 75)
- Text("NetNewsWire")
+ Text(verbatim: "NetNewsWire")
.font(.headline)
Text("\(Bundle.main.versionNumber) (\(Bundle.main.buildNumber))")
.foregroundColor(.secondary)
.font(.callout)
- Text("By Brent Simmons and the NetNewsWire team.")
+ Text("label.text.netnewswire-byline", comment: "By Brent Simmons and the NetNewsWire team.")
.font(.subheadline)
- Text("[netnewswire.com](https://netnewswire.com)")
+ Text("label.markdown.netnewswire-website", comment: "Markdown formatted link to netnewswire.com")
.font(.callout)
Spacer()
diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift
index 38c27414b..824687187 100644
--- a/Mac/MainWindow/AddFeed/AddFeedController.swift
+++ b/Mac/MainWindow/AddFeed/AddFeedController.swift
@@ -136,17 +136,17 @@ private extension AddFeedController {
func showAlreadySubscribedError(_ urlString: String) {
let alert = NSAlert()
alert.alertStyle = .informational
- alert.messageText = NSLocalizedString("Already subscribed", comment: "Feed finder")
- alert.informativeText = NSLocalizedString("Can’t add this feed because you’ve already subscribed to it.", comment: "Feed finder")
+ alert.messageText = AccountError.createErrorAlreadySubscribed.errorTitle
+ alert.informativeText = AccountError.createErrorAlreadySubscribed.localizedDescription
alert.beginSheetModal(for: hostWindow)
}
func showInitialDownloadError(_ error: Error) {
let alert = NSAlert()
alert.alertStyle = .informational
- alert.messageText = NSLocalizedString("Download Error", comment: "Feed finder")
+ alert.messageText = NSLocalizedString("alert.title.download-error", comment: "Download Error")
- let formatString = NSLocalizedString("Can’t add this feed because of a download error: “%@”", comment: "Feed finder")
+ let formatString = NSLocalizedString("alert.message.download-error.%@", comment: "Can’t add this feed because of a download error: “%@”")
let errorText = NSString.localizedStringWithFormat(formatString as NSString, error.localizedDescription)
alert.informativeText = errorText as String
alert.beginSheetModal(for: hostWindow)
@@ -155,15 +155,15 @@ private extension AddFeedController {
func showNoFeedsErrorMessage() {
let alert = NSAlert()
alert.alertStyle = .informational
- alert.messageText = NSLocalizedString("Feed not found", comment: "Feed finder")
- alert.informativeText = NSLocalizedString("Can’t add a feed because no feed was found.", comment: "Feed finder")
+ alert.messageText = AccountError.createErrorNotFound.errorTitle
+ alert.informativeText = AccountError.createErrorNotFound.localizedDescription
alert.beginSheetModal(for: hostWindow)
}
// MARK: Progress
func beginShowingProgress() {
- runIndeterminateProgressWithMessage(NSLocalizedString("Finding feed…", comment:"Feed finder"))
+ runIndeterminateProgressWithMessage(NSLocalizedString("label.text.finding-feed", comment:"Finding feed..."))
}
func endShowingProgress() {
diff --git a/Mac/MainWindow/AddRedditFeedWindowController.swift b/Mac/MainWindow/AddRedditFeedWindowController.swift
index 0d07abccb..fc744d96f 100644
--- a/Mac/MainWindow/AddRedditFeedWindowController.swift
+++ b/Mac/MainWindow/AddRedditFeedWindowController.swift
@@ -148,7 +148,7 @@ private extension AddRedditFeedWindowController {
animateShowHideFields(collapsed: false) {
self.accountLabel.isHidden = false
self.accountPopupButton.isHidden = false
- self.typeDescriptionLabel.stringValue = NSLocalizedString("Your personal Reddit frontpage", comment: "Home")
+ self.typeDescriptionLabel.stringValue = NSLocalizedString("label.text.reddit-front-page", comment: "Your personal Reddit frontpage")
self.subredditTextField.isHidden = true
self.addButton.isEnabled = true
}
@@ -157,7 +157,7 @@ private extension AddRedditFeedWindowController {
accountLabel.isHidden = true
accountPopupButton.isHidden = true
- typeDescriptionLabel.stringValue = NSLocalizedString("The best posts on Reddit for you", comment: "Popular")
+ typeDescriptionLabel.stringValue = NSLocalizedString("label.text.reddit-best-posts", comment: "The best posts on Reddit for you")
subredditTextField.isHidden = true
addButton.isEnabled = true
animateShowHideFields(collapsed: true)
@@ -166,7 +166,7 @@ private extension AddRedditFeedWindowController {
accountLabel.isHidden = true
accountPopupButton.isHidden = true
- typeDescriptionLabel.stringValue = NSLocalizedString("The most active posts", comment: "All")
+ typeDescriptionLabel.stringValue = NSLocalizedString("label.text.reddit-active-posts", comment: "The most active posts")
subredditTextField.isHidden = true
addButton.isEnabled = true
animateShowHideFields(collapsed: true)
@@ -178,12 +178,13 @@ private extension AddRedditFeedWindowController {
self.accountPopupButton.isHidden = true
if !self.subredditTextField.stringValue.isEmpty {
- self.typeDescriptionLabel.stringValue = NSLocalizedString("Posts from r/\(self.subredditTextField.stringValue)", comment: "Subreddit")
+ let subreddit = NSLocalizedString("label.text.posts-from-subreddit.%@", comment: "Posts from r/%@")
+ self.typeDescriptionLabel.stringValue = String(format: subreddit, self.subredditTextField.stringValue)
} else {
self.typeDescriptionLabel.stringValue = ""
}
- self.subredditTextField.placeholderString = NSLocalizedString("Subreddit", comment: "Search Term")
+ self.subredditTextField.placeholderString = NSLocalizedString("label.text.subreddit", comment: "Subreddit")
self.subredditTextField.isHidden = false
self.addButton.isEnabled = !self.subredditTextField.stringValue.isEmpty
}
diff --git a/Mac/MainWindow/ArticleExtractorButton.swift b/Mac/MainWindow/ArticleExtractorButton.swift
index 1ea097769..dc7f5b158 100644
--- a/Mac/MainWindow/ArticleExtractorButton.swift
+++ b/Mac/MainWindow/ArticleExtractorButton.swift
@@ -45,13 +45,13 @@ enum ArticleExtractorButtonState {
override func accessibilityLabel() -> String? {
switch buttonState {
case .error:
- return NSLocalizedString("Error - Reader View", comment: "Error - Reader View")
+ return NSLocalizedString("label.text.error-reader-view", comment: "Error - Reader View")
case .animated:
- return NSLocalizedString("Processing - Reader View", comment: "Processing - Reader View")
+ return NSLocalizedString("label.text.processing-reader-view", comment: "Processing - Reader View")
case .on:
- return NSLocalizedString("Selected - Reader View", comment: "Selected - Reader View")
+ return NSLocalizedString("label.text.selected-reader-view", comment: "Selected - Reader View")
case .off:
- return NSLocalizedString("Reader View", comment: "Reader View")
+ return NSLocalizedString("label.text.reader-view", comment: "Reader View")
}
}
diff --git a/Mac/MainWindow/Detail/DetailWebView.swift b/Mac/MainWindow/Detail/DetailWebView.swift
index 5bdf0cbfc..dd793dd23 100644
--- a/Mac/MainWindow/Detail/DetailWebView.swift
+++ b/Mac/MainWindow/Detail/DetailWebView.swift
@@ -15,7 +15,7 @@ import RSCore
weak var keyboardDelegate: KeyboardDelegate?
override func accessibilityLabel() -> String? {
- return NSLocalizedString("Article", comment: "Article")
+ return NSLocalizedString("label.text.article", comment: "Article")
}
// MARK: - NSResponder
diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift
index 514ad4040..5ed6e1a13 100644
--- a/Mac/MainWindow/MainWindowController.swift
+++ b/Mac/MainWindow/MainWindowController.swift
@@ -207,7 +207,7 @@ enum TimelineSourceMode {
let canCopyArticleURL = canCopyArticleURL()
if let item = item as? NSMenuItem {
- let format = NSLocalizedString("Copy Article URL", comment: "Copy Article URL");
+ let format = NSLocalizedString("button.title.copy-article-url", comment: "Copy Article URL");
item.title = String.localizedStringWithFormat(format, selectedArticles?.count ?? 0)
}
@@ -290,7 +290,7 @@ enum TimelineSourceMode {
let sidebarIsShowing = !splitViewItem.isCollapsed
if let menuItem = item as? NSMenuItem {
- let title = sidebarIsShowing ? NSLocalizedString("Hide Sidebar", comment: "Menu item") : NSLocalizedString("Show Sidebar", comment: "Menu item")
+ let title = sidebarIsShowing ? NSLocalizedString("button.title.hide-sidebar", comment: "Hide Sidebar") : NSLocalizedString("button.title.show-sidebar", comment: "Show Sidebar")
menuItem.title = title
}
@@ -581,7 +581,7 @@ enum TimelineSourceMode {
let menu = NSMenu()
let alwaysUseReaderViewItem = NSMenuItem()
- alwaysUseReaderViewItem.title = NSLocalizedString("Always Use Reader View", comment: "Always Use Reader View")
+ alwaysUseReaderViewItem.title = NSLocalizedString("button.title.always-use-reader-view", comment: "Always Use Reader View")
alwaysUseReaderViewItem.target = self
alwaysUseReaderViewItem.action = #selector(alwaysUseReaderView)
alwaysUseReaderViewItem.state = {
@@ -852,57 +852,57 @@ extension MainWindowController: NSToolbarDelegate {
switch itemIdentifier {
case .sidebarToggle:
- let title = NSLocalizedString("Toggle Sidebar", comment: "Toggle Sidebar")
+ let title = NSLocalizedString("button.title.toggle-sidebar", comment: "Toggle Sidebar")
return buildToolbarButton(.toggleSidebar, title, AppAssets.sidebarToggleImage, "toggleTheSidebar:")
case .refresh:
- let title = NSLocalizedString("Refresh", comment: "Refresh")
+ let title = NSLocalizedString("button.title.refresh", comment: "Refresh")
return buildToolbarButton(.refresh, title, AppAssets.refreshImage, "refreshAll:")
case .newSidebarItemMenu:
let toolbarItem = NSMenuToolbarItem(itemIdentifier: .newSidebarItemMenu)
toolbarItem.image = AppAssets.addNewSidebarItemImage
- let description = NSLocalizedString("Add Item", comment: "Add Item")
+ let description = NSLocalizedString("button.title.add-item", comment: "Add Item")
toolbarItem.toolTip = description
toolbarItem.label = description
toolbarItem.menu = buildNewSidebarItemMenu()
return toolbarItem
case .markAllAsRead:
- let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
+ let title = NSLocalizedString("button.title.mark-all-as-read.titlecase", comment: "Mark All as Read")
return buildToolbarButton(.markAllAsRead, title, AppAssets.markAllAsReadImage, "markAllAsRead:")
case .markAboveAsRead:
- let title = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read")
+ let title = NSLocalizedString("button.title.mark-above-as-read.titlecase", comment: "Mark Above as Read")
return buildToolbarButton(.markAboveAsRead, title, AppAssets.markAboveAsReadImage, "markAboveAsRead:")
case .markBelowAsRead:
- let title = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read")
+ let title = NSLocalizedString("button.title.mark-below-as-read.titlecase", comment: "Mark Below as Read")
return buildToolbarButton(.markBelowAsRead, title, AppAssets.markBelowAsReadImage, "markBelowAsRead:")
case .toggleReadArticlesFilter:
- let title = NSLocalizedString("Read Articles Filter", comment: "Read Articles Filter")
+ let title = NSLocalizedString("button.title.read-articles-filter", comment: "Read Articles Filter")
return buildToolbarButton(.toggleReadArticlesFilter, title, AppAssets.filterInactive, "toggleReadArticlesFilter:")
case .timelineTrackingSeparator:
return NSTrackingSeparatorToolbarItem(identifier: .timelineTrackingSeparator, splitView: splitViewController!.splitView, dividerIndex: 1)
case .markRead:
- let title = NSLocalizedString("Mark Read", comment: "Mark Read")
+ let title = NSLocalizedString("button.title.mark-read", comment: "Mark Read")
return buildToolbarButton(.markRead, title, AppAssets.readClosedImage, "toggleRead:")
case .markStar:
- let title = NSLocalizedString("Star", comment: "Star")
+ let title = NSLocalizedString("button.title.mark-star", comment: "Star")
return buildToolbarButton(.markStar, title, AppAssets.starOpenImage, "toggleStarred:")
case .nextUnread:
- let title = NSLocalizedString("Next Unread", comment: "Next Unread")
+ let title = NSLocalizedString("button.title.next-read", comment: "Next Unread")
return buildToolbarButton(.nextUnread, title, AppAssets.nextUnreadImage, "nextUnread:")
case .readerView:
let toolbarItem = RSToolbarItem(itemIdentifier: .readerView)
toolbarItem.autovalidates = true
- let description = NSLocalizedString("Reader View", comment: "Reader View")
+ let description = NSLocalizedString("button.title.reader-view", comment: "Reader View")
toolbarItem.toolTip = description
toolbarItem.label = description
let button = ArticleExtractorButton()
@@ -914,7 +914,7 @@ extension MainWindowController: NSToolbarDelegate {
return toolbarItem
case .share:
- let title = NSLocalizedString("Share", comment: "Share")
+ let title = NSLocalizedString("button.title.share", comment: "Share")
let image = AppAssets.shareImage
if #available(macOS 13.0, *) {
// `item.view` is required for properly positioning the sharing picker.
@@ -929,25 +929,25 @@ extension MainWindowController: NSToolbarDelegate {
}
case .openInBrowser:
- let title = NSLocalizedString("Open in Browser", comment: "Open in Browser")
+ let title = NSLocalizedString("button.title.open-in-browser", comment: "Open in Browser")
return buildToolbarButton(.openInBrowser, title, AppAssets.openInBrowserImage, "openArticleInBrowser:")
case .articleThemeMenu:
articleThemeMenuToolbarItem.image = AppAssets.articleTheme
- let description = NSLocalizedString("Article Theme", comment: "Article Theme")
+ let description = NSLocalizedString("button.title.article-theme", comment: "Article Theme")
articleThemeMenuToolbarItem.toolTip = description
articleThemeMenuToolbarItem.label = description
return articleThemeMenuToolbarItem
case .search:
let toolbarItem = NSSearchToolbarItem(itemIdentifier: .search)
- let description = NSLocalizedString("Search", comment: "Search")
+ let description = NSLocalizedString("button.title.search", comment: "Search")
toolbarItem.toolTip = description
toolbarItem.label = description
return toolbarItem
case .cleanUp:
- let title = NSLocalizedString("Clean Up", comment: "Clean Up")
+ let title = NSLocalizedString("button.title.clean-up", comment: "Clean Up")
return buildToolbarButton(.cleanUp, title, AppAssets.cleanUpImage, "cleanUp:")
default:
@@ -1162,7 +1162,7 @@ private extension MainWindowController {
result = false
}
- let commandName = markingRead ? NSLocalizedString("Mark as Read", comment: "Command") : NSLocalizedString("Mark as Unread", comment: "Command")
+ let commandName = markingRead ? NSLocalizedString("button.title.mark-as-read", comment: "Mark as Read") : NSLocalizedString("button.title.mark-as-unread", comment: "Mark as Unread")
if let toolbarItem = item as? NSToolbarItem {
toolbarItem.toolTip = commandName
@@ -1249,7 +1249,7 @@ private extension MainWindowController {
result = false
}
- let commandName = starring ? NSLocalizedString("Mark as Starred", comment: "Command") : NSLocalizedString("Mark as Unstarred", comment: "Command")
+ let commandName = starring ? NSLocalizedString("button.title.mark-as-starred", comment: "Mark as Starred") : NSLocalizedString("button.title.mark-as-unstarred", comment: "Mark as Unstarred")
if let toolbarItem = item as? NSToolbarItem {
toolbarItem.toolTip = commandName
@@ -1270,15 +1270,15 @@ private extension MainWindowController {
func validateToggleReadFeeds(_ item: NSValidatedUserInterfaceItem) -> Bool {
guard let menuItem = item as? NSMenuItem else { return false }
- let showCommand = NSLocalizedString("Show Read Feeds", comment: "Command")
- let hideCommand = NSLocalizedString("Hide Read Feeds", comment: "Command")
+ let showCommand = NSLocalizedString("button.title.show-read-feeds", comment: "Show Read Feeds")
+ let hideCommand = NSLocalizedString("button.title.hide-read-feeds", comment: "Hide Read Feeds")
menuItem.title = sidebarViewController?.isReadFiltered ?? false ? showCommand : hideCommand
return true
}
func validateToggleReadArticles(_ item: NSValidatedUserInterfaceItem) -> Bool {
- let showCommand = NSLocalizedString("Show Read Articles", comment: "Command")
- let hideCommand = NSLocalizedString("Hide Read Articles", comment: "Command")
+ let showCommand = NSLocalizedString("button.title.show-read-articles", comment: "Show Read Articles")
+ let hideCommand = NSLocalizedString("button.title.hide-read-articles", comment: "Hide Read Articles")
guard let isReadFiltered = timelineContainerViewController?.isReadFiltered else {
(item as? NSMenuItem)?.title = hideCommand
@@ -1330,14 +1330,14 @@ private extension MainWindowController {
func updateWindowTitle() {
guard timelineSourceMode != .search else {
- let localizedLabel = NSLocalizedString("Search: %@", comment: "Search")
+ let localizedLabel = NSLocalizedString("window.title.search.%@", comment: "Search: %@")
window?.title = NSString.localizedStringWithFormat(localizedLabel as NSString, searchString ?? "") as String
window?.subtitle = ""
return
}
func setSubtitle(_ count: Int) {
- let localizedLabel = NSLocalizedString("%d unread", comment: "Unread")
+ let localizedLabel = NSLocalizedString("window.subtitle.unread-count.%d", comment: "%d unread")
let formattedLabel = NSString.localizedStringWithFormat(localizedLabel as NSString, count)
window?.subtitle = formattedLabel as String
}
@@ -1349,7 +1349,7 @@ private extension MainWindowController {
}
guard selectedObjects.count == 1 else {
- window?.title = NSLocalizedString("Multiple", comment: "Multiple")
+ window?.title = NSLocalizedString("window.title.multiple", comment: "Multiple")
let unreadCount = selectedObjects.reduce(0, { result, selectedObject in
if let unreadCountProvider = selectedObject as? UnreadCountProvider {
return result + unreadCountProvider.unreadCount
@@ -1443,17 +1443,17 @@ private extension MainWindowController {
let menu = NSMenu()
let newWebFeedItem = NSMenuItem()
- newWebFeedItem.title = NSLocalizedString("New Web Feed…", comment: "New Web Feed")
+ newWebFeedItem.title = NSLocalizedString("button.title.new-web-feed", comment: "New Web Feed...")
newWebFeedItem.action = Selector(("showAddWebFeedWindow:"))
menu.addItem(newWebFeedItem)
let newRedditFeedItem = NSMenuItem()
- newRedditFeedItem.title = NSLocalizedString("New Reddit Feed…", comment: "New Reddit Feed")
+ newRedditFeedItem.title = NSLocalizedString("button.title.new-reddit-feed", comment: "New Reddit Feed...")
newRedditFeedItem.action = Selector(("showAddRedditFeedWindow:"))
menu.addItem(newRedditFeedItem)
let newFolderFeedItem = NSMenuItem()
- newFolderFeedItem.title = NSLocalizedString("New Folder…", comment: "New Folder")
+ newFolderFeedItem.title = NSLocalizedString("button.title.open-new-folder", comment: "New Folder...")
newFolderFeedItem.action = Selector(("showAddFolderWindow:"))
menu.addItem(newFolderFeedItem)
diff --git a/Mac/MainWindow/NNW3/NNW3ImportController.swift b/Mac/MainWindow/NNW3/NNW3ImportController.swift
index b36c1e7ee..1db4ed415 100644
--- a/Mac/MainWindow/NNW3/NNW3ImportController.swift
+++ b/Mac/MainWindow/NNW3/NNW3ImportController.swift
@@ -73,7 +73,7 @@ private extension NNW3ImportController {
panel.allowsOtherFileTypes = false
panel.accessoryView = accessoryViewController.view
panel.isAccessoryViewDisclosed = true
- panel.title = NSLocalizedString("Choose a Subscriptions.plist file:", comment: "NNW3 Import")
+ panel.title = NSLocalizedString("panel.title.select-opml-file", comment: "Choose a Subscriptions.plist file:")
panel.beginSheetModal(for: window) { modalResult in
guard modalResult == .OK, let subscriptionsPlistURL = panel.url else {
diff --git a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift
index 0ff8284e0..ad709cec5 100644
--- a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift
+++ b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift
@@ -77,10 +77,10 @@ import Account
let panel = NSSavePanel()
panel.allowedFileTypes = ["opml"]
panel.allowsOtherFileTypes = false
- panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML")
- panel.title = NSLocalizedString("Export OPML", comment: "Export OPML")
- panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML")
- panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML")
+ panel.prompt = NSLocalizedString("panel.prompt.export-opml", comment: "Export OPML")
+ panel.title = NSLocalizedString("panel.title.export-opml", comment: "Export OPML")
+ panel.nameFieldLabel = NSLocalizedString("panel.textfield.export-opml-destination", comment: "Export to:")
+ panel.message = NSLocalizedString("panel.message.export-opml", comment: "Choose a location for the exported OPML file.")
panel.isExtensionHidden = false
let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces)
diff --git a/Mac/MainWindow/SharingServiceDelegate.swift b/Mac/MainWindow/SharingServiceDelegate.swift
index 01e78fc3f..50d5b0161 100644
--- a/Mac/MainWindow/SharingServiceDelegate.swift
+++ b/Mac/MainWindow/SharingServiceDelegate.swift
@@ -17,12 +17,12 @@ import AppKit
}
func sharingService(_ sharingService: NSSharingService, willShareItems items: [Any]) {
- sharingService.subject = items
+ let selectedItemTitles = items
.compactMap { item in
let writer = item as? ArticlePasteboardWriter
return writer?.article.title
}
- .joined(separator: ", ")
+ sharingService.subject = ListFormatter().string(from: selectedItemTitles)
}
func sharingService(_ sharingService: NSSharingService, sourceWindowForShareItems items: [Any], sharingContentScope: UnsafeMutablePointer) -> NSWindow? {
diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift
index ffc1162be..5659c5e3d 100644
--- a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift
+++ b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift
@@ -112,7 +112,7 @@ import RSTree
override func accessibilityLabel() -> String? {
if unreadCount > 0 {
- let unreadLabel = NSLocalizedString("unread", comment: "Unread label for accessibility")
+ let unreadLabel = NSLocalizedString("label.text.unread", comment: "unread")
return "\(name) \(unreadCount) \(unreadLabel)"
} else {
return name
diff --git a/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift b/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift
index 41d1dd07b..94330508c 100644
--- a/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift
+++ b/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift
@@ -35,7 +35,7 @@ protocol RenameWindowControllerDelegate {
newTitleTextField.stringValue = originalTitle!
- let prompt = NSLocalizedString("Rename %@ to:", comment: "Rename sheet")
+ let prompt = NSLocalizedString("textfield.prompt.rename-to.%@", comment: "Rename %@ to:")
let localizedPrompt = NSString.localizedStringWithFormat(prompt as NSString, originalTitle!)
renamePrompt.stringValue = localizedPrompt as String
diff --git a/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift b/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift
index 571137231..fb2ad9e20 100644
--- a/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift
+++ b/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift
@@ -19,22 +19,22 @@ import Account
if nodes.count == 1 {
if let folder = nodes.first?.representedObject as? Folder {
- alert.messageText = NSLocalizedString("Delete Folder", comment: "Delete Folder")
- let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the “%@” folder?", comment: "Folder delete text")
+ alert.messageText = NSLocalizedString("alert.title.delete-folder", comment: "Delete Folder")
+ let localizedInformativeText = NSLocalizedString("alert.message.delete-folder.%@", comment: "Are you sure you want to delete the “%@” folder?")
alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, folder.nameForDisplay) as String
} else if let feed = nodes.first?.representedObject as? Feed {
- alert.messageText = NSLocalizedString("Delete Feed", comment: "Delete Feed")
- let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the “%@” feed?", comment: "Feed delete text")
+ alert.messageText = NSLocalizedString("alert.title.delete-feed", comment: "Delete Feed")
+ let localizedInformativeText = NSLocalizedString("alert.message.delete-feed.%@", comment: "Are you sure you want to delete the “%@” feed?")
alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, feed.nameForDisplay) as String
}
} else {
- alert.messageText = NSLocalizedString("Delete Items", comment: "Delete Items")
- let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the %d selected items?", comment: "Items delete text")
+ alert.messageText = NSLocalizedString("alert.title.delete-items", comment: "Delete Items")
+ let localizedInformativeText = NSLocalizedString("alert.message.delete-items.%d", comment: "Are you sure you want to delete the %d selected items?")
alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, nodes.count) as String
}
- alert.addButton(withTitle: NSLocalizedString("Delete", comment: "Delete Account"))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Delete Account"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.delete", comment: "Delete Account"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel Delete Account"))
return alert
}
diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift
index 67e6dc90e..0d05dc4e5 100644
--- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift
+++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift
@@ -206,10 +206,10 @@ extension SidebarViewController {
func showNotificationsNotEnabledAlert() {
DispatchQueue.main.async {
let alert = NSAlert()
- alert.messageText = NSLocalizedString("Notifications are not enabled", comment: "Notifications are not enabled.")
- alert.informativeText = NSLocalizedString("You can enable NetNewsWire notifications in System Preferences.", comment: "Notifications are not enabled.")
- alert.addButton(withTitle: NSLocalizedString("Open System Preferences", comment: "Open System Preferences"))
- alert.addButton(withTitle: NSLocalizedString("Dismiss", comment: "Dismiss"))
+ alert.messageText = NSLocalizedString("alert.title.notifications-not-enabled", comment: "Notifications are not enabled.")
+ alert.informativeText = NSLocalizedString("alert.message.enable-notifications-in-settings", comment: "You can enable NetNewsWire notifications in System Settings.")
+ alert.addButton(withTitle: NSLocalizedString("button.title.open-settings", comment: "Open Settings"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.dismiss", comment: "Dismiss"))
let userChoice = alert.runModal()
if userChoice == .alertFirstButtonReturn {
let config = NSWorkspace.OpenConfiguration()
@@ -258,8 +258,8 @@ private extension SidebarViewController {
let menu = NSMenu(title: "")
- menu.addItem(withTitle: NSLocalizedString("New Feed", comment: "Command"), action: #selector(AppDelegate.showAddWebFeedWindow(_:)), keyEquivalent: "")
- menu.addItem(withTitle: NSLocalizedString("New Folder", comment: "Command"), action: #selector(AppDelegate.showAddFolderWindow(_:)), keyEquivalent: "")
+ menu.addItem(withTitle: NSLocalizedString("button.title.new-feed", comment: "New Feed"), action: #selector(AppDelegate.showAddWebFeedWindow(_:)), keyEquivalent: "")
+ menu.addItem(withTitle: NSLocalizedString("button.title.new-folder", comment: "New Folder"), action: #selector(AppDelegate.showAddFolderWindow(_:)), keyEquivalent: "")
return menu
}
@@ -270,7 +270,7 @@ private extension SidebarViewController {
if webFeed.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([webFeed]))
- let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
+ let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("button.title.mark-as-read-older-than", comment: "Mark as Read Older Than"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([webFeed])
menu.addItem(catchUpMenuItem)
@@ -280,16 +280,16 @@ private extension SidebarViewController {
}
if let homePageURL = webFeed.homePageURL, let _ = URL(string: homePageURL) {
- let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
+ let item = menuItem(NSLocalizedString("button.title.open-home-page", comment: "Open Home Page"), #selector(openHomePageFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
menu.addItem(item)
menu.addItem(NSMenuItem.separator())
}
- let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url.decodedURLString ?? webFeed.url)
+ let copyFeedURLItem = menuItem(NSLocalizedString("button.title.copy-feed-url", comment: "Copy Feed URL"), #selector(copyURLFromContextualMenu(_:)), webFeed.url.decodedURLString ?? webFeed.url)
menu.addItem(copyFeedURLItem)
if let homePageURL = webFeed.homePageURL {
- let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
+ let item = menuItem(NSLocalizedString("button.title.copy-home-page-url", comment: "Copy Home Page URL"), #selector(copyURLFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
menu.addItem(item)
}
menu.addItem(NSMenuItem.separator())
@@ -306,7 +306,7 @@ private extension SidebarViewController {
if !webFeed.isFeedProvider {
- let articleExtractorText = NSLocalizedString("Always Use Reader View", comment: "Always Use Reader View")
+ let articleExtractorText = NSLocalizedString("button.title.always-use-reader-view", comment: "Always Use Reader View")
let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), webFeed)
if webFeed.isArticleExtractorAlwaysOn == nil || webFeed.isArticleExtractorAlwaysOn! == false {
@@ -332,7 +332,7 @@ private extension SidebarViewController {
if folder.unreadCount > 0 {
menu.addItem(markAllReadMenuItem([folder]))
- let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
+ let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("button.title.mark-as-read-older-than", comment: "Mark as Read Older Than"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([folder])
menu.addItem(catchUpMenuItem)
@@ -360,7 +360,7 @@ private extension SidebarViewController {
}
}
- let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
+ let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("button.title.mark-as-read-older-than", comment: "Mark as Read Older Than"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu([smartFeed])
menu.addItem(catchUpMenuItem)
menu.setSubmenu(catchUpSubMenu, for: catchUpMenuItem)
@@ -374,7 +374,7 @@ private extension SidebarViewController {
if anyObjectInArrayHasNonZeroUnreadCount(objects) {
menu.addItem(markAllReadMenuItem(objects))
- let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("Mark as Read Older Than", comment: "Command Submenu"), action: nil, keyEquivalent: "")
+ let catchUpMenuItem = NSMenuItem(title: NSLocalizedString("button.title.mark-as-read-older-than", comment: "Mark as Read Older Than"), action: nil, keyEquivalent: "")
let catchUpSubMenu = catchUpSubMenu(objects)
menu.addItem(catchUpMenuItem)
@@ -391,31 +391,31 @@ private extension SidebarViewController {
func markAllReadMenuItem(_ objects: [Any]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Mark All as Read", comment: "Command"), #selector(markObjectsReadFromContextualMenu(_:)), objects)
+ return menuItem(NSLocalizedString("button.title.mark-all-as-read.titlecase", comment: "Mark All as Read"), #selector(markObjectsReadFromContextualMenu(_:)), objects)
}
func catchUpSubMenu(_ objects: [Any]) -> NSMenu {
- let menu = NSMenu(title: "Catch up to articles older than...")
+ let menu = NSMenu(title: NSLocalizedString("menu.title.catch-up-to-articles", comment: "Catch up to articles older than..."))
- menu.addItem(menuItem(NSLocalizedString("1 day", comment: "Command"), #selector(markObjectsReadOlderThanOneDayFromContextualMenu(_:)), objects))
- menu.addItem(menuItem(NSLocalizedString("2 days", comment: "Command"), #selector(markObjectsReadOlderThanTwoDaysFromContextualMenu(_:)), objects))
- menu.addItem(menuItem(NSLocalizedString("3 days", comment: "Command"), #selector(markObjectsReadOlderThanThreeDaysFromContextualMenu(_:)), objects))
- menu.addItem(menuItem(NSLocalizedString("1 week", comment: "Command"), #selector(markObjectsReadOlderThanOneWeekFromContextualMenu(_:)), objects))
- menu.addItem(menuItem(NSLocalizedString("2 weeks", comment: "Command"), #selector(markObjectsReadOlderThanTwoWeeksFromContextualMenu(_:)), objects))
- menu.addItem(menuItem(NSLocalizedString("1 month", comment: "Command"), #selector(markObjectsReadOlderThanOneMonthFromContextualMenu(_:)), objects))
- menu.addItem(menuItem(NSLocalizedString("1 year", comment: "Command"), #selector(markObjectsReadOlderThanOneYearFromContextualMenu(_:)), objects))
+ menu.addItem(menuItem(NSLocalizedString("button.title.1-day", comment: "1 day"), #selector(markObjectsReadOlderThanOneDayFromContextualMenu(_:)), objects))
+ menu.addItem(menuItem(NSLocalizedString("button.title.2-days", comment: "2 days"), #selector(markObjectsReadOlderThanTwoDaysFromContextualMenu(_:)), objects))
+ menu.addItem(menuItem(NSLocalizedString("button.title.3-days", comment: "3 days"), #selector(markObjectsReadOlderThanThreeDaysFromContextualMenu(_:)), objects))
+ menu.addItem(menuItem(NSLocalizedString("button.title.1-week", comment: "1 week"), #selector(markObjectsReadOlderThanOneWeekFromContextualMenu(_:)), objects))
+ menu.addItem(menuItem(NSLocalizedString("button.title.2-weeks", comment: "2 weeks"), #selector(markObjectsReadOlderThanTwoWeeksFromContextualMenu(_:)), objects))
+ menu.addItem(menuItem(NSLocalizedString("button.title.1-month", comment: "1 month"), #selector(markObjectsReadOlderThanOneMonthFromContextualMenu(_:)), objects))
+ menu.addItem(menuItem(NSLocalizedString("button.title.1-year", comment: "1 year"), #selector(markObjectsReadOlderThanOneYearFromContextualMenu(_:)), objects))
return menu
}
func deleteMenuItem(_ objects: [Any]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Delete", comment: "Command"), #selector(deleteFromContextualMenu(_:)), objects)
+ return menuItem(NSLocalizedString("button.title.delete", comment: "Delete"), #selector(deleteFromContextualMenu(_:)), objects)
}
func renameMenuItem(_ object: Any) -> NSMenuItem {
- return menuItem(NSLocalizedString("Rename", comment: "Command"), #selector(renameFromContextualMenu(_:)), object)
+ return menuItem(NSLocalizedString("button.title.rename", comment: "Rename"), #selector(renameFromContextualMenu(_:)), object)
}
func anyObjectInArrayHasNonZeroUnreadCount(_ objects: [Any]) -> Bool {
diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift
index 53bb79adb..bb7591bdb 100644
--- a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift
+++ b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift
@@ -11,7 +11,7 @@ import Articles
struct TimelineCellData {
- private static let noText = NSLocalizedString("(No Text)", comment: "No Text")
+ private static let noText = NSLocalizedString("label.text.no-text", comment: "(No Text)")
let title: String
let attributedTitle: NSAttributedString
diff --git a/Mac/MainWindow/Timeline/TimelineTableView.swift b/Mac/MainWindow/Timeline/TimelineTableView.swift
index f5d8a9469..8d997e229 100644
--- a/Mac/MainWindow/Timeline/TimelineTableView.swift
+++ b/Mac/MainWindow/Timeline/TimelineTableView.swift
@@ -14,7 +14,7 @@ import RSCore
weak var keyboardDelegate: KeyboardDelegate?
override func accessibilityLabel() -> String? {
- return NSLocalizedString("Timeline", comment: "Timeline")
+ return NSLocalizedString("label.text.timeline", comment: "Timeline")
}
// MARK: - NSResponder
diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift
index 5e5425728..bdd1879a7 100644
--- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift
+++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift
@@ -227,7 +227,7 @@ private extension TimelineViewController {
return nil
}
- let menu = NSMenu(title: NSLocalizedString("Share", comment: "Share menu name"))
+ let menu = NSMenu(title: NSLocalizedString("button.title.share", comment: "Share menu name"))
services.forEach { (service) in
service.delegate = sharingServiceDelegate
let menuItem = NSMenuItem(title: service.menuItemTitle, action: #selector(performShareServiceFromContextualMenu(_:)), keyEquivalent: "")
@@ -242,34 +242,34 @@ private extension TimelineViewController {
func markReadMenuItem(_ articles: [Article]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Mark as Read", comment: "Command"), #selector(markArticlesReadFromContextualMenu(_:)), articles)
+ return menuItem(NSLocalizedString("button.title.mark-as-read", comment: "Mark as Read"), #selector(markArticlesReadFromContextualMenu(_:)), articles)
}
func markUnreadMenuItem(_ articles: [Article]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Mark as Unread", comment: "Command"), #selector(markArticlesUnreadFromContextualMenu(_:)), articles)
+ return menuItem(NSLocalizedString("button.title.mark-as-unread", comment: "Mark as Unread"), #selector(markArticlesUnreadFromContextualMenu(_:)), articles)
}
func markStarredMenuItem(_ articles: [Article]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Mark as Starred", comment: "Command"), #selector(markArticlesStarredFromContextualMenu(_:)), articles)
+ return menuItem(NSLocalizedString("button.title.mark-as-starred", comment: "Mark as Starred"), #selector(markArticlesStarredFromContextualMenu(_:)), articles)
}
func markUnstarredMenuItem(_ articles: [Article]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Mark as Unstarred", comment: "Command"), #selector(markArticlesUnstarredFromContextualMenu(_:)), articles)
+ return menuItem(NSLocalizedString("button.title.mark-as-unstarred", comment: "Mark as Unstarred"), #selector(markArticlesUnstarredFromContextualMenu(_:)), articles)
}
func markAboveReadMenuItem(_ articles: [Article]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Mark Above as Read", comment: "Command"), #selector(markAboveArticlesReadFromContextualMenu(_:)), articles)
+ return menuItem(NSLocalizedString("button.title-mark-above-as-read.titlecase", comment: "Mark Above as Read"), #selector(markAboveArticlesReadFromContextualMenu(_:)), articles)
}
func markBelowReadMenuItem(_ articles: [Article]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Mark Below as Read", comment: "Command"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles)
+ return menuItem(NSLocalizedString("button.title-mark-below-as-read.titlecase", comment: "Mark Below as Read"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles)
}
func selectFeedInSidebarMenuItem(_ feed: WebFeed) -> NSMenuItem {
- let localizedMenuText = NSLocalizedString("Select “%@” in Sidebar", comment: "Command")
+ let localizedMenuText = NSLocalizedString("button.title.select-in-sidebar.%@", comment: "Select “%@” in Sidebar")
let formattedMenuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay)
return menuItem(formattedMenuText as String, #selector(selectFeedInSidebarFromContextualMenu(_:)), feed)
}
@@ -283,14 +283,14 @@ private extension TimelineViewController {
return nil
}
- let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
+ let localizedMenuText = NSLocalizedString("button.title.mark-all-as-read.%@", comment: "Mark All as Read in “%@”")
let menuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
return menuItem(menuText, #selector(markAllInFeedAsRead(_:)), articles)
}
func openInBrowserMenuItem(_ urlStrings: [String]) -> NSMenuItem {
- return menuItem(NSLocalizedString("Open in Browser", comment: "Command"), #selector(openInBrowserFromContextualMenu(_:)), urlStrings)
+ return menuItem(NSLocalizedString("button.title.open-in-browser", comment: "Open in Browser"), #selector(openInBrowserFromContextualMenu(_:)), urlStrings)
}
func openInBrowserReversedMenuItem(_ urlStrings: [String]) -> NSMenuItem {
@@ -301,13 +301,13 @@ private extension TimelineViewController {
}
func copyArticleURLsMenuItem(_ urlStrings: [String?]) -> NSMenuItem {
- let format = NSLocalizedString("Copy Article URL", comment: "Command")
+ let format = NSLocalizedString("button.title.copy-article-urls.%ld", comment: "Copy Article URL or Copy Article URLs (if more than one)")
let title = String.localizedStringWithFormat(format, urlStrings.count)
return menuItem(title, #selector(copyURLFromContextualMenu(_:)), urlStrings)
}
func copyExternalURLMenuItem(_ urlString: String) -> NSMenuItem {
- return menuItem(NSLocalizedString("Copy External URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString)
+ return menuItem(NSLocalizedString("button.title.copy-external-url", comment: "Copy External URL"), #selector(copyURLFromContextualMenu(_:)), urlString)
}
diff --git a/Mac/MainWindow/URLPasteboardWriter+NetNewsWire.swift b/Mac/MainWindow/URLPasteboardWriter+NetNewsWire.swift
index 0d3a9488e..0a70612f7 100644
--- a/Mac/MainWindow/URLPasteboardWriter+NetNewsWire.swift
+++ b/Mac/MainWindow/URLPasteboardWriter+NetNewsWire.swift
@@ -20,8 +20,8 @@ import RSCore
if urlStrings.contains(nil), !AppDefaults.shared.hasSeenNotAllArticlesHaveURLsAlert {
let alert = NSAlert()
- alert.messageText = NSLocalizedString("Some articles don’t have links, so they weren't copied.", comment: "\"Some articles have no links\" copy alert message text")
- alert.informativeText = NSLocalizedString("You won't see this message again.", comment: "You won't see this message again")
+ alert.messageText = NSLocalizedString("alert.message.articles-without-links", comment: "Some articles don’t have links, so they weren't copied.")
+ alert.informativeText = NSLocalizedString("alert.informative.will-not-see-again", comment: "You won't see this message again")
if let window {
alert.beginSheetModal(for: window)
diff --git a/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift b/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift
index 945453122..1576ecfe4 100644
--- a/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift
+++ b/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift
@@ -13,7 +13,7 @@ enum AccountsAddCloudKitWindowControllerError: LocalizedError {
case iCloudDriveMissing
var errorDescription: String? {
- return NSLocalizedString("Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Preferences.", comment: "Unable to add iCloud Account.")
+ return NSLocalizedString("error.description.cloudkit-unavailable", comment: "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.")
}
}
diff --git a/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift b/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift
index 373ffea30..5ebeb07d0 100644
--- a/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift
+++ b/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift
@@ -23,7 +23,7 @@ import Account
override func windowDidLoad() {
super.windowDidLoad()
- localAccountNameTextField.stringValue = NSLocalizedString("Create a local account on your Mac.", comment: "Account Local")
+ localAccountNameTextField.stringValue = NSLocalizedString("textfield.text.create-a-local-account", comment: "Create a local account on your Mac.")
}
// MARK: API
diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift
index 687e0c864..3ca520f6a 100644
--- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift
+++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift
@@ -34,13 +34,13 @@ import Secrets
override func windowDidLoad() {
if let account = account, let credentials = try? account.retrieveCredentials(type: .basic) {
usernameTextField.stringValue = credentials.username
- actionButton.title = NSLocalizedString("Update", comment: "Update")
- signInTextField.stringValue = NSLocalizedString("Update your Feedbin account credentials.", comment: "SignIn")
+ actionButton.title = NSLocalizedString("button.title.update", comment: "Update")
+ signInTextField.stringValue = NSLocalizedString("textfield.text.update-feedbin-credentials", comment: "Update your Feedbin account credentials.")
noAccountTextField.isHidden = true
createNewAccountButton.isHidden = true
} else {
- actionButton.title = NSLocalizedString("Create", comment: "Add Account")
- signInTextField.stringValue = NSLocalizedString("Sign in to your Feedbin account.", comment: "SignIn")
+ actionButton.title = NSLocalizedString("button.title.create", comment: "Create")
+ signInTextField.stringValue = NSLocalizedString("textfield.text.sign-in-feedbin", comment: "Sign in to your Feedbin account.")
}
enableAutofill()
@@ -66,12 +66,13 @@ import Secrets
self.errorMessageLabel.stringValue = ""
guard !usernameTextField.stringValue.isEmpty && !passwordTextField.stringValue.isEmpty else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Username & password required.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.userNameAndPasswordRequired.localizedDescription
+
return
}
guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: .feedbin, username: usernameTextField.stringValue) else {
- self.errorMessageLabel.stringValue = NSLocalizedString("There is already a Feedbin account with that username created.", comment: "Duplicate Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.duplicateAccount.localizedDescription
return
}
@@ -92,7 +93,8 @@ import Secrets
case .success(let validatedCredentials):
guard let validatedCredentials = validatedCredentials else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription
+
return
}
@@ -115,13 +117,13 @@ import Secrets
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
} catch {
- self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)")
}
case .failure:
- self.errorMessageLabel.stringValue = NSLocalizedString("Network error. Try again later.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription
}
diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift
index 9cb726e17..bdb0441d5 100644
--- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift
+++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift
@@ -34,13 +34,13 @@ import Secrets
override func windowDidLoad() {
if let account = account, let credentials = try? account.retrieveCredentials(type: .newsBlurBasic) {
usernameTextField.stringValue = credentials.username
- actionButton.title = NSLocalizedString("Update", comment: "Update")
- signInTextField.stringValue = NSLocalizedString("Update your NewsBlur account credentials.", comment: "SignIn")
+ actionButton.title = NSLocalizedString("button.title.update", comment: "Update")
+ signInTextField.stringValue = NSLocalizedString("textfield.text.update-newsblur-credentials", comment: "Update your NewsBlur account credentials.")
noAccountTextField.isHidden = true
createNewAccountButton.isHidden = true
} else {
- actionButton.title = NSLocalizedString("Create", comment: "Create")
- signInTextField.stringValue = NSLocalizedString("Sign in to your NewsBlur account.", comment: "SignIn")
+ actionButton.title = NSLocalizedString("button.title.create", comment: "Create")
+ signInTextField.stringValue = NSLocalizedString("textfield.text.sign-in-newsblur", comment: "Sign in to your NewsBlur account.")
}
enableAutofill()
usernameTextField.becomeFirstResponder()
@@ -63,12 +63,12 @@ import Secrets
self.errorMessageLabel.stringValue = ""
guard !usernameTextField.stringValue.isEmpty else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Username required.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.userNameRequired.localizedDescription
return
}
guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: .newsBlur, username: usernameTextField.stringValue) else {
- self.errorMessageLabel.stringValue = NSLocalizedString("There is already a NewsBlur account with that username created.", comment: "Duplicate Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.duplicateAccount.localizedDescription
return
}
@@ -88,7 +88,7 @@ import Secrets
switch result {
case .success(let validatedCredentials):
guard let validatedCredentials = validatedCredentials else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription
return
}
@@ -113,14 +113,12 @@ import Secrets
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
} catch {
- self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)")
}
case .failure:
-
- self.errorMessageLabel.stringValue = NSLocalizedString("Network error. Try again later.", comment: "Credentials Error")
-
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription
}
}
}
diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift
index 66a0173cb..05aaa2dff 100644
--- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift
+++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift
@@ -66,12 +66,12 @@ protocol AccountsPreferencesAddAccountDelegate {
let alert = NSAlert()
alert.alertStyle = .warning
- let deletePrompt = NSLocalizedString("Delete", comment: "Delete")
- alert.messageText = "\(deletePrompt) “\(acctName)”?"
- alert.informativeText = NSLocalizedString("Are you sure you want to delete the account “\(acctName)”? This cannot be undone.", comment: "Delete text")
+ let deletePrompt = NSLocalizedString("alert.title.delete.%@", comment: "Delete “%@“")
+ alert.messageText = String(format: deletePrompt, acctName)
+ alert.informativeText = NSLocalizedString("alert.message.cannot-undo-action", comment: "Cannot undo action")
- alert.addButton(withTitle: NSLocalizedString("Delete", comment: "Delete Account"))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Delete Account"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.delete", comment: "Delete Account"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel Delete Account"))
alert.beginSheetModal(for: view.window!) { [weak self] result in
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
@@ -191,13 +191,13 @@ extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelega
private func runAwaitingFeedlyLoginAlertModal(forLifetimeOf operation: OAuthAccountAuthorizationOperation) {
let alert = NSAlert()
alert.alertStyle = .informational
- alert.messageText = NSLocalizedString("Waiting for access to Feedly",
+ alert.messageText = NSLocalizedString("alert.title.waiting-for-feedly-access",
comment: "Alert title when adding a Feedly account and waiting for authorization from the user.")
- alert.informativeText = NSLocalizedString("A web browser will open the Feedly login for you to authorize access.",
- comment: "Alert informative text when adding a Feedly account and waiting for authorization from the user.")
+ alert.informativeText = NSLocalizedString("alert.message.feedly-web-browser-information",
+ comment: "A web browser will open the Feedly login for you to authorize access.")
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel"))
let attachedWindow = self.view.window!
@@ -242,9 +242,9 @@ private extension AccountsPreferencesViewController {
if tableView.selectedRow == -1 {
var helpText = ""
if sortedAccounts.count == 0 {
- helpText = NSLocalizedString("Add an account by clicking the + button.", comment: "Add Account Explainer")
+ helpText = NSLocalizedString("label.text.add-account-explainer", comment: "Add an account by clicking the + button.")
} else {
- helpText = NSLocalizedString("Select an account or add a new account by clicking the + button.", comment: "Add Account Explainer")
+ helpText = NSLocalizedString("label.text.select-or-add-account-explainer", comment: "Select an account or add a new account by clicking the + button.")
}
let textHostingController = NSHostingController(rootView:
diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift
index e282f327e..1e94b8e72 100644
--- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift
+++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift
@@ -41,25 +41,25 @@ import Secrets
switch accountType {
case .freshRSS:
titleImageView.image = AppAssets.accountFreshRSS
- titleLabel.stringValue = NSLocalizedString("Sign in to your FreshRSS account.", comment: "FreshRSS")
- noAccountTextField.stringValue = NSLocalizedString("Don’t have a FreshRSS instance?", comment: "No FreshRSS")
- createAccountButton.title = NSLocalizedString("Find out more", comment: "No FreshRSS Button")
- apiURLTextField.placeholderString = NSLocalizedString("fresh.rss.net/api/greader.php", comment: "FreshRSS API Helper")
+ titleLabel.stringValue = NSLocalizedString("label.text.sign-in-freshrss", comment: "Sign in to your FreshRSS account.")
+ noAccountTextField.stringValue = NSLocalizedString("label.text.no-fresh-rss", comment: "Don’t have a FreshRSS instance?")
+ createAccountButton.title = NSLocalizedString("label.text.find-out-more", comment: "Find out more")
+ apiURLTextField.placeholderString = "fresh.rss.net/api/greader.php" // not localized.
case .inoreader:
titleImageView.image = AppAssets.accountInoreader
- titleLabel.stringValue = NSLocalizedString("Sign in to your InoReader account.", comment: "InoReader")
+ titleLabel.stringValue = NSLocalizedString("label.text.sign-in-inoreader", comment: "Sign in to your InoReader account.")
gridView.row(at: 2).isHidden = true
- noAccountTextField.stringValue = NSLocalizedString("Don’t have an InoReader account?", comment: "No InoReader")
+ noAccountTextField.stringValue = NSLocalizedString("label.text.no-inoreader", comment: "Don’t have an InoReader account?")
case .bazQux:
titleImageView.image = AppAssets.accountBazQux
- titleLabel.stringValue = NSLocalizedString("Sign in to your BazQux account.", comment: "BazQux")
+ titleLabel.stringValue = NSLocalizedString("label.text.sign-in-bazqux", comment: "Sign in to your BazQux account.")
gridView.row(at: 2).isHidden = true
- noAccountTextField.stringValue = NSLocalizedString("Don’t have a BazQux account?", comment: "No BazQux")
+ noAccountTextField.stringValue = NSLocalizedString("label.text.no-bazqux", comment: "Don’t have a BazQux account?")
case .theOldReader:
titleImageView.image = AppAssets.accountTheOldReader
- titleLabel.stringValue = NSLocalizedString("Sign in to your The Old Reader account.", comment: "The Old Reader")
+ titleLabel.stringValue = NSLocalizedString("label.text.sign-in-old-reader", comment: "Sign in to your The Old Reader account.")
gridView.row(at: 2).isHidden = true
- noAccountTextField.stringValue = NSLocalizedString("Don’t have a The Old Reader account?", comment: "No OldReader")
+ noAccountTextField.stringValue = NSLocalizedString("label.text.no-old-reader", comment: "Don’t have a The Old Reader account?")
default:
break
}
@@ -68,9 +68,9 @@ import Secrets
if let account = account, let credentials = try? account.retrieveCredentials(type: .readerBasic) {
usernameTextField.stringValue = credentials.username
apiURLTextField.stringValue = account.endpointURL?.absoluteString ?? ""
- actionButton.title = NSLocalizedString("Update", comment: "Update")
+ actionButton.title = NSLocalizedString("button.title.update", comment: "Update")
} else {
- actionButton.title = NSLocalizedString("Create", comment: "Create")
+ actionButton.title = NSLocalizedString("button.title.create", comment: "Create")
}
enableAutofill()
@@ -94,17 +94,17 @@ import Secrets
self.errorMessageLabel.stringValue = ""
guard !usernameTextField.stringValue.isEmpty && !passwordTextField.stringValue.isEmpty else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Username, password & API URL are required.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.userNameAndPasswordRequired.localizedDescription
return
}
guard let accountType = accountType, !(accountType == .freshRSS && apiURLTextField.stringValue.isEmpty) else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Username, password & API URL are required.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.userNamePasswordAndURLRequired.localizedDescription
return
}
guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: accountType, username: usernameTextField.stringValue) else {
- self.errorMessageLabel.stringValue = NSLocalizedString("There is already an account of this type with that username created.", comment: "Duplicate Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.duplicateAccount.localizedDescription
return
}
@@ -112,7 +112,7 @@ import Secrets
switch accountType {
case .freshRSS:
guard let inputURL = URL(string: apiURLTextField.stringValue) else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Invalid API URL.", comment: "Invalid API URL")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidURL.localizedDescription
return
}
apiURL = inputURL
@@ -123,7 +123,7 @@ import Secrets
case .theOldReader:
apiURL = URL(string: ReaderAPIVariant.theOldReader.host)!
default:
- self.errorMessageLabel.stringValue = NSLocalizedString("Unrecognized account type.", comment: "Bad account type")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.unrecognizedAccount.localizedDescription
return
}
@@ -143,7 +143,7 @@ import Secrets
switch result {
case .success(let validatedCredentials):
guard let validatedCredentials = validatedCredentials else {
- self.errorMessageLabel.stringValue = NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription
return
}
@@ -170,12 +170,12 @@ import Secrets
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
} catch {
- self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription
self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)")
}
case .failure:
- self.errorMessageLabel.stringValue = NSLocalizedString("Network error. Try again later.", comment: "Credentials Error")
+ self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription
}
}
diff --git a/Mac/Preferences/Accounts/AddAccountHelpView.swift b/Mac/Preferences/Accounts/AddAccountHelpView.swift
index 4fa1fd54a..7a40b1804 100644
--- a/Mac/Preferences/Accounts/AddAccountHelpView.swift
+++ b/Mac/Preferences/Accounts/AddAccountHelpView.swift
@@ -43,8 +43,8 @@ struct AddAccountHelpView: View {
}
.alert(isPresented: $iCloudUnavailableError, content: {
- Alert(title: Text(NSLocalizedString("Error", comment: "Error")),
- message: Text(NSLocalizedString("You've already set up an iCloud account.", comment: "Error")),
+ Alert(title: Text(NSLocalizedString("alert.title.error", comment: "Error")),
+ message: Text(NSLocalizedString("alert.message.cloudkit-already-setup", comment: "You've already set up an iCloud account.")),
dismissButton: Alert.Button.cancel({
iCloudUnavailableError = false
}))
diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift
index fb8fd6687..8a0b44c30 100644
--- a/Mac/Preferences/Accounts/AddAccountsView.swift
+++ b/Mac/Preferences/Accounts/AddAccountsView.swift
@@ -20,13 +20,13 @@ enum AddAccountSections: Int, CaseIterable {
var sectionHeader: String {
switch self {
case .local:
- return NSLocalizedString("Local", comment: "Local Account")
+ return NSLocalizedString("label.text.local", comment: "Local")
case .icloud:
- return NSLocalizedString("iCloud", comment: "iCloud Account")
+ return NSLocalizedString("label.text.cloudkit", comment: "iCloud")
case .web:
- return NSLocalizedString("Web", comment: "Web Account")
+ return NSLocalizedString("label.text.web", comment: "Web")
case .selfhosted:
- return NSLocalizedString("Self-hosted", comment: "Self hosted Account")
+ return NSLocalizedString("label.text.self-hosted", comment: "Self-hosted")
case .allOrdered:
return ""
}
@@ -35,13 +35,13 @@ enum AddAccountSections: Int, CaseIterable {
var sectionFooter: String {
switch self {
case .local:
- return NSLocalizedString("Local accounts do not sync feeds across devices", comment: "Local Account")
+ return NSLocalizedString("label.text.local-account-explainer", comment: "Local accounts do not sync your feeds across devices")
case .icloud:
- return NSLocalizedString("Your iCloud account syncs your feeds across your Mac and iOS devices", comment: "iCloud Account")
+ return NSLocalizedString("label.text.cloudkit-explainer", comment: "Your iCloud account syncs your feeds across your Mac and iOS devices")
case .web:
- return NSLocalizedString("Web accounts sync your feeds across all your devices", comment: "Web Account")
+ return NSLocalizedString("label.text.web-account-explainer", comment: "Web accounts sync your feeds across all your devices")
case .selfhosted:
- return NSLocalizedString("Self-hosted accounts sync your feeds across all your devices", comment: "Self hosted Account")
+ return NSLocalizedString("label.text.self-hosted-accounts-explainer", comment: "Self-hosted accounts sync your feeds across all your devices")
case .allOrdered:
return ""
}
@@ -87,7 +87,7 @@ struct AddAccountsView: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
- Text("Choose an account type to add...")
+ Text("label.text.choose-account-to-add", comment: "Choose an account type to add...")
.font(.headline)
.padding()
@@ -106,20 +106,20 @@ struct AddAccountsView: View {
Button(action: {
parent?.dismiss(nil)
}, label: {
- Text("Cancel")
+ Text("button.title.cancel")
.frame(width: 76)
})
- .help("Cancel")
+ .help("label.text.cancel")
.keyboardShortcut(.cancelAction)
Button(action: {
addAccountDelegate?.presentSheetForAccount(selectedAccount)
parent?.dismiss(nil)
}, label: {
- Text("Continue")
+ Text("button.title.continue", comment: "Continue")
.frame(width: 76)
})
- .help("Add Account")
+ .help("label.text.add-account")
.keyboardShortcut(.defaultAction)
}
.padding(.top, 12)
@@ -133,7 +133,7 @@ struct AddAccountsView: View {
var localAccount: some View {
VStack(alignment: .leading) {
- Text("Local")
+ Text("label.text.local", comment: "Local")
.font(.headline)
.padding(.horizontal)
@@ -164,7 +164,7 @@ struct AddAccountsView: View {
var icloudAccount: some View {
VStack(alignment: .leading) {
- Text("iCloud")
+ Text("label.text.cloudkit", comment: "iCloud")
.font(.headline)
.padding(.horizontal)
.padding(.top, 8)
@@ -196,7 +196,7 @@ struct AddAccountsView: View {
@ViewBuilder
var webAccounts: some View {
VStack(alignment: .leading) {
- Text("Web")
+ Text("label.text.web", comment: "Web")
.font(.headline)
.padding(.horizontal)
.padding(.top, 8)
@@ -234,7 +234,7 @@ struct AddAccountsView: View {
var selfhostedAccounts: some View {
VStack(alignment: .leading) {
- Text("Self-hosted")
+ Text("label.text.self-hosted", comment: "Self-hosted")
.font(.headline)
.padding(.horizontal)
.padding(.top, 8)
diff --git a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift
index 5f8d0c113..a9d799047 100644
--- a/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift
+++ b/Mac/Preferences/ExtensionPoints/EnableExtensionPointView.swift
@@ -25,7 +25,7 @@ struct EnableExtensionPointView: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) {
- Text("Choose an extension to add...")
+ Text("label.text.choose-extension-to-add", comment: "Choose an extension to add...")
.font(.headline)
.padding()
@@ -37,7 +37,7 @@ struct EnableExtensionPointView: View {
Button(action: {
parent?.dismiss(nil)
}, label: {
- Text("Cancel")
+ Text("button.title.cancel", comment: "Cancel")
.frame(width: 80)
})
.help("Cancel")
@@ -72,7 +72,7 @@ struct EnableExtensionPointView: View {
VStack(alignment: .leading) {
let extensionPointTypeNames = Self.feedProviderExtensionPointTypes.map { String(describing: $0) }
if extensionPointTypeNames.count > 0 {
- Text("Feed Provider")
+ Text("label.text.feed-provider", comment: "Feed Provider")
.font(.headline)
.padding(.horizontal)
@@ -95,7 +95,7 @@ struct EnableExtensionPointView: View {
.pickerStyle(RadioGroupPickerStyle())
.offset(x: 7.5, y: 0)
- Text("An extension that makes websites appear to provide RSS feeds for their content.")
+ Text("label.text.feed-provider-explainer", comment: "An extension that makes websites appear to provide RSS feeds for their content.")
.foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
@@ -113,7 +113,7 @@ struct EnableExtensionPointView: View {
VStack(alignment: .leading) {
let extensionPointTypeNames = Self.sendToCommandExtensionPointTypes.map { String(describing: $0) }
if extensionPointTypeNames.count > 0 {
- Text("Third-Party Integration")
+ Text("label.text.third-party-integration", comment: "Third-Party Integration")
.font(.headline)
.padding(.horizontal)
.padding(.top, 8)
@@ -137,7 +137,7 @@ struct EnableExtensionPointView: View {
.pickerStyle(RadioGroupPickerStyle())
.offset(x: 7.5, y: 0)
- Text("An extension that enables a share menu item that passes article data to a third-party application.")
+ Text("label.text.share-extension-explainer", comment: "An extension that enables a share menu item that passes article data to a third-party application.")
.foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
diff --git a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift
index 16a797a80..9c4d1f15b 100644
--- a/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift
+++ b/Mac/Preferences/ExtensionPoints/ExtensionPointPreferencesViewController.swift
@@ -64,13 +64,13 @@ protocol ExtensionPointPreferencesEnabler: AnyObject {
let alert = NSAlert()
alert.alertStyle = .warning
- let prompt = NSLocalizedString("Deactivate", comment: "Deactivate")
- alert.messageText = "\(prompt) “\(extensionPoint.title)”?"
+ let prompt = NSLocalizedString("alert.title.deactivate-extension.%@", comment: "Deactivate “%@“?")
+ alert.messageText = String(format: prompt, extensionPoint.title)
let extensionPointTypeTitle = extensionPoint.extensionPointID.extensionPointType.title
- alert.informativeText = NSLocalizedString("Are you sure you want to deactivate the \(extensionPointTypeTitle) extension “\(extensionPoint.title)”?", comment: "Deactivate text")
+ alert.informativeText = NSLocalizedString("alert.message.cannot-undo-action", comment: "You can't undo this action.")
- alert.addButton(withTitle: NSLocalizedString("Deactivate", comment: "Deactivate Extension"))
- alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Deactivate Extension"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.deactivate", comment: "Deactivate Extension"))
+ alert.addButton(withTitle: NSLocalizedString("button.title.cancel", comment: "Cancel Deactivate Extension"))
alert.beginSheetModal(for: view.window!) { [weak self] result in
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
@@ -201,12 +201,12 @@ private extension ExtensionPointPreferencesViewController {
if tableView.selectedRow == -1 {
var helpText = ""
if ExtensionPointManager.shared.availableExtensionPointTypes.count == 0 {
- helpText = NSLocalizedString("You've added all available extensions.", comment: "Extension Explainer")
+ helpText = NSLocalizedString("label.text.added-all-extensions", comment: "You've added all available extensions.")
}
else if activeExtensionPoints.count == 0 {
- helpText = NSLocalizedString("Add an extension by clicking the + button.", comment: "Extension Explainer")
+ helpText = NSLocalizedString("label.text.add-extension", comment: "Add an extension by clicking the + button.")
} else {
- helpText = NSLocalizedString("Select an extension or add a new extension by clicking the + button.", comment: "Extension Explainer")
+ helpText = NSLocalizedString("label.text.select-or-add-extension", comment: "Select an extension or add a new extension by clicking the + button.")
}
if let controller = children.first {
@@ -244,12 +244,12 @@ private extension ExtensionPointPreferencesViewController {
if tableView.selectedRow == -1 {
var helpText = ""
if ExtensionPointManager.shared.availableExtensionPointTypes.count == 0 {
- helpText = NSLocalizedString("You've added all available extensions.", comment: "Extension Explainer")
+ helpText = NSLocalizedString("label.text.added-all-extensions", comment: "You've added all available extensions.")
}
else if activeExtensionPoints.count == 0 {
- helpText = NSLocalizedString("Add an extension by clicking the + button.", comment: "Extension Explainer")
+ helpText = NSLocalizedString("label.text.add-extension", comment: "Add an extension by clicking the + button.")
} else {
- helpText = NSLocalizedString("Select an extension or add a new extension by clicking the + button.", comment: "Extension Explainer")
+ helpText = NSLocalizedString("label.text.select-or-add-extension", comment: "Select an extension or add a new extension by clicking the + button.")
}
let textHostingController = NSHostingController(rootView: EnableExtensionPointHelpView(helpText: helpText, preferencesController: self))
diff --git a/Mac/Preferences/General/GeneralPrefencesViewController.swift b/Mac/Preferences/General/GeneralPrefencesViewController.swift
index 0b55a756d..581de2688 100644
--- a/Mac/Preferences/General/GeneralPrefencesViewController.swift
+++ b/Mac/Preferences/General/GeneralPrefencesViewController.swift
@@ -109,7 +109,7 @@ private extension GeneralPreferencesViewController {
let defaultBrowser = MacWebBrowser.default
- let defaultBrowserFormat = NSLocalizedString("System Default (%@)", comment: "Default browser item title format")
+ let defaultBrowserFormat = NSLocalizedString("button.title.default-browser.%@", comment: "System Default (%@)")
let defaultBrowserTitle = String(format: defaultBrowserFormat, defaultBrowser.name!)
let item = NSMenuItem(title: defaultBrowserTitle, action: nil, keyEquivalent: "")
let icon = defaultBrowser.icon!
@@ -148,10 +148,10 @@ private extension GeneralPreferencesViewController {
func showNotificationsDeniedError() {
let updateAlert = NSAlert()
updateAlert.alertStyle = .informational
- updateAlert.messageText = NSLocalizedString("Enable Notifications", comment: "Notifications")
- updateAlert.informativeText = NSLocalizedString("To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list.", comment: "To enable notifications, open Notifications in System Preferences, then find NetNewsWire in the list.")
- updateAlert.addButton(withTitle: NSLocalizedString("Open System Preferences", comment: "Open System Preferences"))
- updateAlert.addButton(withTitle: NSLocalizedString("Close", comment: "Close"))
+ updateAlert.messageText = NSLocalizedString("alert.title.enable-notifications", comment: "Enable Notifications")
+ updateAlert.informativeText = NSLocalizedString("alert.message.enable-notifications-in-system-settings", comment: "To enable notifications, open Notifications in System Settings, then find NetNewsWire in the list.")
+ updateAlert.addButton(withTitle: NSLocalizedString("button.title.open-system-settings", comment: "Open System Settings"))
+ updateAlert.addButton(withTitle: NSLocalizedString("button.title.close", comment: "Close"))
let modalResponse = updateAlert.runModal()
if modalResponse == .alertFirstButtonReturn {
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.notifications")!)
diff --git a/Mac/Preferences/PreferencesWindowController.swift b/Mac/Preferences/PreferencesWindowController.swift
index f4d7b898f..081edc724 100644
--- a/Mac/Preferences/PreferencesWindowController.swift
+++ b/Mac/Preferences/PreferencesWindowController.swift
@@ -35,13 +35,13 @@ private struct ToolbarItemIdentifier {
private let toolbarItemSpecs: [PreferencesToolbarItemSpec] = {
var specs = [PreferencesToolbarItemSpec]()
specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.General,
- name: NSLocalizedString("General", comment: "Preferences"),
+ name: NSLocalizedString("button.title.general", comment: "General"),
image: AppAssets.preferencesToolbarGeneralImage)]
specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Accounts,
- name: NSLocalizedString("Accounts", comment: "Preferences"),
+ name: NSLocalizedString("button.title.accounts", comment: "Account"),
image: AppAssets.preferencesToolbarAccountsImage)]
specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Extensions,
- name: NSLocalizedString("Extensions", comment: "Preferences"),
+ name: NSLocalizedString("button.title.extensions", comment: "Extensions"),
image: AppAssets.preferencesToolbarExtensionsImage)]
// Omit the Advanced Preferences for now because the Software Update related functionality is
@@ -50,7 +50,7 @@ private struct ToolbarItemIdentifier {
// of the content in this tab.
#if !MAC_APP_STORE
specs += [PreferencesToolbarItemSpec(identifierRawValue: ToolbarItemIdentifier.Advanced,
- name: NSLocalizedString("Advanced", comment: "Preferences"),
+ name: NSLocalizedString("button.title.advanced", comment: "Advanced"),
image: AppAssets.preferencesToolbarAdvancedImage)]
#endif
return specs
diff --git a/Mac/Resources/en-GB.lproj/InfoPlist.strings b/Mac/Resources/en-GB.lproj/InfoPlist.strings
new file mode 100644
index 000000000..72e482ac9
--- /dev/null
+++ b/Mac/Resources/en-GB.lproj/InfoPlist.strings
@@ -0,0 +1,12 @@
+/* Bundle name */
+"CFBundleName" = "NetNewsWire";
+
+/* (No Comment) */
+"NetNewsWire Theme" = "NetNewsWire Theme";
+
+/* Privacy - AppleEvents Sending Usage Description */
+"NSAppleEventsUsageDescription" = "NetNewsWire communicates with other apps on your Mac when you choose to share an article.";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2002-2022 Brent Simmons. All rights reserved.";
+
diff --git a/Mac/Resources/en-GB.lproj/Localizable.strings b/Mac/Resources/en-GB.lproj/Localizable.strings
new file mode 100644
index 000000000..a15770112
--- /dev/null
+++ b/Mac/Resources/en-GB.lproj/Localizable.strings
@@ -0,0 +1,702 @@
+/* No comment provided by engineer. */
+"%@ (%@)" = "%1$@ (%2$@)";
+
+/* On My iPad */
+"account.name.ipad" = "On My iPad";
+
+/* On My iPhone */
+"account.name.iphone" = "On My iPhone";
+
+/* On My Mac */
+"account.name.mac" = "On My Mac";
+
+/* See articles in “%@” */
+"activity.title.see-article-in.folder.%@" = "See articles in “%@”";
+
+/* See first unread article */
+"activity.title.see-first-unread-article" = "See first unread article";
+
+/* Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings. */
+"alert.error.cloudkit-missing" = "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.";
+
+/* There is already an account of that type with that username created. */
+"alert.error.duplicate-account-username" = "There is already an account of that type with that username created.";
+
+/* Invalid API URL. */
+"alert.error.invalid-api-url" = "Invalid API URL.";
+
+/* Error message: The user provided an invalid username or password. */
+"alert.error.invalid-username-or-password" = "A username or password is required.";
+
+/* Error message: Unable to save due a Keychain error. */
+"alert.error.keychain-error" = "Unable to save account credentials due to a Keychain error.";
+
+/* Network error. Please try later. */
+"alert.error.network-error" = "A network error has occurred. Please try later.";
+
+/* This theme cannot be used because of data corruption in the Info.plist: %@. */
+"alert.error.theme-data-corruption.%@" = "This theme cannot be used because of data corruption in the Info.plist: %@.";
+
+/* Error message: This theme shares the same name as a provided theme and cannot be imported. */
+"alert.error.theme-duplicate-of-provided" = "This theme shares the same name as a provided theme and cannot be imported.";
+
+/* This theme cannot be used because the the key—“%@”—is not found in the Info.plist. */
+"alert.error.theme-key-not-found.%@" = "This theme cannot be used because the the key—“%@”—is not found in the Info.plist.";
+
+/* This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist */
+"alert.error.theme-type-mismatch.%@" = "This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist";
+
+/* This theme cannot be used because the the value—“%@”—is not found in the Info.plist. */
+"alert.error.theme-value-missing.%@" = "This theme cannot be used because the the value—“%@”—is not found in the Info.plist.";
+
+/* The account type in invalid. */
+"alert.error.unrecognized-account" = "The account type is not recognised.";
+
+/* Error message: The user must provide a username and password. */
+"alert.error.username-and-password-required" = "A username and password are required.";
+
+/* The user must provide a username, password, and URL. */
+"alert.error.username-password-url-required" = "A username, password, and API URL are required.";
+
+/* Username required. */
+"alert.error.username-required" = "A username is required.";
+
+/* You won't see this message again */
+"alert.informative.will-not-see-again" = "You won't see this message again";
+
+/* Some articles don’t have links, so they weren't copied. */
+"alert.message.articles-without-links" = "Some articles don’t have links, so they weren't copied.";
+
+/* Cannot undo action
+ You can't undo this action. */
+"alert.message.cannot-undo-action" = "You can't undo this action.";
+
+/* Are you sure you want to clear the image caches? This will restart NetNewsWire to begin reloading the remote images. */
+"alert.message.clear-image-cache-confirmation" = "Are you sure you want to clear the image caches? This will restart NetNewsWire to begin reloading the remote images.";
+
+/* You've already set up an iCloud account. */
+"alert.message.cloudkit-already-setup" = "You've already set up an iCloud account.";
+
+/* Are you sure you want to delete the “%@” feed? */
+"alert.message.delete-feed.%@" = "Are you sure you want to delete the “%@” feed?";
+
+/* Are you sure you want to delete the “%@” folder? */
+"alert.message.delete-folder.%@" = "Are you sure you want to delete the “%@” folder?";
+
+/* Are you sure you want to delete the %d selected items? */
+"alert.message.delete-items.%d" = "Are you sure you want to delete the %d selected items?";
+
+/* Can’t add this feed because of a download error: “%@” */
+"alert.message.download-error.%@" = "Can’t add this feed because of a download error: “%@”";
+
+/* The theme “%@” already exists. Overwrite it? */
+"alert.message.duplicate-theme.%@" = "The theme “%@” already exists. Overwrite it?";
+
+/* You can enable NetNewsWire notifications in System Settings. */
+"alert.message.enable-notifications-in-settings" = "You can enable NetNewsWire notifications in System Settings.";
+
+/* To enable notifications, open Notifications in System Settings, then find NetNewsWire in the list. */
+"alert.message.enable-notifications-in-system-settings" = "To enable notifications, open Notifications in System Settings, then find NetNewsWire in the list.";
+
+
+/* A web browser will open the Feedly login for you to authorize access. */
+"alert.message.feedly-web-browser-information" = "A web browser will open the Feedly login for you to authorise access.";
+
+/* The theme “%@” has been installed. */
+"alert.message.theme-installed.%@" = "The theme “%@” has been installed.";
+
+/* Twitter Deprecation Message */
+"alert.message.twitter-deprecation-message" = "On February 1, 2023, Twitter announced the end of free access to the Twitter API, effective February 9.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.";
+
+/* Author's website: */
+"alert.title.authors-website" = "Author's website:";
+
+/* Deactivate “%@“? */
+"alert.title.deactivate-extension.%@" = "Deactivate “%@“?";
+
+/* Delete Feed */
+"alert.title.delete-feed" = "Delete Feed";
+
+/* Delete Folder */
+"alert.title.delete-folder" = "Delete Folder";
+
+/* Delete Items */
+"alert.title.delete-items" = "Delete Items";
+
+/* Delete “%@“ */
+"alert.title.delete.%@" = "Delete “%@“";
+
+/* Download Error */
+"alert.title.download-error" = "Download Error";
+
+/* Enable Notifications */
+"alert.title.enable-notifications" = "Enable Notifications";
+
+/* Error */
+"alert.title.error" = "Error";
+
+/* Install theme “%@” by %@? — the order of the variables is theme name, author name */
+"alert.title.install-theme.%@.%@" = "Install theme “%@” by %@?";
+
+/* Notifications are not enabled. */
+"alert.title.notifications-not-enabled" = "Notifications are not enabled.";
+
+/* Are you sure you want to open %ld articles in your browser? */
+"alert.title.open-articles-in-browser.%ld" = "Are you sure you want to open %ld articles in your browser?";
+
+/* Theme error */
+"alert.title.theme-error" = "Theme error";
+
+/* Theme installed */
+"alert.title.theme-installed" = "Theme installed";
+
+/* Twitter Integration Removed */
+"alert.title.twitter-integration-removed" = "Twitter Integration Removed";
+
+/* Alert title when adding a Feedly account and waiting for authorization from the user. */
+"alert.title.waiting-for-feedly-access" = "Waiting for Feedly access...";
+
+/* Mark Above as Read */
+"button.title-mark-above-as-read.titlecase" = "Mark Above as Read";
+
+/* Mark Below as Read */
+"button.title-mark-below-as-read.titlecase" = "Mark Below as Read";
+
+/* 1 day */
+"button.title.1-day" = "1 day";
+
+/* 1 month */
+"button.title.1-month" = "1 month";
+
+/* 1 week */
+"button.title.1-week" = "1 week";
+
+/* 1 year */
+"button.title.1-year" = "1 year";
+
+/* 2 days */
+"button.title.2-days" = "2 days";
+
+/* Every 2 Hours */
+"button.title.2-hours" = "Every 2 Hours";
+
+/* 2 weeks */
+"button.title.2-weeks" = "2 weeks";
+
+/* 3 days */
+"button.title.3-days" = "3 days";
+
+/* Every 4 Hours */
+"button.title.4-hours" = "Every 4 Hours";
+
+/* Every 8 Hours */
+"button.title.8-hours" = "Every 8 Hours";
+
+/* Every 10 Minutes */
+"button.title.10-minutes" = "Every 10 Minutes";
+
+/* Every 30 Minutes */
+"button.title.30-minutes" = "Every 30 Minutes";
+
+/* Account */
+"button.title.accounts" = "Account";
+
+/* Add Item */
+"button.title.add-item" = "Add Item";
+
+/* Advanced */
+"button.title.advanced" = "Advanced";
+
+/* Always Use Reader View */
+"button.title.always-use-reader-view" = "Always User Reader View";
+
+/* Article Theme */
+"button.title.article-theme" = "Article Theme";
+
+/* Cancel
+ Cancel button
+ Cancel Deactivate Extension
+ Cancel Delete Account
+ Cancel Install Theme */
+"button.title.cancel" = "Cancel";
+
+/* Clean Up */
+"button.title.clean-up" = "Clean Up";
+
+/* Clear & Restart */
+"button.title.clear-and-restart" = "Clear & Restart";
+
+/* Close */
+"button.title.close" = "Close";
+
+/* Continue */
+"button.title.continue" = "Continue";
+
+/* Copy Article URL */
+"button.title.copy-article-url" = "Copy Article URL";
+
+/* Copy External URL */
+"button.title.copy-external-url" = "Copy External URL";
+
+/* Copy Feed URL */
+"button.title.copy-feed-url" = "Copy Feed URL";
+
+/* Copy Home Page URL */
+"button.title.copy-home-page-url" = "Copy Home Page URL";
+
+/* Create */
+"button.title.create" = "Create";
+
+/* Deactivate Extension */
+"button.title.deactivate" = "Deactivate";
+
+/* System Default (%@) */
+"button.title.default-browser.%@" = "System Default (%@)";
+
+/* Delete
+ Delete Account */
+"button.title.delete" = "Delete";
+
+/* Delete Feed */
+"button.title.delete-feed" = "Delete Feed";
+
+/* Delete Feeds */
+"button.title.delete-feeds" = "Delete Feeds";
+
+/* Delete Feeds and Folders */
+"button.title.delete-feeds-and-folders" = "Delete Feeds and Folders";
+
+/* Delete Folder */
+"button.title.delete-folder" = "Delete Folder";
+
+/* Delete Folders */
+"button.title.delete-folders" = "Delete Folders";
+
+/* Dismiss */
+"button.title.dismiss" = "Dismiss";
+
+/* Every Hour */
+"button.title.every-hour" = "Every Hour";
+
+/* Extensions */
+"button.title.extensions" = "Extensions";
+
+/* General */
+"button.title.general" = "General";
+
+/* Hide Read Articles */
+"button.title.hide-read-articles" = "Hide Read Articles";
+
+/* Hide Read Feeds */
+"button.title.hide-read-feeds" = "Hide Read Feeds";
+
+/* Hide Sidebar */
+"button.title.hide-sidebar" = "Hide Sidebar";
+
+/* Install Theme */
+"button.title.install-theme" = "Install Theme";
+
+/* Manually */
+"button.title.manually" = "Manually";
+
+/* Mark Above as Read */
+"button.title.mark-above-as-read.titlecase" = "Mark Above as Read";
+
+/* Mark All as Read in “%@” */
+"button.title.mark-all-as-read.%@" = "Mark All as Read in “%@”";
+
+/* Mark All as Read */
+"button.title.mark-all-as-read.titlecase" = "Mark All as Read";
+
+/* Mark as Read */
+"button.title.mark-as-read" = "Mark as Read";
+
+/* Mark as Read Older Than */
+"button.title.mark-as-read-older-than" = "Mark as Read Older Than";
+
+/* Mark as Starred */
+"button.title.mark-as-starred" = "Mark as Starred";
+
+/* Mark as Unread */
+"button.title.mark-as-unread" = "Mark as Unread";
+
+/* Mark as Unstarred */
+"button.title.mark-as-unstarred" = "Mask as Unstarred";
+
+/* Mark Below as Read */
+"button.title.mark-below-as-read.titlecase" = "Mark Below as Read";
+
+/* Mark Read */
+"button.title.mark-read" = "Mark Read";
+
+/* Star */
+"button.title.mark-star" = "Mark Star";
+
+/* Mark Starred */
+"button.title.mark-starred" = "Mark Starred";
+
+/* Mark Unread */
+"button.title.mark-unread" = "Mark Unread";
+
+/* Mark Unstarred */
+"button.title.mark-unstarred" = "Mark Unstarred";
+
+/* New Feed */
+"button.title.new-feed" = "New Feed";
+
+/* New Folder
+ New Folder... */
+"button.title.new-folder" = "New Folder";
+
+/* New Reddit Feed... */
+"button.title.new-reddit-feed" = "New Reddit Feed...";
+
+/* New Twitter Feed... */
+"button.title.new-twitter-feed" = "New Twitter Feed...";
+
+/* New Web Feed... */
+"button.title.new-web-feed" = "New Web Feed...";
+
+/* Next Unread */
+"button.title.next-read" = "Next Unread";
+
+/* OK */
+"button.title.ok" = "OK";
+
+/* Open */
+"button.title.open" = "Open";
+
+/* Open %ld Articles */
+"button.title.open-articles.%ld" = "Open %ld Articles";
+
+/* Open Home Page */
+"button.title.open-home-page" = "Open Home Page";
+
+/* Open in Browser in Background */
+"button.title.open-in-background" = "Open in Browser in Background";
+
+/* Open in Browser */
+"button.title.open-in-browser" = "Open in Browser";
+
+/* Open in Browser in Foreground */
+"button.title.open-in-foreground" = "Open in Browser in Foreground";
+
+/* New Folder... */
+"button.title.open-new-folder" = "New Folder...";
+
+/* Open Settings */
+"button.title.open-settings" = "Open Settings";
+
+/* Open System Settings */
+"button.title.open-system-settings" = "Open System Settings";
+
+/* Overwrite */
+"button.title.overwrite" = "Overwrite";
+
+/* Read Articles Filter */
+"button.title.read-articles-filter" = "Read Articles Filter";
+
+/* Reader View */
+"button.title.reader-view" = "Reader View";
+
+/* Refresh */
+"button.title.refresh" = "Refresh";
+
+/* Rename */
+"button.title.rename" = "Rename";
+
+/* Search */
+"button.title.search" = "Search";
+
+/* Select “%@” in Sidebar */
+"button.title.select-in-sidebar.%@" = "Select “%@” in Sidebar";
+
+/* Share
+ Share menu name */
+"button.title.share" = "Share";
+
+/* Show Read Articles */
+"button.title.show-read-articles" = "Show Read Articles";
+
+/* Show Read Feeds */
+"button.title.show-read-feeds" = "Show Read Feeds";
+
+/* Show Sidebar */
+"button.title.show-sidebar" = "Show Sidebar";
+
+/* Toggle Sidebar */
+"button.title.toggle-sidebar" = "Toggle Sidebar";
+
+/* Update */
+"button.title.update" = "Update";
+
+/* Show notifications for new articles */
+"checkbox.title.show-new-article-notifications" = "Show notifications for new articles";
+
+/* No comment provided by engineer. */
+"Continue" = "Continue";
+
+/* Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings. */
+"error.description.cloudkit-unavailable" = "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.";
+
+/* Unable to create extension. */
+"error.message.unable-to-create-extension" = "Unable to create extension.";
+
+/* Markdown formatted link to netnewswire.com */
+"label.markdown.netnewswire-website" = "[netnewswire.com](https://netnewswire.com)";
+
+/* Add an account by clicking the + button. */
+"label.text.add-account-explainer" = "Add an account by clicking the + button.";
+
+/* Add an extension by clicking the + button. */
+"label.text.add-extension" = "Add an extension by clicking the + button.";
+
+/* You've added all available extensions. */
+"label.text.added-all-extensions" = "You've added all available extensions.";
+
+/* Article */
+"label.text.article" = "Article";
+
+/* Choose an account type to add... */
+"label.text.choose-account-to-add" = "Choose an account type to add...";
+
+/* Choose an extension to add... */
+"label.text.choose-extension-to-add" = "Choose an extension to add...";
+
+/* iCloud */
+"label.text.cloudkit" = "iCloud";
+
+/* Your iCloud account syncs your feeds across your Mac and iOS devices */
+"label.text.cloudkit-explainer" = "Your iCloud account syncs your feeds across your Mac and iOS devices";
+
+/* Error - Reader View */
+"label.text.error-reader-view" = "Error - Reader View";
+
+/* XX-Large */
+"label.text.extra-extra-large" = "XX-Large";
+
+/* X-Large */
+"label.text.extra-large" = "X-Large";
+
+/* Feed Provider */
+"label.text.feed-provider" = "Feed Provider";
+
+/* An extension that makes websites appear to provide RSS feeds for their content. */
+"label.text.feed-provider-explainer" = "An extension that makes websites appear to provide RSS feeds for their content.";
+
+/* Find out more */
+"label.text.find-out-more" = "Find out more";
+
+/* Finding feed... */
+"label.text.finding-feed" = "Finding feed...";
+
+/* Large */
+"label.text.large" = "Large";
+
+/* Link: */
+"label.text.link" = "Link:";
+
+/* Local */
+"label.text.local" = "Local";
+
+/* Local accounts do not sync your feeds across devices */
+"label.text.local-account-explainer" = "Local accounts do not sync your feeds across devices";
+
+/* This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work. */
+"label.text.marsedit.explainer" = "This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.";
+
+/* Medium */
+"label.text.medium" = "Medium";
+
+/* This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work. */
+"label.text.micro-blog-expaliner" = "This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.";
+
+/* By Brent Simmons and the NetNewsWire team. */
+"label.text.netnewswire-byline" = "By Brent Simmons and the NetNewsWire team.";
+
+/* Don’t have a BazQux account? */
+"label.text.no-bazqux" = "Don’t have a BazQux account?";
+
+/* Don’t have a FreshRSS instance? */
+"label.text.no-fresh-rss" = "Don’t have a FreshRSS instance?";
+
+/* Don’t have an InoReader account? */
+"label.text.no-inoreader" = "Don’t have an InoReader account?";
+
+/* Don’t have a The Old Reader account? */
+"label.text.no-old-reader" = "Don’t have a The Old Reader account?";
+
+/* (No Text) */
+"label.text.no-text" = "(No Text)";
+
+/* Posts from r/%@ */
+"label.text.posts-from-subreddit.%@" = "Posts from r/%@";
+
+/* Privacy Policy */
+"label.text.privacy-policy" = "Privacy Policy";
+
+/* Processing - Reader View */
+"label.text.processing-reader-view" = "Processing - Reader View";
+
+/* Reader View */
+"label.text.reader-view" = "Reader View";
+
+/* The most active posts */
+"label.text.reddit-active-posts" = "The most active posts";
+
+/* The best posts on Reddit for you */
+"label.text.reddit-best-posts" = "The best posts on Reddit for you";
+
+/* Your personal Reddit frontpage */
+"label.text.reddit-front-page" = "Your personal Reddit frontpage";
+
+/* Select an account or add a new account by clicking the + button. */
+"label.text.select-or-add-account-explainer" = "Select an account or add a new account by clicking the + button.";
+
+/* Select an extension or add a new extension by clicking the + button. */
+"label.text.select-or-add-extension" = "Select an extension or add a new extension by clicking the + button.";
+
+/* Selected - Reader View */
+"label.text.selected-reader-view" = "Selected - Reader View";
+
+/* Self-hosted */
+"label.text.self-hosted" = "Self-hosted";
+
+/* Self-hosted accounts sync your feeds across all your devices */
+"label.text.self-hosted-accounts-explainer" = "Self-hosted accounts sync your feeds across all your devices";
+
+/* An extension that enables a share menu item that passes article data to a third-party application. */
+"label.text.share-extension-explainer" = "An extension that enables a share menu item that passes article data to a third-party application.";
+
+/* Sign in to your BazQux account. */
+"label.text.sign-in-bazqux" = "Sign in to your BazQux account.";
+
+/* Sign in to your FreshRSS account. */
+"label.text.sign-in-freshrss" = "Sign in to your FreshRSS account.";
+
+/* Sign in to your InoReader account. */
+"label.text.sign-in-inoreader" = "Sign in to your InoReader account.";
+
+/* Sign in to your The Old Reader account. */
+"label.text.sign-in-old-reader" = "Sign in to your The Old Reader account.";
+
+/* Small */
+"label.text.small" = "Small";
+
+/* Subreddit */
+"label.text.subreddit" = "Subreddit";
+
+/* Built-in */
+"label.text.themes-builtin" = "Built-in Themes";
+
+/* Third Party */
+"label.text.themes-third-party" = "Other Themes";
+
+/* Third-Party Integration */
+"label.text.third-party-integration" = "Third-Party Integration";
+
+/* Timeline */
+"label.text.timeline" = "Timeline";
+
+/* Tweets that contain %@ */
+"label.text.tweets-containing.%@" = "Tweets that contain %@";
+
+/* Tweets from everyone you follow */
+"label.text.tweets-from-everyone" = "Tweets from everyone you follow";
+
+/* Tweets from @%@ */
+"label.text.tweets-from.%@" = "Tweets from @%@";
+
+/* Tweets mentioning you */
+"label.text.tweets-mentioning-you" = "Tweets mentioning you";
+
+/* unread */
+"label.text.unread" = "unread";
+
+/* Web */
+"label.text.web" = "Web";
+
+/* Web accounts sync your feeds across all your devices */
+"label.text.web-account-explainer" = "Web accounts sync your feeds across all your devices";
+
+/* Catch up to articles older than... */
+"menu.title.catch-up-to-articles" = "Catch up to articles older than...";
+
+/* Choose a location for the exported OPML file. */
+"panel.message.export-opml" = "Choose a location for the exported OPML file.";
+
+/* Export OPML */
+"panel.prompt.export-opml" = "Export OPML";
+
+/* Export to: */
+"panel.textfield.export-opml-destination" = "Export to:";
+
+/* Export OPML */
+"panel.title.export-opml" = "Export OPML";
+
+/* Choose a Subscriptions.plist file: */
+"panel.title.select-opml-file" = "Choose a Subscriptions.plist file:";
+
+/* All Unread pseudo-feed title */
+"smartfeed.title.allunread" = "All Unread";
+
+/* Starred pseudo-feed title */
+"smartfeed.title.starred" = "Starred";
+
+/* Today pseudo-feed title */
+"smartfeed.title.today" = "Today";
+
+/* Smart Feeds group title */
+"smartfeeds.title" = "Smart Feeds";
+
+/* Search: */
+"textfield.placeholder.search" = "Search:";
+
+/* Search Term or #hashtag */
+"textfield.placeholder.search-term-hashtag" = "Search Term or #hashtag";
+
+/* @name */
+"textfield.placeholder.twitter-username" = "@name";
+
+/* Rename %@ to: */
+"textfield.prompt.rename-to.%@" = "Rename %@ to:";
+
+/* Create a local account on your Mac. */
+"textfield.text.create-a-local-account" = "Create a local account on your Mac.";
+
+/* Sign in to your Feedbin account. */
+"textfield.text.sign-in-feedbin" = "Sign in to your Feedbin account.";
+
+/* Sign in to your NewsBlur account. */
+"textfield.text.sign-in-newsblur" = "Sign in to your NewsBlur account.";
+
+/* Update your Feedbin account credentials. */
+"textfield.text.update-feedbin-credentials" = "Update your Feedbin account credentials.";
+
+/* Update your NewsBlur account credentials. */
+"textfield.text.update-newsblur-credentials" = "Update your NewsBlur account credentials.";
+
+/* %d unread */
+"window.subtitle.unread-count.%d" = "%d unread";
+
+/* Feed Inspector */
+"window.title.feed-inspector" = "Feed Inspector";
+
+/* Folder Inspector */
+"window.title.folder-inspector" = "Folder Inspector";
+
+/* Inspector */
+"window.title.inspector" = "Inspector";
+
+/* Keyboard Shortcuts */
+"window.title.keyboard-shortcuts" = "Keyboard Shortcuts";
+
+/* Multiple */
+"window.title.multiple" = "Multiple";
+
+/* Search: %@ */
+"window.title.search.%@" = "Search: %@";
+
+/* Smart Feed Inspector */
+"window.title.smart-feed-inspector" = "Smart Feed Inspector";
+
diff --git a/Mac/Resources/Localizable.stringsdict b/Mac/Resources/en-GB.lproj/Localizable.stringsdict
similarity index 84%
rename from Mac/Resources/Localizable.stringsdict
rename to Mac/Resources/en-GB.lproj/Localizable.stringsdict
index bbe875a1f..f271d95e3 100644
--- a/Mac/Resources/Localizable.stringsdict
+++ b/Mac/Resources/en-GB.lproj/Localizable.stringsdict
@@ -2,20 +2,20 @@
- Copy Article URL
+ button.title.copy-article-urls.%ld
NSStringLocalizedFormatKey
- %#@copy_article_url@
- copy_article_url
+ %#@count@
+ count
NSStringFormatSpecTypeKey
NSStringPluralRuleType
NSStringFormatValueTypeKey
ld
- other
- Copy Article URLs
one
Copy Article URL
+ other
+ Copy Article URLs
diff --git a/Mac/Resources/en.lproj/Localizable.strings b/Mac/Resources/en.lproj/Localizable.strings
new file mode 100644
index 000000000..cba3a859f
--- /dev/null
+++ b/Mac/Resources/en.lproj/Localizable.strings
@@ -0,0 +1,695 @@
+/* On My iPad */
+"account.name.ipad" = "On My iPad";
+
+/* On My iPhone */
+"account.name.iphone" = "On My iPhone";
+
+/* On My Mac */
+"account.name.mac" = "On My Mac";
+
+/* See articles in “%@” */
+"activity.title.see-article-in.folder.%@" = "See articles in “%@”";
+
+/* See first unread article */
+"activity.title.see-first-unread-article" = "See first unread article";
+
+/* Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings. */
+"alert.error.cloudkit-missing" = "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.";
+
+/* There is already an account of that type with that username created. */
+"alert.error.duplicate-account-username" = "There is already an account of that type with that username created.";
+
+/* Invalid API URL. */
+"alert.error.invalid-api-url" = "Invalid API URL.";
+
+/* Error message: The user provided an invalid username or password. */
+"alert.error.invalid-username-or-password" = "A username or password is required.";
+
+/* Error message: Unable to save due a Keychain error. */
+"alert.error.keychain-error" = "Unable to save account credentials due to a Keychain error.";
+
+/* Network error. Please try later. */
+"alert.error.network-error" = "A network error has occurred. Please try later.";
+
+/* This theme cannot be used because of data corruption in the Info.plist: %@. */
+"alert.error.theme-data-corruption.%@" = "This theme cannot be used because of data corruption in the Info.plist: %@.";
+
+/* Error message: This theme shares the same name as a provided theme and cannot be imported. */
+"alert.error.theme-duplicate-of-provided" = "This theme shares the same name as a provided theme and cannot be imported.";
+
+/* This theme cannot be used because the the key—“%@”—is not found in the Info.plist. */
+"alert.error.theme-key-not-found.%@" = "This theme cannot be used because the the key—“%@”—is not found in the Info.plist.";
+
+/* This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist */
+"alert.error.theme-type-mismatch.%@" = "This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist";
+
+/* This theme cannot be used because the the value—“%@”—is not found in the Info.plist. */
+"alert.error.theme-value-missing.%@" = "This theme cannot be used because the the value—“%@”—is not found in the Info.plist.";
+
+/* The account type in invalid. */
+"alert.error.unrecognized-account" = "The account type is not recognized.";
+
+/* Error message: The user must provide a username and password. */
+"alert.error.username-and-password-required" = "A username and password are required.";
+
+/* The user must provide a username, password, and URL. */
+"alert.error.username-password-url-required" = "A username, password, and API URL are required.";
+
+/* Username required. */
+"alert.error.username-required" = "A username is required.";
+
+/* You won't see this message again */
+"alert.informative.will-not-see-again" = "You won't see this message again";
+
+/* Some articles don’t have links, so they weren't copied. */
+"alert.message.articles-without-links" = "Some articles don’t have links, so they weren't copied.";
+
+/* Cannot undo action
+ You can't undo this action. */
+"alert.message.cannot-undo-action" = "You can't undo this action.";
+
+/* Are you sure you want to clear the image caches? This will restart NetNewsWire to begin reloading the remote images. */
+"alert.message.clear-image-cache-confirmation" = "Are you sure you want to clear the image caches? This will restart NetNewsWire to begin reloading the remote images.";
+
+/* You've already set up an iCloud account. */
+"alert.message.cloudkit-already-setup" = "You've already set up an iCloud account.";
+
+/* Are you sure you want to delete the “%@” feed? */
+"alert.message.delete-feed.%@" = "Are you sure you want to delete the “%@” feed?";
+
+/* Are you sure you want to delete the “%@” folder? */
+"alert.message.delete-folder.%@" = "Are you sure you want to delete the “%@” folder?";
+
+/* Are you sure you want to delete the %d selected items? */
+"alert.message.delete-items.%d" = "Are you sure you want to delete the %d selected items?";
+
+/* Can’t add this feed because of a download error: “%@” */
+"alert.message.download-error.%@" = "Can’t add this feed because of a download error: “%@”";
+
+/* The theme “%@” already exists. Overwrite it? */
+"alert.message.duplicate-theme.%@" = "The theme “%@” already exists. Overwrite it?";
+
+/* You can enable NetNewsWire notifications in System Settings. */
+"alert.message.enable-notifications-in-settings" = "You can enable NetNewsWire notifications in System Settings.";
+
+/* To enable notifications, open Notifications in System Settings, then find NetNewsWire in the list. */
+"alert.message.enable-notifications-in-system-settings" = "To enable notifications, open Notifications in System Settings, then find NetNewsWire in the list.";
+
+/* A web browser will open the Feedly login for you to authorize access. */
+"alert.message.feedly-web-browser-information" = "A web browser will open the Feedly login for you to authorize access.";
+
+/* The theme “%@” has been installed. */
+"alert.message.theme-installed.%@" = "The theme “%@” has been installed.";
+
+/* Twitter Deprecation Message */
+"alert.message.twitter-deprecation-message" = "On February 1, 2023, Twitter announced the end of free access to the Twitter API, effective February 9.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.";
+
+/* Author's website: */
+"alert.title.authors-website" = "Author's website:";
+
+/* Deactivate “%@“? */
+"alert.title.deactivate-extension.%@" = "Deactivate “%@“?";
+
+/* Delete Feed */
+"alert.title.delete-feed" = "Delete Feed";
+
+/* Delete Folder */
+"alert.title.delete-folder" = "Delete Folder";
+
+/* Delete Items */
+"alert.title.delete-items" = "Delete Items";
+
+/* Delete “%@“ */
+"alert.title.delete.%@" = "Delete “%@“";
+
+/* Download Error */
+"alert.title.download-error" = "Download Error";
+
+/* Enable Notifications */
+"alert.title.enable-notifications" = "Enable Notifications";
+
+/* Error */
+"alert.title.error" = "Error";
+
+/* Install theme “%@” by %@? — the order of the variables is theme name, author name */
+"alert.title.install-theme.%@.%@" = "Install theme “%@” by %@?";
+
+/* Notifications are not enabled. */
+"alert.title.notifications-not-enabled" = "Notifications are not enabled.";
+
+/* Are you sure you want to open %ld articles in your browser? */
+"alert.title.open-articles-in-browser.%ld" = "Are you sure you want to open %ld articles in your browser?";
+
+/* Theme error */
+"alert.title.theme-error" = "Theme error";
+
+/* Theme installed */
+"alert.title.theme-installed" = "Theme installed";
+
+/* Twitter Integration Removed */
+"alert.title.twitter-integration-removed" = "Twitter Integration Removed";
+
+/* Alert title when adding a Feedly account and waiting for authorization from the user. */
+"alert.title.waiting-for-feedly-access" = "Waiting for Feedly access...";
+
+/* Mark Above as Read */
+"button.title-mark-above-as-read.titlecase" = "Mark Above as Read";
+
+/* Mark Below as Read */
+"button.title-mark-below-as-read.titlecase" = "Mark Below as Read";
+
+/* 1 day */
+"button.title.1-day" = "1 day";
+
+/* 1 month */
+"button.title.1-month" = "1 month";
+
+/* 1 week */
+"button.title.1-week" = "1 week";
+
+/* 1 year */
+"button.title.1-year" = "1 year";
+
+/* 2 days */
+"button.title.2-days" = "2 days";
+
+/* Every 2 Hours */
+"button.title.2-hours" = "Every 2 Hours";
+
+/* 2 weeks */
+"button.title.2-weeks" = "2 weeks";
+
+/* 3 days */
+"button.title.3-days" = "3 days";
+
+/* Every 4 Hours */
+"button.title.4-hours" = "Every 4 Hours";
+
+/* Every 8 Hours */
+"button.title.8-hours" = "Every 8 Hours";
+
+/* Every 10 Minutes */
+"button.title.10-minutes" = "Every 10 Minutes";
+
+/* Every 30 Minutes */
+"button.title.30-minutes" = "Every 30 Minutes";
+
+/* Account */
+"button.title.accounts" = "Account";
+
+/* Add Item */
+"button.title.add-item" = "Add Item";
+
+/* Advanced */
+"button.title.advanced" = "Advanced";
+
+/* Always Use Reader View */
+"button.title.always-use-reader-view" = "Always User Reader View";
+
+/* Article Theme */
+"button.title.article-theme" = "Article Theme";
+
+/* Cancel
+ Cancel button
+ Cancel Deactivate Extension
+ Cancel Delete Account
+ Cancel Install Theme */
+"button.title.cancel" = "Cancel";
+
+/* Clean Up */
+"button.title.clean-up" = "Clean Up";
+
+/* Clear & Restart */
+"button.title.clear-and-restart" = "Clear & Restart";
+
+/* Close */
+"button.title.close" = "Close";
+
+/* Continue */
+"button.title.continue" = "Continue";
+
+/* Copy Article URL */
+"button.title.copy-article-url" = "Copy Article URL";
+
+/* Copy External URL */
+"button.title.copy-external-url" = "Copy External URL";
+
+/* Copy Feed URL */
+"button.title.copy-feed-url" = "Copy Feed URL";
+
+/* Copy Home Page URL */
+"button.title.copy-home-page-url" = "Copy Home Page URL";
+
+/* Create */
+"button.title.create" = "Create";
+
+/* Deactivate Extension */
+"button.title.deactivate" = "Deactivate";
+
+/* System Default (%@) */
+"button.title.default-browser.%@" = "System Default (%@)";
+
+/* Delete
+ Delete Account */
+"button.title.delete" = "Delete";
+
+/* Delete Feed */
+"button.title.delete-feed" = "Delete Feed";
+
+/* Delete Feeds */
+"button.title.delete-feeds" = "Delete Feeds";
+
+/* Delete Feeds and Folders */
+"button.title.delete-feeds-and-folders" = "Delete Feeds and Folders";
+
+/* Delete Folder */
+"button.title.delete-folder" = "Delete Folder";
+
+/* Delete Folders */
+"button.title.delete-folders" = "Delete Folders";
+
+/* Dismiss */
+"button.title.dismiss" = "Dismiss";
+
+/* Every Hour */
+"button.title.every-hour" = "Every Hour";
+
+/* Extensions */
+"button.title.extensions" = "Extensions";
+
+/* General */
+"button.title.general" = "General";
+
+/* Hide Read Articles */
+"button.title.hide-read-articles" = "Hide Read Articles";
+
+/* Hide Read Feeds */
+"button.title.hide-read-feeds" = "Hide Read Feeds";
+
+/* Hide Sidebar */
+"button.title.hide-sidebar" = "Hide Sidebar";
+
+/* Install Theme */
+"button.title.install-theme" = "Install Theme";
+
+/* Manually */
+"button.title.manually" = "Manually";
+
+/* Mark Above as Read */
+"button.title.mark-above-as-read.titlecase" = "Mark Above as Read";
+
+/* Mark All as Read in “%@” */
+"button.title.mark-all-as-read.%@" = "Mark All as Read in “%@”";
+
+/* Mark All as Read */
+"button.title.mark-all-as-read.titlecase" = "Mark All as Read";
+
+/* Mark as Read */
+"button.title.mark-as-read" = "Mark as Read";
+
+/* Mark as Read Older Than */
+"button.title.mark-as-read-older-than" = "Mark as Read Older Than";
+
+/* Mark as Starred */
+"button.title.mark-as-starred" = "Mark as Starred";
+
+/* Mark as Unread */
+"button.title.mark-as-unread" = "Mark as Unread";
+
+/* Mark as Unstarred */
+"button.title.mark-as-unstarred" = "Mask as Unstarred";
+
+/* Mark Below as Read */
+"button.title.mark-below-as-read.titlecase" = "Mark Below as Read";
+
+/* Mark Read */
+"button.title.mark-read" = "Mark Read";
+
+/* Star */
+"button.title.mark-star" = "Mark Star";
+
+/* Mark Starred */
+"button.title.mark-starred" = "Mark Starred";
+
+/* Mark Unread */
+"button.title.mark-unread" = "Mark Unread";
+
+/* Mark Unstarred */
+"button.title.mark-unstarred" = "Mark Unstarred";
+
+/* New Feed */
+"button.title.new-feed" = "New Feed";
+
+/* New Folder
+ New Folder... */
+"button.title.new-folder" = "New Folder";
+
+/* New Reddit Feed... */
+"button.title.new-reddit-feed" = "New Reddit Feed...";
+
+/* New Twitter Feed... */
+"button.title.new-twitter-feed" = "New Twitter Feed...";
+
+/* New Web Feed... */
+"button.title.new-web-feed" = "New Web Feed...";
+
+/* Next Unread */
+"button.title.next-read" = "Next Unread";
+
+/* OK */
+"button.title.ok" = "OK";
+
+/* Open */
+"button.title.open" = "Open";
+
+/* Open %ld Articles */
+"button.title.open-articles.%ld" = "Open %ld Articles";
+
+/* Open Home Page */
+"button.title.open-home-page" = "Open Home Page";
+
+/* Open in Browser in Background */
+"button.title.open-in-background" = "Open in Browser in Background";
+
+/* Open in Browser */
+"button.title.open-in-browser" = "Open in Browser";
+
+/* Open in Browser in Foreground */
+"button.title.open-in-foreground" = "Open in Browser in Foreground";
+
+/* New Folder... */
+"button.title.open-new-folder" = "New Folder...";
+
+/* Open Settings */
+"button.title.open-settings" = "Open Settings";
+
+/* Open System Settings */
+"button.title.open-system-settings" = "Open System Settings";
+
+/* Overwrite */
+"button.title.overwrite" = "Overwrite";
+
+/* Read Articles Filter */
+"button.title.read-articles-filter" = "Read Articles Filter";
+
+/* Reader View */
+"button.title.reader-view" = "Reader View";
+
+/* Refresh */
+"button.title.refresh" = "Refresh";
+
+/* Rename */
+"button.title.rename" = "Rename";
+
+/* Search */
+"button.title.search" = "Search";
+
+/* Select “%@” in Sidebar */
+"button.title.select-in-sidebar.%@" = "Select “%@” in Sidebar";
+
+/* Share
+ Share menu name */
+"button.title.share" = "Share";
+
+/* Show Read Articles */
+"button.title.show-read-articles" = "Show Read Articles";
+
+/* Show Read Feeds */
+"button.title.show-read-feeds" = "Show Read Feeds";
+
+/* Show Sidebar */
+"button.title.show-sidebar" = "Show Sidebar";
+
+/* Toggle Sidebar */
+"button.title.toggle-sidebar" = "Toggle Sidebar";
+
+/* Update */
+"button.title.update" = "Update";
+
+/* Show notifications for new articles */
+"checkbox.title.show-new-article-notifications" = "Show notifications for new articles";
+
+/* Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings. */
+"error.description.cloudkit-unavailable" = "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.";
+
+/* Unable to create extension. */
+"error.message.unable-to-create-extension" = "Unable to create extension.";
+
+/* Markdown formatted link to netnewswire.com */
+"label.markdown.netnewswire-website" = "[netnewswire.com](https://netnewswire.com)";
+
+/* Add an account by clicking the + button. */
+"label.text.add-account-explainer" = "Add an account by clicking the + button.";
+
+/* Add an extension by clicking the + button. */
+"label.text.add-extension" = "Add an extension by clicking the + button.";
+
+/* You've added all available extensions. */
+"label.text.added-all-extensions" = "You've added all available extensions.";
+
+/* Article */
+"label.text.article" = "Article";
+
+/* Choose an account type to add... */
+"label.text.choose-account-to-add" = "Choose an account type to add...";
+
+/* Choose an extension to add... */
+"label.text.choose-extension-to-add" = "Choose an extension to add...";
+
+/* iCloud */
+"label.text.cloudkit" = "iCloud";
+
+/* Your iCloud account syncs your feeds across your Mac and iOS devices */
+"label.text.cloudkit-explainer" = "Your iCloud account syncs your feeds across your Mac and iOS devices";
+
+/* Error - Reader View */
+"label.text.error-reader-view" = "Error - Reader View";
+
+/* XX-Large */
+"label.text.extra-extra-large" = "XX-Large";
+
+/* X-Large */
+"label.text.extra-large" = "X-Large";
+
+/* Feed Provider */
+"label.text.feed-provider" = "Feed Provider";
+
+/* An extension that makes websites appear to provide RSS feeds for their content. */
+"label.text.feed-provider-explainer" = "An extension that makes websites appear to provide RSS feeds for their content.";
+
+/* Find out more */
+"label.text.find-out-more" = "Find out more";
+
+/* Finding feed... */
+"label.text.finding-feed" = "Finding feed...";
+
+/* Large */
+"label.text.large" = "Large";
+
+/* Link: */
+"label.text.link" = "Link:";
+
+/* Local */
+"label.text.local" = "Local";
+
+/* Local accounts do not sync your feeds across devices */
+"label.text.local-account-explainer" = "Local accounts do not sync your feeds across devices";
+
+/* This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work. */
+"label.text.marsedit.explainer" = "This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.";
+
+/* Medium */
+"label.text.medium" = "Medium";
+
+/* This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work. */
+"label.text.micro-blog-expaliner" = "This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.";
+
+/* By Brent Simmons and the NetNewsWire team. */
+"label.text.netnewswire-byline" = "By Brent Simmons and the NetNewsWire team.";
+
+/* Don’t have a BazQux account? */
+"label.text.no-bazqux" = "Don’t have a BazQux account?";
+
+/* Don’t have a FreshRSS instance? */
+"label.text.no-fresh-rss" = "Don’t have a FreshRSS instance?";
+
+/* Don’t have an InoReader account? */
+"label.text.no-inoreader" = "Don’t have an InoReader account?";
+
+/* Don’t have a The Old Reader account? */
+"label.text.no-old-reader" = "Don’t have a The Old Reader account?";
+
+/* (No Text) */
+"label.text.no-text" = "(No Text)";
+
+/* Posts from r/%@ */
+"label.text.posts-from-subreddit.%@" = "Posts from r/%@";
+
+/* Privacy Policy */
+"label.text.privacy-policy" = "Privacy Policy";
+
+/* Processing - Reader View */
+"label.text.processing-reader-view" = "Processing - Reader View";
+
+/* Reader View */
+"label.text.reader-view" = "Reader View";
+
+/* The most active posts */
+"label.text.reddit-active-posts" = "The most active posts";
+
+/* The best posts on Reddit for you */
+"label.text.reddit-best-posts" = "The best posts on Reddit for you";
+
+/* Your personal Reddit frontpage */
+"label.text.reddit-front-page" = "Your personal Reddit frontpage";
+
+/* Select an account or add a new account by clicking the + button. */
+"label.text.select-or-add-account-explainer" = "Select an account or add a new account by clicking the + button.";
+
+/* Select an extension or add a new extension by clicking the + button. */
+"label.text.select-or-add-extension" = "Select an extension or add a new extension by clicking the + button.";
+
+/* Selected - Reader View */
+"label.text.selected-reader-view" = "Selected - Reader View";
+
+/* Self-hosted */
+"label.text.self-hosted" = "Self-hosted";
+
+/* Self-hosted accounts sync your feeds across all your devices */
+"label.text.self-hosted-accounts-explainer" = "Self-hosted accounts sync your feeds across all your devices";
+
+/* An extension that enables a share menu item that passes article data to a third-party application. */
+"label.text.share-extension-explainer" = "An extension that enables a share menu item that passes article data to a third-party application.";
+
+/* Sign in to your BazQux account. */
+"label.text.sign-in-bazqux" = "Sign in to your BazQux account.";
+
+/* Sign in to your FreshRSS account. */
+"label.text.sign-in-freshrss" = "Sign in to your FreshRSS account.";
+
+/* Sign in to your InoReader account. */
+"label.text.sign-in-inoreader" = "Sign in to your InoReader account.";
+
+/* Sign in to your The Old Reader account. */
+"label.text.sign-in-old-reader" = "Sign in to your The Old Reader account.";
+
+/* Small */
+"label.text.small" = "Small";
+
+/* Subreddit */
+"label.text.subreddit" = "Subreddit";
+
+/* Built-in */
+"label.text.themes-builtin" = "Built-in Themes";
+
+/* Third Party */
+"label.text.themes-third-party" = "Other Themes";
+
+/* Third-Party Integration */
+"label.text.third-party-integration" = "Third-Party Integration";
+
+/* Timeline */
+"label.text.timeline" = "Timeline";
+
+/* Tweets that contain %@ */
+"label.text.tweets-containing.%@" = "Tweets that contain %@";
+
+/* Tweets from everyone you follow */
+"label.text.tweets-from-everyone" = "Tweets from everyone you follow";
+
+/* Tweets from @%@ */
+"label.text.tweets-from.%@" = "Tweets from @%@";
+
+/* Tweets mentioning you */
+"label.text.tweets-mentioning-you" = "Tweets mentioning you";
+
+/* unread */
+"label.text.unread" = "unread";
+
+/* Web */
+"label.text.web" = "Web";
+
+/* Web accounts sync your feeds across all your devices */
+"label.text.web-account-explainer" = "Web accounts sync your feeds across all your devices";
+
+/* Catch up to articles older than... */
+"menu.title.catch-up-to-articles" = "Catch up to articles older than...";
+
+/* Choose a location for the exported OPML file. */
+"panel.message.export-opml" = "Choose a location for the exported OPML file.";
+
+/* Export OPML */
+"panel.prompt.export-opml" = "Export OPML";
+
+/* Export to: */
+"panel.textfield.export-opml-destination" = "Export to:";
+
+/* Export OPML */
+"panel.title.export-opml" = "Export OPML";
+
+/* Choose a Subscriptions.plist file: */
+"panel.title.select-opml-file" = "Choose a Subscriptions.plist file:";
+
+/* All Unread pseudo-feed title */
+"smartfeed.title.allunread" = "All Unread";
+
+/* Starred pseudo-feed title */
+"smartfeed.title.starred" = "Starred";
+
+/* Today pseudo-feed title */
+"smartfeed.title.today" = "Today";
+
+/* Smart Feeds group title */
+"smartfeeds.title" = "Smart Feeds";
+
+/* Search: */
+"textfield.placeholder.search" = "Search:";
+
+/* Search Term or #hashtag */
+"textfield.placeholder.search-term-hashtag" = "Search Term or #hashtag";
+
+/* @name */
+"textfield.placeholder.twitter-username" = "@name";
+
+/* Rename %@ to: */
+"textfield.prompt.rename-to.%@" = "Rename %@ to:";
+
+/* Create a local account on your Mac. */
+"textfield.text.create-a-local-account" = "Create a local account on your Mac.";
+
+/* Sign in to your Feedbin account. */
+"textfield.text.sign-in-feedbin" = "Sign in to your Feedbin account.";
+
+/* Sign in to your NewsBlur account. */
+"textfield.text.sign-in-newsblur" = "Sign in to your NewsBlur account.";
+
+/* Update your Feedbin account credentials. */
+"textfield.text.update-feedbin-credentials" = "Update your Feedbin account credentials.";
+
+/* Update your NewsBlur account credentials. */
+"textfield.text.update-newsblur-credentials" = "Update your NewsBlur account credentials.";
+
+/* %d unread */
+"window.subtitle.unread-count.%d" = "%d unread";
+
+/* Feed Inspector */
+"window.title.feed-inspector" = "Feed Inspector";
+
+/* Folder Inspector */
+"window.title.folder-inspector" = "Folder Inspector";
+
+/* Inspector */
+"window.title.inspector" = "Inspector";
+
+/* Keyboard Shortcuts */
+"window.title.keyboard-shortcuts" = "Keyboard Shortcuts";
+
+/* Multiple */
+"window.title.multiple" = "Multiple";
+
+/* Search: %@ */
+"window.title.search.%@" = "Search: %@";
+
+/* Smart Feed Inspector */
+"window.title.smart-feed-inspector" = "Smart Feed Inspector";
+
diff --git a/Mac/Resources/en.lproj/Localizable.stringsdict b/Mac/Resources/en.lproj/Localizable.stringsdict
new file mode 100644
index 000000000..9a5aa2079
--- /dev/null
+++ b/Mac/Resources/en.lproj/Localizable.stringsdict
@@ -0,0 +1,22 @@
+
+
+
+
+ button.title.copy-article-urls.%ld
+
+ NSStringLocalizedFormatKey
+ %#@count@
+ count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ ld
+ other
+ Copy Article URLs
+ one
+ Copy Article URL
+
+
+
+
diff --git a/Mac/SafariExtension/en-GB.lproj/InfoPlist.strings b/Mac/SafariExtension/en-GB.lproj/InfoPlist.strings
new file mode 100644
index 000000000..03410935b
--- /dev/null
+++ b/Mac/SafariExtension/en-GB.lproj/InfoPlist.strings
@@ -0,0 +1,12 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "Subscribe to Feed";
+
+/* Bundle name */
+"CFBundleName" = "Subscribe to Feed";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2019-2022 Brent Simmons. All rights reserved.";
+
+/* Human readable description */
+"NSHumanReadableDescription" = "This extension adds a Safari toolbar button for easily subscribing to the syndication feed for the current page.";
+
diff --git a/Mac/SafariExtension/en-GB.lproj/SafariExtensionViewController.strings b/Mac/SafariExtension/en-GB.lproj/SafariExtensionViewController.strings
new file mode 100644
index 000000000..1efba9052
--- /dev/null
+++ b/Mac/SafariExtension/en-GB.lproj/SafariExtensionViewController.strings
@@ -0,0 +1,3 @@
+/* Class = "NSTextFieldCell"; title = "Subscribe to Feed"; ObjectID = "2Ec-kd-q2K"; */
+"2Ec-kd-q2K.title" = "Subscribe to Feed";
+
diff --git a/Mac/ShareExtension/en-GB.lproj/InfoPlist.strings b/Mac/ShareExtension/en-GB.lproj/InfoPlist.strings
new file mode 100644
index 000000000..189b7b3f2
--- /dev/null
+++ b/Mac/ShareExtension/en-GB.lproj/InfoPlist.strings
@@ -0,0 +1,9 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "NetNewsWire";
+
+/* Bundle name */
+"CFBundleName" = "NetNewsWire Share Extension MAS";
+
+/* Copyright (human-readable) */
+"NSHumanReadableCopyright" = "Copyright © 2020-2022 Ranchero Software. All rights reserved.";
+
diff --git a/Mac/ShareExtension/en-GB.lproj/Localizable.strings b/Mac/ShareExtension/en-GB.lproj/Localizable.strings
new file mode 100644
index 000000000..1013e0f27
--- /dev/null
+++ b/Mac/ShareExtension/en-GB.lproj/Localizable.strings
@@ -0,0 +1,36 @@
+/* Every 2 Hours */
+"button.title.2-hours" = "Every 2 Hours";
+
+/* Every 4 Hours */
+"button.title.4-hours" = "Every 4 Hours";
+
+/* Every 8 Hours */
+"button.title.8-hours" = "Every 8 Hours";
+
+/* Every 10 Minutes */
+"button.title.10-minutes" = "Every 10 Minutes";
+
+/* Every 30 Minutes */
+"button.title.30-minutes" = "Every 30 Minutes";
+
+/* Every Hour */
+"button.title.every-hour" = "Every Hour";
+
+/* Manually */
+"button.title.manually" = "Manually";
+
+/* XX-Large */
+"label.text.extra-extra-large" = "XX-Large";
+
+/* X-Large */
+"label.text.extra-large" = "X-Large";
+
+/* Large */
+"label.text.large" = "Large";
+
+/* Medium */
+"label.text.medium" = "Medium";
+
+/* Small */
+"label.text.small" = "Small";
+
diff --git a/Mac/ShareExtension/en-GB.lproj/ShareViewController.strings b/Mac/ShareExtension/en-GB.lproj/ShareViewController.strings
new file mode 100644
index 000000000..ef5991302
--- /dev/null
+++ b/Mac/ShareExtension/en-GB.lproj/ShareViewController.strings
@@ -0,0 +1,27 @@
+/* Class = "NSButtonCell"; title = "Send"; ObjectID = "2l4-PO-we5"; */
+"2l4-PO-we5.title" = "Send";
+
+/* Class = "NSTextFieldCell"; title = "NetNewsWire"; ObjectID = "5Ed-E1-Gf1"; */
+"5Ed-E1-Gf1.title" = "NetNewsWire";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "6Up-t3-mwm"; */
+"6Up-t3-mwm.title" = "Cancel";
+
+/* Class = "NSTextFieldCell"; placeholderString = "Optional"; ObjectID = "27c-xz-zoJ"; */
+"27c-xz-zoJ.placeholderString" = "Optional";
+
+/* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "Djb-KO-yjg"; */
+"Djb-KO-yjg.title" = "Item 3";
+
+/* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "jf0-bY-EUJ"; */
+"jf0-bY-EUJ.title" = "Item 2";
+
+/* Class = "NSTextFieldCell"; title = "Name:"; ObjectID = "Piv-xr-hYI"; */
+"Piv-xr-hYI.title" = "Name:";
+
+/* Class = "NSTextFieldCell"; title = "Folder:"; ObjectID = "qp4-R2-aO5"; */
+"qp4-R2-aO5.title" = "Folder:";
+
+/* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "wvm-BK-3HS"; */
+"wvm-BK-3HS.title" = "Item 1";
+
diff --git a/Mac/ShareExtension/en.lproj/Localizable.strings b/Mac/ShareExtension/en.lproj/Localizable.strings
new file mode 100644
index 000000000..1013e0f27
--- /dev/null
+++ b/Mac/ShareExtension/en.lproj/Localizable.strings
@@ -0,0 +1,36 @@
+/* Every 2 Hours */
+"button.title.2-hours" = "Every 2 Hours";
+
+/* Every 4 Hours */
+"button.title.4-hours" = "Every 4 Hours";
+
+/* Every 8 Hours */
+"button.title.8-hours" = "Every 8 Hours";
+
+/* Every 10 Minutes */
+"button.title.10-minutes" = "Every 10 Minutes";
+
+/* Every 30 Minutes */
+"button.title.30-minutes" = "Every 30 Minutes";
+
+/* Every Hour */
+"button.title.every-hour" = "Every Hour";
+
+/* Manually */
+"button.title.manually" = "Manually";
+
+/* XX-Large */
+"label.text.extra-extra-large" = "XX-Large";
+
+/* X-Large */
+"label.text.extra-large" = "X-Large";
+
+/* Large */
+"label.text.large" = "Large";
+
+/* Medium */
+"label.text.medium" = "Medium";
+
+/* Small */
+"label.text.small" = "Small";
+
diff --git a/Mac/en-GB.lproj/AddFolderSheet.strings b/Mac/en-GB.lproj/AddFolderSheet.strings
new file mode 100644
index 000000000..dc8434181
--- /dev/null
+++ b/Mac/en-GB.lproj/AddFolderSheet.strings
@@ -0,0 +1,24 @@
+/* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "Aat-NW-hPO"; */
+"Aat-NW-hPO.title" = "Item 2";
+
+/* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "CVJ-R1-O8v"; */
+"CVJ-R1-O8v.title" = "Item 3";
+
+/* Class = "NSButtonCell"; title = "Add Folder"; ObjectID = "F5a-Q7-NMn"; */
+"F5a-Q7-NMn.title" = "Add Folder";
+
+/* Class = "NSTextFieldCell"; title = "Folder Name:"; ObjectID = "k8o-om-Rgh"; */
+"k8o-om-Rgh.title" = "Folder Name:";
+
+/* Class = "NSWindow"; title = "Add Folder"; ObjectID = "QvC-M9-y7g"; */
+"QvC-M9-y7g.title" = "Add Folder";
+
+/* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "SDZ-cp-t7j"; */
+"SDZ-cp-t7j.title" = "Item 1";
+
+/* Class = "NSTextFieldCell"; title = "Account:"; ObjectID = "t6T-ar-V3d"; */
+"t6T-ar-V3d.title" = "Account:";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "VXk-Yp-k6W"; */
+"VXk-Yp-k6W.title" = "Cancel";
+
diff --git a/Mac/en-GB.lproj/AddRedditFeedSheet.strings b/Mac/en-GB.lproj/AddRedditFeedSheet.strings
new file mode 100644
index 000000000..cfce0c88a
--- /dev/null
+++ b/Mac/en-GB.lproj/AddRedditFeedSheet.strings
@@ -0,0 +1,78 @@
+/* Class = "NSMenuItem"; title = "Subreddit"; ObjectID = "0gG-oY-8yR"; */
+"0gG-oY-8yR.title" = "Subreddit";
+
+/* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "5AA-um-oEb"; */
+"5AA-um-oEb.title" = "Label";
+
+/* Class = "NSButtonCell"; title = "Add"; ObjectID = "6NK-Ql-drk"; */
+"6NK-Ql-drk.title" = "Add";
+
+/* Class = "NSTextFieldCell"; title = "Name:"; ObjectID = "8ca-Qp-BkT"; */
+"8ca-Qp-BkT.title" = "Name:";
+
+/* Class = "NSMenuItem"; title = "Popular"; ObjectID = "177-F8-Esj"; */
+"177-F8-Esj.title" = "Popular";
+
+/* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "APc-af-7Um"; */
+"APc-af-7Um.title" = "Item 2";
+
+/* Class = "NSMenuItem"; title = "Best"; ObjectID = "aub-jN-9Gq"; */
+"aub-jN-9Gq.title" = "Best";
+
+/* Class = "NSTextFieldCell"; title = "Sort:"; ObjectID = "cxl-iF-EB5"; */
+"cxl-iF-EB5.title" = "Sort:";
+
+/* Class = "NSMenuItem"; title = "All"; ObjectID = "DBZ-RV-FfV"; */
+"DBZ-RV-FfV.title" = "All";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "Dop-HC-6Q9"; */
+"Dop-HC-6Q9.title" = "Cancel";
+
+/* Class = "NSMenuItem"; title = "Top"; ObjectID = "fuh-g6-Ro7"; */
+"fuh-g6-Ro7.title" = "Top";
+
+/* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "fza-9c-2en"; */
+"fza-9c-2en.title" = "Item 3";
+
+/* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "j09-9b-bGs"; */
+"j09-9b-bGs.title" = "Item 3";
+
+/* Class = "NSMenuItem"; title = "Hot"; ObjectID = "KA5-sN-dho"; */
+"KA5-sN-dho.title" = "Hot";
+
+/* Class = "NSMenuItem"; title = "New"; ObjectID = "kUE-J5-iAE"; */
+"kUE-J5-iAE.title" = "New";
+
+/* Class = "NSTextFieldCell"; title = "Folder:"; ObjectID = "Kwx-7B-CIu"; */
+"Kwx-7B-CIu.title" = "Folder:";
+
+/* Class = "NSTextFieldCell"; title = "Account:"; ObjectID = "LFf-JL-Ahl"; */
+"LFf-JL-Ahl.title" = "Account:";
+
+/* Class = "NSTextFieldCell"; placeholderString = "Optional"; ObjectID = "pLP-pL-5R5"; */
+"pLP-pL-5R5.placeholderString" = "Optional";
+
+/* Class = "NSTextFieldCell"; title = "Type:"; ObjectID = "qto-IO-a1j"; */
+"qto-IO-a1j.title" = "Type:";
+
+/* Class = "NSWindow"; title = "Add Reddit Feed"; ObjectID = "QvC-M9-y7g"; */
+"QvC-M9-y7g.title" = "Add Reddit Feed";
+
+/* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "s6D-9M-HpZ"; */
+"s6D-9M-HpZ.title" = "Item 1";
+
+/* Class = "NSMenuItem"; title = "u/username"; ObjectID = "Tfk-aQ-RKg"; */
+"Tfk-aQ-RKg.title" = "u/username";
+
+/* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "tLJ-zY-CcZ"; */
+"tLJ-zY-CcZ.title" = "Item 1";
+
+/* Class = "NSMenuItem"; title = "Home"; ObjectID = "uE6-1a-w5g"; */
+"uE6-1a-w5g.title" = "Home";
+
+/* Class = "NSMenuItem"; title = "Rising"; ObjectID = "XIL-2V-O3A"; */
+"XIL-2V-O3A.title" = "Rising";
+
+/* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "xTm-Qr-PIp"; */
+"xTm-Qr-PIp.title" = "Item 2";
+
diff --git a/Mac/en-GB.lproj/AddTwitterFeedSheet.strings b/Mac/en-GB.lproj/AddTwitterFeedSheet.strings
new file mode 100644
index 000000000..577d4deff
--- /dev/null
+++ b/Mac/en-GB.lproj/AddTwitterFeedSheet.strings
@@ -0,0 +1,51 @@
+/* Class = "NSMenuItem"; title = "Search"; ObjectID = "0gG-oY-8yR"; */
+"0gG-oY-8yR.title" = "Search";
+
+/* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "5AA-um-oEb"; */
+"5AA-um-oEb.title" = "Label";
+
+/* Class = "NSButtonCell"; title = "Add"; ObjectID = "6NK-Ql-drk"; */
+"6NK-Ql-drk.title" = "Add";
+
+/* Class = "NSTextFieldCell"; title = "Name:"; ObjectID = "8ca-Qp-BkT"; */
+"8ca-Qp-BkT.title" = "Name:";
+
+/* Class = "NSMenuItem"; title = "Mentions"; ObjectID = "177-F8-Esj"; */
+"177-F8-Esj.title" = "Mentions";
+
+/* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "APc-af-7Um"; */
+"APc-af-7Um.title" = "Item 2";
+
+/* Class = "NSMenuItem"; title = "Screen Name"; ObjectID = "DBZ-RV-FfV"; */
+"DBZ-RV-FfV.title" = "Screen Name";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "Dop-HC-6Q9"; */
+"Dop-HC-6Q9.title" = "Cancel";
+
+/* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "j09-9b-bGs"; */
+"j09-9b-bGs.title" = "Item 3";
+
+/* Class = "NSTextFieldCell"; title = "Folder:"; ObjectID = "Kwx-7B-CIu"; */
+"Kwx-7B-CIu.title" = "Folder:";
+
+/* Class = "NSTextFieldCell"; title = "Account:"; ObjectID = "LFf-JL-Ahl"; */
+"LFf-JL-Ahl.title" = "Account:";
+
+/* Class = "NSTextFieldCell"; placeholderString = "Optional"; ObjectID = "pLP-pL-5R5"; */
+"pLP-pL-5R5.placeholderString" = "Optional";
+
+/* Class = "NSTextFieldCell"; title = "Type:"; ObjectID = "qto-IO-a1j"; */
+"qto-IO-a1j.title" = "Type:";
+
+/* Class = "NSWindow"; title = "Add Twitter Feed"; ObjectID = "QvC-M9-y7g"; */
+"QvC-M9-y7g.title" = "Add Twitter Feed";
+
+/* Class = "NSMenuItem"; title = "@username"; ObjectID = "Tfk-aQ-RKg"; */
+"Tfk-aQ-RKg.title" = "@username";
+
+/* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "tLJ-zY-CcZ"; */
+"tLJ-zY-CcZ.title" = "Item 1";
+
+/* Class = "NSMenuItem"; title = "Home Timeline"; ObjectID = "uE6-1a-w5g"; */
+"uE6-1a-w5g.title" = "Home Timeline";
+
diff --git a/Mac/en-GB.lproj/AddWebFeedSheet.strings b/Mac/en-GB.lproj/AddWebFeedSheet.strings
new file mode 100644
index 000000000..f32b38ee0
--- /dev/null
+++ b/Mac/en-GB.lproj/AddWebFeedSheet.strings
@@ -0,0 +1,33 @@
+/* Class = "NSButtonCell"; title = "Add"; ObjectID = "6NK-Ql-drk"; */
+"6NK-Ql-drk.title" = "Add";
+
+/* Class = "NSTextFieldCell"; title = "Name:"; ObjectID = "8ca-Qp-BkT"; */
+"8ca-Qp-BkT.title" = "Name:";
+
+/* Class = "NSTextFieldCell"; title = "URL:"; ObjectID = "8jE-9v-BT2"; */
+"8jE-9v-BT2.title" = "URL:";
+
+/* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "APc-af-7Um"; */
+"APc-af-7Um.title" = "Item 2";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "Dop-HC-6Q9"; */
+"Dop-HC-6Q9.title" = "Cancel";
+
+/* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "j09-9b-bGs"; */
+"j09-9b-bGs.title" = "Item 3";
+
+/* Class = "NSTextFieldCell"; title = "Folder:"; ObjectID = "Kwx-7B-CIu"; */
+"Kwx-7B-CIu.title" = "Folder:";
+
+/* Class = "NSTextFieldCell"; placeholderString = "Optional"; ObjectID = "pLP-pL-5R5"; */
+"pLP-pL-5R5.placeholderString" = "Optional";
+
+/* Class = "NSWindow"; title = "Add Web Feed"; ObjectID = "QvC-M9-y7g"; */
+"QvC-M9-y7g.title" = "Add Web Feed";
+
+/* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "tLJ-zY-CcZ"; */
+"tLJ-zY-CcZ.title" = "Item 1";
+
+/* Class = "NSButtonCell"; title = "Open Feed Directory"; ObjectID = "wKl-a9-7FY"; */
+"wKl-a9-7FY.title" = "Open Feed Directory";
+
diff --git a/Mac/en-GB.lproj/Main.strings b/Mac/en-GB.lproj/Main.strings
new file mode 100644
index 000000000..80b790d52
--- /dev/null
+++ b/Mac/en-GB.lproj/Main.strings
@@ -0,0 +1,378 @@
+/* Class = "NSMenu"; title = "Find"; ObjectID = "1b7-l0-nxx"; */
+"1b7-l0-nxx.title" = "Find";
+
+/* Class = "NSMenuItem"; title = "Check for Updates…"; ObjectID = "1nF-7O-aKU"; */
+"1nF-7O-aKU.title" = "Check for Updates…";
+
+/* Class = "NSMenuItem"; title = "Customize Toolbar…"; ObjectID = "1UK-8n-QPP"; */
+"1UK-8n-QPP.title" = "Customise Toolbar…";
+
+/* Class = "NSMenuItem"; title = "NetNewsWire"; ObjectID = "1Xt-HY-uBw"; */
+"1Xt-HY-uBw.title" = "NetNewsWire";
+
+/* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "2oI-Rn-ZJC"; */
+"2oI-Rn-ZJC.title" = "Transformations";
+
+/* Class = "NSMenu"; title = "Spelling"; ObjectID = "3IN-sU-3Bg"; */
+"3IN-sU-3Bg.title" = "Spelling";
+
+/* Class = "NSMenu"; title = "Speech"; ObjectID = "3rS-ZA-NoH"; */
+"3rS-ZA-NoH.title" = "Speech";
+
+/* Class = "NSMenuItem"; title = "Find"; ObjectID = "4EN-yA-p0u"; */
+"4EN-yA-p0u.title" = "Find";
+
+/* Class = "NSMenuItem"; title = "Open in Browser"; ObjectID = "4iQ-1v-dTa"; */
+"4iQ-1v-dTa.title" = "Open in Browser";
+
+/* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "4J7-dP-txa"; */
+"4J7-dP-txa.title" = "Enter Full Screen";
+
+/* Class = "NSMenuItem"; title = "Quit NetNewsWire"; ObjectID = "4sb-4s-VLi"; */
+"4sb-4s-VLi.title" = "Quit NetNewsWire";
+
+/* Class = "NSMenuItem"; title = "About NetNewsWire"; ObjectID = "5kV-Vb-QxS"; */
+"5kV-Vb-QxS.title" = "About NetNewsWire";
+
+/* Class = "NSMenuItem"; title = "Edit"; ObjectID = "5QF-Oa-p0T"; */
+"5QF-Oa-p0T.title" = "Edit";
+
+/* Class = "NSMenuItem"; title = "Redo"; ObjectID = "6dh-zS-Vam"; */
+"6dh-zS-Vam.title" = "Redo";
+
+/* Class = "NSMenuItem"; title = "Mark Below as Read"; ObjectID = "8lZ-XI-I4y"; */
+"8lZ-XI-I4y.title" = "Mark Below as Read";
+
+/* Class = "NSMenuItem"; title = "All Unread"; ObjectID = "8ZT-ew-JNc"; */
+"8ZT-ew-JNc.title" = "All Unread";
+
+/* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "9ic-FL-obx"; */
+"9ic-FL-obx.title" = "Substitutions";
+
+/* Class = "NSMenuItem"; title = "Debug Search"; ObjectID = "9Ot-wC-s5U"; */
+"9Ot-wC-s5U.title" = "Debug Search";
+
+/* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "9yt-4B-nSM"; */
+"9yt-4B-nSM.title" = "Smart Copy/Paste";
+
+/* Class = "NSMenuItem"; title = "Info"; ObjectID = "24r-2i-fXR"; */
+"24r-2i-fXR.title" = "Info";
+
+/* Class = "NSMenu"; title = "Article"; ObjectID = "57V-gv-vEw"; */
+"57V-gv-vEw.title" = "Article";
+
+/* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "78Y-hA-62v"; */
+"78Y-hA-62v.title" = "Correct Spelling Automatically";
+
+/* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */
+"aUF-d1-5bR.title" = "Window";
+
+/* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */
+"AYu-sK-qS6.title" = "Main Menu";
+
+/* Class = "NSMenuItem"; title = "Hide Read Articles"; ObjectID = "b10-sA-Yzi"; */
+"b10-sA-Yzi.title" = "Hide Read Articles";
+
+/* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */
+"bib-Uj-vzu.title" = "File";
+
+/* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */
+"BOF-NM-1cW.title" = "Preferences…";
+
+/* Class = "NSMenuItem"; title = "Drop Conditional Get Info"; ObjectID = "btO-Tb-2qS"; */
+"btO-Tb-2qS.title" = "Drop Conditional Get Info";
+
+/* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "buJ-ug-pKt"; */
+"buJ-ug-pKt.title" = "Use Selection for Find";
+
+/* Class = "NSMenu"; title = "Transformations"; ObjectID = "c8a-y6-VQd"; */
+"c8a-y6-VQd.title" = "Transformations";
+
+/* Class = "NSMenuItem"; title = "Add NetNewsWire News Feed"; ObjectID = "cmP-uH-mS8"; */
+"cmP-uH-mS8.title" = "Add NetNewsWire News Feed";
+
+/* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "cwL-P1-jid"; */
+"cwL-P1-jid.title" = "Smart Links";
+
+/* Class = "NSMenuItem"; title = "Item"; ObjectID = "CZA-zr-llF"; */
+"CZA-zr-llF.title" = "Item";
+
+/* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "d9M-CD-aMd"; */
+"d9M-CD-aMd.title" = "Make Lower Case";
+
+/* Class = "NSMenu"; title = "Share"; ObjectID = "Dbl-YW-bmb"; */
+"Dbl-YW-bmb.title" = "Share";
+
+/* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */
+"dMs-cI-mzQ.title" = "File";
+
+/* Class = "NSMenuItem"; title = "Undo"; ObjectID = "dRJ-4n-Yzg"; */
+"dRJ-4n-Yzg.title" = "Undo";
+
+/* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Dv1-io-Yv7"; */
+"Dv1-io-Yv7.title" = "Spelling and Grammar";
+
+/* Class = "NSMenuItem"; title = "Close Window"; ObjectID = "DVo-aG-piG"; */
+"DVo-aG-piG.title" = "Close Window";
+
+/* Class = "NSMenuItem"; title = "Hide Read Feeds"; ObjectID = "E9K-zV-nLv"; */
+"E9K-zV-nLv.title" = "Hide Read Feeds";
+
+/* Class = "NSMenuItem"; title = "Open in Browser Inverted"; ObjectID = "EjD-X9-Pjf"; */
+"EjD-X9-Pjf.title" = "Open in Browser Inverted";
+
+/* Class = "NSMenuItem"; title = "Import NNW 3 Subscriptions…"; ObjectID = "ely-yi-STg"; */
+"ely-yi-STg.title" = "Import NNW 3 Subscriptions…";
+
+/* Class = "NSMenuItem"; title = "Enable Web Inspector"; ObjectID = "EwI-z4-ZA3"; */
+"EwI-z4-ZA3.title" = "Enable Web Inspector";
+
+/* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */
+"F2S-fz-NVQ.title" = "Help";
+
+/* Class = "NSMenuItem"; title = "Mark as Read"; ObjectID = "Fc9-c7-2AY"; */
+"Fc9-c7-2AY.title" = "Mark as Read";
+
+/* Class = "NSMenu"; title = "Substitutions"; ObjectID = "FeM-D8-WVr"; */
+"FeM-D8-WVr.title" = "Substitutions";
+
+/* Class = "NSMenuItem"; title = "NetNewsWire Help"; ObjectID = "FKE-Sm-Kum"; */
+"FKE-Sm-Kum.title" = "NetNewsWire Help";
+
+/* Class = "NSMenuItem"; title = "Copy External URL"; ObjectID = "fOF-99-6Iv"; */
+"fOF-99-6Iv.title" = "Copy External URL";
+
+/* Class = "NSMenuItem"; title = "Go"; ObjectID = "FPs-q4-hLT"; */
+"FPs-q4-hLT.title" = "Go";
+
+/* Class = "NSMenuItem"; title = "Paste"; ObjectID = "gVA-U4-sdL"; */
+"gVA-U4-sdL.title" = "Paste";
+
+/* Class = "NSMenuItem"; title = "Test Crash Reporter Window"; ObjectID = "gVd-kQ-efj"; */
+"gVd-kQ-efj.title" = "Test Crash Reporter Window";
+
+/* Class = "NSMenuItem"; title = "Force Crash"; ObjectID = "gVt-cz-eoJ"; */
+"gVt-cz-eoJ.title" = "Force Crash";
+
+/* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */
+"H8h-7b-M4v.title" = "View";
+
+/* Class = "NSMenuItem"; title = "Mark All as Read"; ObjectID = "HdN-Ks-cwh"; */
+"HdN-Ks-cwh.title" = "Mark All as Read";
+
+/* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "HFo-cy-zxI"; */
+"HFo-cy-zxI.title" = "Show Spelling and Grammar";
+
+/* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "HFQ-gK-NFA"; */
+"HFQ-gK-NFA.title" = "Text Replacement";
+
+/* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "hQb-2v-fYv"; */
+"hQb-2v-fYv.title" = "Smart Quotes";
+
+/* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */
+"HyV-fh-RgO.title" = "View";
+
+/* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "hz2-CU-CR7"; */
+"hz2-CU-CR7.title" = "Check Document Now";
+
+/* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */
+"hz9-B4-Xy5.title" = "Services";
+
+/* Class = "NSMenuItem"; title = "Oldest Article on Top"; ObjectID = "iii-kP-qoF"; */
+"iii-kP-qoF.title" = "Oldest Article on Top";
+
+/* Class = "NSMenuItem"; title = "Clean Up Articles"; ObjectID = "J5h-uQ-57w"; */
+"J5h-uQ-57w.title" = "Clean Up Articles";
+
+/* Class = "NSMenuItem"; title = "Share"; ObjectID = "jaf-gh-gSi"; */
+"jaf-gh-gSi.title" = "Share";
+
+/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */
+"Kd2-mp-pUS.title" = "Show All";
+
+/* Class = "NSMenuItem"; title = "New Twitter Feed…"; ObjectID = "ki4-7l-tM6"; */
+"ki4-7l-tM6.title" = "New Twitter Feed…";
+
+/* Class = "NSMenuItem"; title = "Show Sidebar"; ObjectID = "kIP-vf-haE"; */
+"kIP-vf-haE.title" = "Show Sidebar";
+
+/* Class = "NSMenuItem"; title = "Today"; ObjectID = "L20-jv-7c9"; */
+"L20-jv-7c9.title" = "Today";
+
+/* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */
+"LE2-aR-0XJ.title" = "Bring All to Front";
+
+/* Class = "NSMenuItem"; title = "Main Window"; ObjectID = "LSZ-ci-Jb5"; */
+"LSZ-ci-Jb5.title" = "Main Window";
+
+/* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "mK6-2p-4JG"; */
+"mK6-2p-4JG.title" = "Check Grammar With Spelling";
+
+/* Class = "NSMenuItem"; title = "New Reddit Feed…"; ObjectID = "n6h-Bp-CIc"; */
+"n6h-Bp-CIc.title" = "New Reddit Feed…";
+
+/* Class = "NSMenuItem"; title = "Article Search…"; ObjectID = "nB2-mv-2i5"; */
+"nB2-mv-2i5.title" = "Article Search…";
+
+/* Class = "NSMenuItem"; title = "Sort Articles By"; ObjectID = "nLP-fa-KUi"; */
+"nLP-fa-KUi.title" = "Sort Articles By";
+
+/* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */
+"NMo-om-nkz.title" = "Services";
+
+/* Class = "NSMenu"; title = "Debug"; ObjectID = "NOT-8E-ykF"; */
+"NOT-8E-ykF.title" = "Debug";
+
+/* Class = "NSMenu"; title = "Go"; ObjectID = "NQW-Ph-lw2"; */
+"NQW-Ph-lw2.title" = "Go";
+
+/* Class = "NSMenuItem"; title = "Clear Image Caches"; ObjectID = "o1k-E6-ctu"; */
+"o1k-E6-ctu.title" = "Clear Image Caches";
+
+/* Class = "NSMenu"; title = "Sort Articles By"; ObjectID = "OlJ-93-6OP"; */
+"OlJ-93-6OP.title" = "Sort Articles By";
+
+/* Class = "NSMenuItem"; title = "Hide NetNewsWire"; ObjectID = "Olw-nP-bQN"; */
+"Olw-nP-bQN.title" = "Hide NetNewsWire";
+
+/* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "OwM-mh-QMV"; */
+"OwM-mh-QMV.title" = "Find Previous";
+
+/* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */
+"OY7-WF-poV.title" = "Minimise";
+
+/* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "Oyz-dy-DGm"; */
+"Oyz-dy-DGm.title" = "Stop Speaking";
+
+/* Class = "NSMenuItem"; title = "Mark Above as Read"; ObjectID = "p1o-EG-Uo8"; */
+"p1o-EG-Uo8.title" = "Mark Above as Read";
+
+/* Class = "NSMenuItem"; title = "Show Reader View"; ObjectID = "p5x-Xq-1fW"; */
+"p5x-Xq-1fW.title" = "Show Reader View";
+
+/* Class = "NSMenuItem"; title = "Delete"; ObjectID = "pa3-QI-u2k"; */
+"pa3-QI-u2k.title" = "Delete";
+
+/* Class = "NSMenuItem"; title = "New Window"; ObjectID = "pGg-Gc-PU2"; */
+"pGg-Gc-PU2.title" = "New Window";
+
+/* Class = "NSMenuItem"; title = "Refresh"; ObjectID = "Ppm-uh-K5n"; */
+"Ppm-uh-K5n.title" = "Refresh";
+
+/* Class = "NSMenuItem"; title = "NetNewsWire Website"; ObjectID = "q2Z-9K-GBd"; */
+"q2Z-9K-GBd.title" = "Website";
+
+/* Class = "NSMenuItem"; title = "Next Unread"; ObjectID = "q3p-nE-m2k"; */
+"q3p-nE-m2k.title" = "Next Unread";
+
+/* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "q09-fT-Sye"; */
+"q09-fT-Sye.title" = "Find Next";
+
+/* Class = "NSMenuItem"; title = "Copy Article URL"; ObjectID = "qNk-By-jKp"; */
+"qNk-By-jKp.title" = "Copy Article URL";
+
+/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */
+"R4o-n2-Eq4.title" = "Zoom";
+
+/* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "rbD-Rh-wIN"; */
+"rbD-Rh-wIN.title" = "Check Spelling While Typing";
+
+/* Class = "NSMenuItem"; title = "Open Application Support Folder"; ObjectID = "rg6-L6-JUr"; */
+"rg6-L6-JUr.title" = "Open Application Support Folder";
+
+/* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "rgM-f4-ycn"; */
+"rgM-f4-ycn.title" = "Smart Dashes";
+
+/* Class = "NSMenuItem"; title = "Import Subscriptions…"; ObjectID = "rSl-F4-qo7"; */
+"rSl-F4-qo7.title" = "Import Subscriptions…";
+
+/* Class = "NSMenuItem"; title = "Select All"; ObjectID = "Ruw-6m-B2m"; */
+"Ruw-6m-B2m.title" = "Select All";
+
+/* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "S0p-oC-mLd"; */
+"S0p-oC-mLd.title" = "Jump to Selection";
+
+/* Class = "NSMenuItem"; title = "Show Toolbar"; ObjectID = "snW-S8-Cw5"; */
+"snW-S8-Cw5.title" = "Show Toolbar";
+
+/* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */
+"Td7-aD-5lo.title" = "Window";
+
+/* Class = "NSMenuItem"; title = "Newest Article on Top"; ObjectID = "TNS-TV-n0U"; */
+"TNS-TV-n0U.title" = "Newest Article on Top";
+
+/* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "tRr-pd-1PS"; */
+"tRr-pd-1PS.title" = "Data Detectors";
+
+/* Class = "NSMenuItem"; title = "Article"; ObjectID = "TzI-3g-N0v"; */
+"TzI-3g-N0v.title" = "Article";
+
+/* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "UEZ-Bs-lqG"; */
+"UEZ-Bs-lqG.title" = "Capitalise";
+
+/* Class = "NSMenuItem"; title = "Debug"; ObjectID = "UqE-mp-gtV"; */
+"UqE-mp-gtV.title" = "Debug";
+
+/* Class = "NSMenu"; title = "NetNewsWire"; ObjectID = "uQy-DD-JDr"; */
+"uQy-DD-JDr.title" = "NetNewsWire";
+
+/* Class = "NSMenuItem"; title = "Cut"; ObjectID = "uRl-iY-unG"; */
+"uRl-iY-unG.title" = "Cut";
+
+/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */
+"Vdr-fp-XzO.title" = "Hide Others";
+
+/* Class = "NSMenuItem"; title = "Refresh"; ObjectID = "Veh-SV-KWy"; */
+"Veh-SV-KWy.title" = "Refresh";
+
+/* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "vmV-6d-7jI"; */
+"vmV-6d-7jI.title" = "Make Upper Case";
+
+/* Class = "NSMenuItem"; title = "Mark as Starred"; ObjectID = "vvo-ZM-8kl"; */
+"vvo-ZM-8kl.title" = "Mark as Starred";
+
+/* Class = "NSMenuItem"; title = "Keyboard Shortcuts"; ObjectID = "w6o-j8-cda"; */
+"w6o-j8-cda.title" = "Keyboard Shortcuts";
+
+/* Class = "NSMenu"; title = "Edit"; ObjectID = "W48-6f-4Dl"; */
+"W48-6f-4Dl.title" = "Edit";
+
+/* Class = "NSMenuItem"; title = "New Web Feed…"; ObjectID = "Was-JA-tGl"; */
+"Was-JA-tGl.title" = "New Web Feed…";
+
+/* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "WeT-3V-zwk"; */
+"WeT-3V-zwk.title" = "Paste and Match Style";
+
+/* Class = "NSMenuItem"; title = "New Folder…"; ObjectID = "wkh-LX-Xp1"; */
+"wkh-LX-Xp1.title" = "New Folder…";
+
+/* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */
+"wpr-3q-Mcd.title" = "Help";
+
+/* Class = "NSMenuItem"; title = "Copy"; ObjectID = "x3v-GG-iWU"; */
+"x3v-GG-iWU.title" = "Copy";
+
+/* Class = "NSMenuItem"; title = "Test Crash Log Sender"; ObjectID = "XJG-gO-OKi"; */
+"XJG-gO-OKi.title" = "Test Crash Log Sender";
+
+/* Class = "NSMenuItem"; title = "Speech"; ObjectID = "xrE-MZ-jX0"; */
+"xrE-MZ-jX0.title" = "Speech";
+
+/* Class = "NSMenuItem"; title = "Export Subscriptions…"; ObjectID = "Xy2-v8-Lj8"; */
+"Xy2-v8-Lj8.title" = "Export Subscriptions…";
+
+/* Class = "NSMenuItem"; title = "Find…"; ObjectID = "Xz5-n4-O0W"; */
+"Xz5-n4-O0W.title" = "Find…";
+
+/* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "Ynk-f8-cLZ"; */
+"Ynk-f8-cLZ.title" = "Start Speaking";
+
+/* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "z6F-FW-3nz"; */
+"z6F-FW-3nz.title" = "Show Substitutions";
+
+/* Class = "NSMenuItem"; title = "Starred"; ObjectID = "ZRx-me-QXO"; */
+"ZRx-me-QXO.title" = "Starred";
+
+/* Class = "NSMenuItem"; title = "Group By Feed"; ObjectID = "Zxm-O6-NRE"; */
+"Zxm-O6-NRE.title" = "Group By Feed";
+
diff --git a/Mac/en-GB.lproj/MainWindow.strings b/Mac/en-GB.lproj/MainWindow.strings
new file mode 100644
index 000000000..4fee89250
--- /dev/null
+++ b/Mac/en-GB.lproj/MainWindow.strings
@@ -0,0 +1,36 @@
+/* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "1F7-qu-7oN"; */
+"1F7-qu-7oN.title" = "Item 2";
+
+/* Class = "NSMenuItem"; title = "Sort"; ObjectID = "4BZ-ya-evy"; */
+"4BZ-ya-evy.title" = "Sort";
+
+/* Class = "NSMenuItem"; title = "Newest Article on Top"; ObjectID = "40c-kt-vhO"; */
+"40c-kt-vhO.title" = "Newest Article on Top";
+
+/* Class = "NSOutlineView"; ibExternalAccessibilityDescription = "Feeds"; ObjectID = "cnV-kg-Dn2"; */
+"cnV-kg-Dn2.ibExternalAccessibilityDescription" = "Feeds";
+
+/* Class = "NSTextFieldCell"; title = "HEADER CELL"; ObjectID = "dRB-0K-qxz"; */
+"dRB-0K-qxz.title" = "HEADER CELL";
+
+/* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "dVE-XG-mlU"; */
+"dVE-XG-mlU.title" = "Label";
+
+/* Class = "NSWindow"; title = "NetNewsWire"; ObjectID = "IQv-IB-iLA"; */
+"IQv-IB-iLA.title" = "NetNewsWire";
+
+/* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "r9E-FO-GoU"; */
+"r9E-FO-GoU.title" = "Item 3";
+
+/* Class = "NSMenuItem"; title = "Oldest Article on Top"; ObjectID = "sOF-Ez-vIL"; */
+"sOF-Ez-vIL.title" = "Oldest Article on Top";
+
+/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "sXh-y7-12P"; */
+"sXh-y7-12P.title" = "Text Cell";
+
+/* Class = "NSMenuItem"; title = "Group by Feed"; ObjectID = "YSR-5C-Yjd"; */
+"YSR-5C-Yjd.title" = "Group by Feed";
+
+/* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "ZDH-CV-Y2s"; */
+"ZDH-CV-Y2s.title" = "Item 1";
+
diff --git a/Mac/en-GB.lproj/Preferences.strings b/Mac/en-GB.lproj/Preferences.strings
new file mode 100644
index 000000000..f0bf2346b
--- /dev/null
+++ b/Mac/en-GB.lproj/Preferences.strings
@@ -0,0 +1,120 @@
+/* Class = "NSWindow"; title = "Preferences"; ObjectID = "2C0-LP-36T"; */
+"2C0-LP-36T.title" = "Preferences";
+
+/* Class = "NSMenuItem"; title = "Extra Extra Large"; ObjectID = "4Pi-2Y-XmV"; */
+"4Pi-2Y-XmV.title" = "Extra Extra Large";
+
+/* Class = "NSTextFieldCell"; title = "Download:"; ObjectID = "6bb-c0-guo"; */
+"6bb-c0-guo.title" = "Download:";
+
+/* Class = "NSMenuItem"; title = "Every 4 hours"; ObjectID = "7e2-TV-hth"; */
+"7e2-TV-hth.title" = "Every 4 hours";
+
+/* Class = "NSMenuItem"; title = "Every 8 hours"; ObjectID = "7gV-gL-Nue"; */
+"7gV-gL-Nue.title" = "Every 8 hours";
+
+/* Class = "NSButtonCell"; title = "Check for Updates"; ObjectID = "AaA-Rr-UYD"; */
+"AaA-Rr-UYD.title" = "Check for Updates";
+
+/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "CcS-BO-sdv"; */
+"CcS-BO-sdv.title" = "Table View Cell";
+
+/* Class = "NSTextFieldCell"; title = "Browser:"; ObjectID = "CgU-dE-Qtb"; */
+"CgU-dE-Qtb.title" = "Browser:";
+
+/* Class = "NSMenuItem"; title = "Large"; ObjectID = "ckZ-0Q-rNz"; */
+"ckZ-0Q-rNz.title" = "Large";
+
+/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "Djh-3Q-J0Q"; */
+"Djh-3Q-J0Q.title" = "Text Cell";
+
+/* Class = "NSButtonCell"; title = "Check automatically"; ObjectID = "dm8-Xy-0Ba"; */
+"dm8-Xy-0Ba.title" = "Check automatically";
+
+/* Class = "NSMenuItem"; title = "Manually only"; ObjectID = "doa-Wq-4Uq"; */
+"doa-Wq-4Uq.title" = "Manually only";
+
+/* Class = "NSTextFieldCell"; title = "Press the Shift key to do the opposite."; ObjectID = "EMq-9M-zTJ"; */
+"EMq-9M-zTJ.title" = "Press the Shift key to do the opposite.";
+
+/* Class = "NSTextFieldCell"; title = "Safari Extension:"; ObjectID = "Eth-o0-pWM"; */
+"Eth-o0-pWM.title" = "Safari Extension:";
+
+/* Class = "NSTextFieldCell"; title = "Refresh Feeds:"; ObjectID = "F7c-lm-g97"; */
+"F7c-lm-g97.title" = "Refresh Feeds:";
+
+/* Class = "NSButtonCell"; title = "Release builds"; ObjectID = "F8M-rS-und"; */
+"F8M-rS-und.title" = "Release builds";
+
+/* Class = "NSTextFieldCell"; title = "If you’re not sure, choose Release builds. Test builds may have bugs, which may include crashing bugs and data loss."; ObjectID = "fOZ-zv-QTc"; */
+"fOZ-zv-QTc.title" = "If you’re not sure, choose Release builds. Test builds may have bugs, which may include crashing bugs and data loss.";
+
+/* Class = "NSButtonCell"; title = "Test builds"; ObjectID = "Fuf-rU-D6M"; */
+"Fuf-rU-D6M.title" = "Test builds";
+
+/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "goO-QG-kk7"; */
+"goO-QG-kk7.title" = "Table View Cell";
+
+/* Class = "NSViewController"; title = "General"; ObjectID = "iuH-lz-18x"; */
+"iuH-lz-18x.title" = "General";
+
+/* Class = "NSMenuItem"; title = "Medium"; ObjectID = "jMV-2o-5Oh"; */
+"jMV-2o-5Oh.title" = "Medium";
+
+/* Class = "NSButtonCell"; title = "Send automatically"; ObjectID = "jnc-C5-4oI"; */
+"jnc-C5-4oI.title" = "Send automatically";
+
+/* Class = "NSTextFieldCell"; title = "Article Theme:"; ObjectID = "MQe-Za-N8J"; */
+"MQe-Za-N8J.title" = "Article Theme:";
+
+/* Class = "NSMenuItem"; title = "Every 2 hours"; ObjectID = "o1h-xo-elW"; */
+"o1h-xo-elW.title" = "Every 2 hours";
+
+/* Class = "NSMenuItem"; title = "Safari"; ObjectID = "ObP-qK-qDJ"; */
+"ObP-qK-qDJ.title" = "Safari";
+
+/* Class = "NSMenuItem"; title = "Default"; ObjectID = "Pkl-EA-Goa"; */
+"Pkl-EA-Goa.title" = "Default";
+
+/* Class = "NSTextFieldCell"; title = "Crash logs:"; ObjectID = "qcq-fU-Ks0"; */
+"qcq-fU-Ks0.title" = "Crash logs:";
+
+/* Class = "NSMenuItem"; title = "Every hour"; ObjectID = "Qjt-qr-WER"; */
+"Qjt-qr-WER.title" = "Every hour";
+
+/* Class = "NSMenuItem"; title = "Extra Large"; ObjectID = "qMe-6g-Vme"; */
+"qMe-6g-Vme.title" = "Extra Large";
+
+/* Class = "NSTextFieldCell"; title = "Privacy Policy"; ObjectID = "rJu-r1-AW4"; */
+"rJu-r1-AW4.title" = "Privacy Policy";
+
+/* Class = "NSMenuItem"; title = "Small"; ObjectID = "roB-Mu-Ht7"; */
+"roB-Mu-Ht7.title" = "Small";
+
+/* Class = "NSMenuItem"; title = "Every 30 minutes"; ObjectID = "rZU-Tg-xwo"; */
+"rZU-Tg-xwo.title" = "Every 30 minutes";
+
+/* Class = "NSButtonCell"; title = "Open feeds in default news reader"; ObjectID = "SkZ-tE-blK"; */
+"SkZ-tE-blK.title" = "Open feeds in default news reader";
+
+/* Class = "NSButtonCell"; title = "Open web pages in background in browser"; ObjectID = "t0a-LN-rCv"; */
+"t0a-LN-rCv.title" = "Open web pages in background in browser";
+
+/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "uax-iF-gzP"; */
+"uax-iF-gzP.title" = "Text Cell";
+
+/* Class = "NSButtonCell"; title = "Open feeds in NetNewsWire"; ObjectID = "uvx-O8-HvU"; */
+"uvx-O8-HvU.title" = "Open feeds in NetNewsWire";
+
+/* Class = "NSTextFieldCell"; title = "Article Text Size:"; ObjectID = "xQu-QV-91i"; */
+"xQu-QV-91i.title" = "Article Text Size:";
+
+/* Class = "NSButtonCell"; title = "Open Themes Folder"; ObjectID = "ySX-5i-SP1"; */
+"ySX-5i-SP1.title" = "Open Themes Folder";
+
+/* Class = "NSMenuItem"; title = "Every 10 minutes"; ObjectID = "zmy-xe-mVw"; */
+"zmy-xe-mVw.title" = "Every 10 minutes";
+
+/* Class = "NSTextFieldCell"; title = "App Updates:"; ObjectID = "zqG-X2-E9b"; */
+"zqG-X2-E9b.title" = "App Updates:";
+
diff --git a/Mac/en-GB.lproj/RenameSheet.strings b/Mac/en-GB.lproj/RenameSheet.strings
new file mode 100644
index 000000000..9352abe5f
--- /dev/null
+++ b/Mac/en-GB.lproj/RenameSheet.strings
@@ -0,0 +1,12 @@
+/* Class = "NSTextFieldCell"; title = "<>"; ObjectID = "7Os-lV-Jer"; */
+"7Os-lV-Jer.title" = "<>";
+
+/* Class = "NSButtonCell"; title = "Rename"; ObjectID = "gcS-uM-zjx"; */
+"gcS-uM-zjx.title" = "Rename";
+
+/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "oe4-cJ-2V8"; */
+"oe4-cJ-2V8.title" = "Cancel";
+
+/* Class = "NSWindow"; title = "Rename"; ObjectID = "QvC-M9-y7g"; */
+"QvC-M9-y7g.title" = "Rename";
+
diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj
index 8d62caec5..a319d30e9 100644
--- a/NetNewsWire.xcodeproj/project.pbxproj
+++ b/NetNewsWire.xcodeproj/project.pbxproj
@@ -8,7 +8,6 @@
/* Begin PBXBuildFile section */
1701E1B52568983D009453D8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1701E1B72568983D009453D8 /* Localizable.strings */; };
- 1701E1E725689D1E009453D8 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1701E1E625689D1E009453D8 /* Localized.swift */; };
17071EF026F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; };
17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */; };
1710B9132552354E00679C0D /* AddAccountHelpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1710B9122552354E00679C0D /* AddAccountHelpView.swift */; };
@@ -49,7 +48,6 @@
179D280D26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */; };
179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; };
179DB3CE822BFCC2D774D9F4 /* AccountsNewsBlurWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */; };
- 17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */; };
17D643B126F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */; };
17D643B226F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */; };
17E0084625941887000C23F0 /* SizeCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E0084525941887000C23F0 /* SizeCategories.swift */; };
@@ -783,7 +781,6 @@
84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; };
84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; };
84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
- B20180AB28E3B76F0059686A /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = B20180AA28E3B76F0059686A /* Localizable.stringsdict */; };
B24E9ADC245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; };
B24E9ADD245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; };
B24E9ADE245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; };
@@ -832,9 +829,24 @@
DF59F072292085B800ACD33D /* ColorPaletteSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */; };
DF59F0742920DB5100ACD33D /* AccountsManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */; };
DF5AD10128D6922200CA3BF7 /* SmartFeedSummaryWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1768144D2564BCE000D98635 /* SmartFeedSummaryWidget.swift */; };
+ DF6DE50A2965907A002EC085 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE5082965907A002EC085 /* Localizable.strings */; };
+ DF6DE50D2965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE50B2965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE5102965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE50E2965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE5142965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE5122965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE5152965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE5122965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE5182965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE5162965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE5192965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE5162965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE51C2965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE51A2965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE51D2965924C002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE51A2965924C002EC085 /* InfoPlist.strings */; };
+ DF6DE5202965924D002EC085 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE51E2965924C002EC085 /* Localizable.strings */; };
+ DF6DE5212965924D002EC085 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE51E2965924C002EC085 /* Localizable.strings */; };
+ DF6DE52529659443002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE52329659443002EC085 /* InfoPlist.strings */; };
+ DF6DE5282965971A002EC085 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DF6DE5262965971A002EC085 /* InfoPlist.strings */; };
DF766FED29377FD9006FBBE2 /* ExtensionsManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF766FEC29377FD9006FBBE2 /* ExtensionsManagementView.swift */; };
DF790D6228E990A900455FC7 /* AboutData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF790D6128E990A900455FC7 /* AboutData.swift */; };
DF84E563295122BA0045C334 /* TimelineCustomizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF84E562295122BA0045C334 /* TimelineCustomizerView.swift */; };
+ DF93DB2B296A319000586C0E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DF93DB2D296A319000586C0E /* Localizable.stringsdict */; };
+ DF93DB2F296A42BD00586C0E /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = DF93DB31296A42BD00586C0E /* Localizable.stringsdict */; };
DFB3497A294A962D00BC81AD /* AddAccountListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB34979294A962D00BC81AD /* AddAccountListView.swift */; };
DFB34980294B085100BC81AD /* AccountInspectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB3497F294B085100BC81AD /* AccountInspectorView.swift */; };
DFB34988294B447F00BC81AD /* InjectedNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB34987294B447F00BC81AD /* InjectedNavigationView.swift */; };
@@ -847,6 +859,9 @@
DFB349A0294E87B700BC81AD /* LocalAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB3499F294E87B700BC81AD /* LocalAddAccountView.swift */; };
DFB349A2294E90B500BC81AD /* FeedbinAddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB349A1294E90B500BC81AD /* FeedbinAddAccountView.swift */; };
DFB349A4294E914D00BC81AD /* AccountSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB349A3294E914D00BC81AD /* AccountSectionHeader.swift */; };
+ DFB616A92965300400A359AB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DFB616A72965300400A359AB /* Localizable.strings */; };
+ DFB616AC2965300400A359AB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DFB616AA2965300400A359AB /* Localizable.strings */; };
+ DFB616AD2965300400A359AB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DFB616AA2965300400A359AB /* Localizable.strings */; };
DFBB4EAC2951BC0200639228 /* NNWThemeDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */; };
DFBB4EAD2951BC0200639228 /* NNWThemeDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */; };
DFBB4EAE2951BC0200639228 /* NNWThemeDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */; };
@@ -865,6 +880,7 @@
DFD406FA291FB5E400C02962 /* SettingsRows.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD406F9291FB5E400C02962 /* SettingsRows.swift */; };
DFD406FC291FB63B00C02962 /* SettingsHelpSheets.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD406FB291FB63B00C02962 /* SettingsHelpSheets.swift */; };
DFD406FF291FDC0C00C02962 /* DisplayAndBehaviorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD406FE291FDC0C00C02962 /* DisplayAndBehaviorsView.swift */; };
+ DFD86796295D553D0070D62D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DFD86798295D553D0070D62D /* Localizable.strings */; };
DFE522A32953DEF400376B77 /* CustomInsetGroupedRowStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFE522A22953DEF400376B77 /* CustomInsetGroupedRowStyle.swift */; };
DFFB8FC2279B75E300AC21D7 /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51BC2F4A24D343A500E90810 /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
DFFC4E7428E95C01006B82AF /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFFC4E7328E95C01006B82AF /* AboutView.swift */; };
@@ -1133,7 +1149,6 @@
/* Begin PBXFileReference section */
1701E1B62568983D009453D8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
- 1701E1E625689D1E009453D8 /* Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = ""; };
17071EEF26F8137400F5E71D /* ArticleTheme+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ArticleTheme+Notifications.swift"; sourceTree = ""; };
1710B9122552354E00679C0D /* AddAccountHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountHelpView.swift; sourceTree = ""; };
1710B928255246F900679C0D /* EnableExtensionPointHelpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointHelpView.swift; sourceTree = ""; };
@@ -1164,7 +1179,6 @@
178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountsView.swift; sourceTree = ""; };
179D280C26F73D83003B2E0A /* ArticleThemePlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemePlist.swift; sourceTree = ""; };
179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = ""; };
- 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; };
17D643B026F8A436008D4C05 /* ArticleThemeDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemeDownloader.swift; sourceTree = ""; };
17D7586C2679C21700B17787 /* NetNewsWire-iOS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-iOS-Bridging-Header.h"; sourceTree = ""; };
17E0084525941887000C23F0 /* SizeCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeCategories.swift; sourceTree = ""; };
@@ -1537,7 +1551,6 @@
84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = ""; };
84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; };
- B20180AA28E3B76F0059686A /* Localizable.stringsdict */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; };
B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+NetNewsWire.swift"; sourceTree = ""; };
B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; };
B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; };
@@ -1575,15 +1588,34 @@
DF28B452294FE6C600C4D8CA /* EnableExtensionPointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointView.swift; sourceTree = ""; };
DF28B454294FE74A00C4D8CA /* ExtensionSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionSectionHeader.swift; sourceTree = ""; };
DF28B4562950163F00C4D8CA /* EnableExtensionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionViewModel.swift; sourceTree = ""; };
+ DF332714295BBBB900BFD911 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Main.strings; sourceTree = ""; };
+ DF332716295BBBBF00BFD911 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LaunchScreenPad.strings; sourceTree = ""; };
+ DF332718295BBBC200BFD911 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LaunchScreenPhone.strings; sourceTree = ""; };
DF3630EA2936183D00326FB8 /* OPMLDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLDocument.swift; sourceTree = ""; };
DF3630EE293618A900326FB8 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; };
DF394EFF29357A180081EB6E /* NewArticleNotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArticleNotificationsView.swift; sourceTree = ""; };
DF47CDB1294803AB00FCD57E /* AddExtensionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddExtensionListView.swift; sourceTree = ""; };
DF59F071292085B800ACD33D /* ColorPaletteSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPaletteSelectorView.swift; sourceTree = ""; };
DF59F0732920DB5100ACD33D /* AccountsManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsManagementView.swift; sourceTree = ""; };
+ DF6DE5092965907A002EC085 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
+ DF6DE50C2965924C002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; };
+ DF6DE50F2965924C002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; };
+ DF6DE5112965924C002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; };
+ DF6DE5132965924C002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; };
+ DF6DE5172965924C002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; };
+ DF6DE51B2965924C002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; };
+ DF6DE51F2965924D002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; };
+ DF6DE5222965937C002EC085 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
+ DF6DE52429659443002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; };
+ DF6DE5272965971A002EC085 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/InfoPlist.strings"; sourceTree = ""; };
DF766FEC29377FD9006FBBE2 /* ExtensionsManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsManagementView.swift; sourceTree = ""; };
DF790D6128E990A900455FC7 /* AboutData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutData.swift; sourceTree = ""; };
DF84E562295122BA0045C334 /* TimelineCustomizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineCustomizerView.swift; sourceTree = ""; };
+ DF93DB2C296A319000586C0E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; };
+ DF93DB2E296A324100586C0E /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-GB"; path = "en-GB.lproj/Localizable.stringsdict"; sourceTree = ""; };
+ DF93DB30296A42BD00586C0E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; };
+ DF93DB33296A4A8F00586C0E /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; };
+ DF93DB34296A4A9300586C0E /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-GB"; path = "en-GB.lproj/Localizable.stringsdict"; sourceTree = ""; };
DFB34979294A962D00BC81AD /* AddAccountListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountListView.swift; sourceTree = ""; };
DFB3497F294B085100BC81AD /* AccountInspectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInspectorView.swift; sourceTree = ""; };
DFB34987294B447F00BC81AD /* InjectedNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectedNavigationView.swift; sourceTree = ""; };
@@ -1595,6 +1627,26 @@
DFB3499F294E87B700BC81AD /* LocalAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAddAccountView.swift; sourceTree = ""; };
DFB349A1294E90B500BC81AD /* FeedbinAddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinAddAccountView.swift; sourceTree = ""; };
DFB349A3294E914D00BC81AD /* AccountSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSectionHeader.swift; sourceTree = ""; };
+ DFB616A82965300400A359AB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
+ DFB616AB2965300400A359AB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
+ DFB616AE29653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Main.strings"; sourceTree = ""; };
+ DFB616AF29653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "../en-GB.lproj/MainWindow.strings"; sourceTree = ""; };
+ DFB616B029653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "../../../en-GB.lproj/RenameSheet.strings"; sourceTree = ""; };
+ DFB616B129653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "../en-GB.lproj/AddRedditFeedSheet.strings"; sourceTree = ""; };
+ DFB616B229653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "../en-GB.lproj/AddTwitterFeedSheet.strings"; sourceTree = ""; };
+ DFB616B329653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "../en-GB.lproj/AddWebFeedSheet.strings"; sourceTree = ""; };
+ DFB616B429653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "../../en-GB.lproj/AddFolderSheet.strings"; sourceTree = ""; };
+ DFB616B529653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "../en-GB.lproj/Preferences.strings"; sourceTree = ""; };
+ DFB616B629653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/SafariExtensionViewController.strings"; sourceTree = ""; };
+ DFB616B729653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/ShareViewController.strings"; sourceTree = ""; };
+ DFB616B829653A0600A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/LaunchScreenPhone.strings"; sourceTree = ""; };
+ DFB616B929653A0700A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/LaunchScreenPad.strings"; sourceTree = ""; };
+ DFB616BA29653A0700A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Main.strings"; sourceTree = ""; };
+ DFB616BB29653A0700A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Intents.strings"; sourceTree = ""; };
+ DFB616BC29653A0700A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/MainInterface.strings"; sourceTree = ""; };
+ DFB616BD29653A0700A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; };
+ DFB616BE29653A0700A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; };
+ DFB616BF29653A0700A359AB /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = ""; };
DFBB4EAB2951BC0200639228 /* NNWThemeDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NNWThemeDocument.swift; sourceTree = ""; };
DFBB4EAF2951BCAC00639228 /* ArticleThemeManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemeManagerView.swift; sourceTree = ""; };
DFC14F0E28EA55BD00F6EE86 /* AboutWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; };
@@ -1608,6 +1660,7 @@
DFD406FB291FB63B00C02962 /* SettingsHelpSheets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHelpSheets.swift; sourceTree = ""; };
DFD406FE291FDC0C00C02962 /* DisplayAndBehaviorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayAndBehaviorsView.swift; sourceTree = ""; };
DFD6AACB27ADE80900463FAD /* NewsFax.nnwtheme */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NewsFax.nnwtheme; sourceTree = ""; };
+ DFD86797295D553D0070D62D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
DFE522A22953DEF400376B77 /* CustomInsetGroupedRowStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomInsetGroupedRowStyle.swift; sourceTree = ""; };
DFFC4E7328E95C01006B82AF /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; };
FF3ABF09232599450074C542 /* ArticleSorterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleSorterTests.swift; sourceTree = ""; };
@@ -1781,6 +1834,7 @@
176814792564BE3C00D98635 /* Resources */,
176813FB2564BB2D00D98635 /* Assets.xcassets */,
176813FD2564BB2D00D98635 /* Info.plist */,
+ DF6DE5262965971A002EC085 /* InfoPlist.strings */,
);
path = Widget;
sourceTree = "";
@@ -1808,10 +1862,9 @@
176814792564BE3C00D98635 /* Resources */ = {
isa = PBXGroup;
children = (
- 1701E1E625689D1E009453D8 /* Localized.swift */,
1768147A2564BE5400D98635 /* widget-sample.json */,
1701E1B72568983D009453D8 /* Localizable.strings */,
- 17D0682B2564F47E00C0B37E /* Localizable.stringsdict */,
+ DF93DB31296A42BD00586C0E /* Localizable.stringsdict */,
);
path = Resources;
sourceTree = "";
@@ -1823,6 +1876,8 @@
510C416024E5CDE3008226FD /* ShareViewController.swift */,
510C416224E5CDE3008226FD /* ShareViewController.xib */,
510C416524E5CDE3008226FD /* Info.plist */,
+ DF6DE51E2965924C002EC085 /* Localizable.strings */,
+ DF6DE51A2965924C002EC085 /* InfoPlist.strings */,
510C416624E5CDE3008226FD /* ShareExtension.entitlements */,
);
path = ShareExtension;
@@ -1914,6 +1969,8 @@
children = (
51314666235A7E4600387FDC /* IntentHandler.swift */,
51314665235A7E4600387FDC /* Info.plist */,
+ DF6DE50B2965924C002EC085 /* InfoPlist.strings */,
+ DFB616A72965300400A359AB /* Localizable.strings */,
51314684235A7EB900387FDC /* NetNewsWire_iOS_IntentsExtension.entitlements */,
);
path = IntentsExtension;
@@ -1929,6 +1986,8 @@
51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */,
513C5CEA232571C2003D4054 /* MainInterface.storyboard */,
513C5CED232571C2003D4054 /* Info.plist */,
+ DF6DE52329659443002EC085 /* InfoPlist.strings */,
+ DF6DE5082965907A002EC085 /* Localizable.strings */,
515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */,
);
path = ShareExtension;
@@ -2210,6 +2269,7 @@
6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */,
6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */,
6581C73E20CED60100F4AD34 /* Info.plist */,
+ DF6DE5162965924C002EC085 /* InfoPlist.strings */,
6581C73F20CED60100F4AD34 /* netnewswire-subscribe-to-feed.js */,
6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */,
6581C74320CED60100F4AD34 /* Subscribe_to_Feed.entitlements */,
@@ -2652,11 +2712,13 @@
children = (
849C64671ED37A5D003D8FC0 /* Assets.xcassets */,
84C9FC8922629E8F00D921D6 /* Credits.rtf */,
- B20180AA28E3B76F0059686A /* Localizable.stringsdict */,
+ DF93DB2D296A319000586C0E /* Localizable.stringsdict */,
84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */,
84C9FC9022629ECB00D921D6 /* NetNewsWire.entitlements */,
51F805D32428499E0022C792 /* NetNewsWire-dev.entitlements */,
84C9FC9122629F2200D921D6 /* Info.plist */,
+ DF6DE5122965924C002EC085 /* InfoPlist.strings */,
+ DFB616AA2965300400A359AB /* Localizable.strings */,
65ED409F235DEFF00081F399 /* container-migration.plist */,
17192AE12567B3FE00AAEACA /* org.sparkle-project.Downloader.xpc */,
17192AE22567B3FE00AAEACA /* org.sparkle-project.InstallerConnection.xpc */,
@@ -2712,12 +2774,14 @@
84C9FC9A2262A1A900D921D6 /* Resources */ = {
isa = PBXGroup;
children = (
+ DFD86798295D553D0070D62D /* Localizable.strings */,
5103A9B324216A4200410853 /* blank.html */,
51BB7C302335ACDE008E8144 /* page.html */,
514219572353C28900E07E2C /* main_ios.js */,
17D7586C2679C21700B17787 /* NetNewsWire-iOS-Bridging-Header.h */,
84C9FC9B2262A1A900D921D6 /* Assets.xcassets */,
84C9FC9C2262A1A900D921D6 /* Info.plist */,
+ DF6DE50E2965924C002EC085 /* InfoPlist.strings */,
84BB0F812333426400DED65E /* NetNewsWire.entitlements */,
51F805ED24284C1C0022C792 /* NetNewsWire-dev.entitlements */,
);
@@ -2970,7 +3034,6 @@
176813EF2564BB2C00D98635 /* Sources */,
176813F02564BB2C00D98635 /* Frameworks */,
176813F12564BB2C00D98635 /* Resources */,
- 1701E1BF25689B44009453D8 /* SwiftGen Localization */,
17EF6A1725C4E59D002C9F81 /* Embed Frameworks */,
);
buildRules = (
@@ -3348,6 +3411,7 @@
knownRegions = (
en,
Base,
+ "en-GB",
);
mainGroup = 849C64571ED37A5D003D8FC0;
packageReferences = (
@@ -3388,7 +3452,8 @@
176813FC2564BB2D00D98635 /* Assets.xcassets in Resources */,
1701E1B52568983D009453D8 /* Localizable.strings in Resources */,
1768147B2564BE5400D98635 /* widget-sample.json in Resources */,
- 17D0682C2564F47E00C0B37E /* Localizable.stringsdict in Resources */,
+ DF93DB2F296A42BD00586C0E /* Localizable.stringsdict in Resources */,
+ DF6DE5282965971A002EC085 /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3396,7 +3461,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DF6DE5212965924D002EC085 /* Localizable.strings in Resources */,
510C416424E5CDE3008226FD /* ShareViewController.xib in Resources */,
+ DF6DE51D2965924C002EC085 /* InfoPlist.strings in Resources */,
5132779F2591034D0064F1E7 /* icon.icns in Resources */,
51EFDA1B24E6D16A0085C3D6 /* SafariExt.js in Resources */,
);
@@ -3406,6 +3473,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DFB616A92965300400A359AB /* Localizable.strings in Resources */,
+ DF6DE50D2965924C002EC085 /* InfoPlist.strings in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3414,6 +3483,8 @@
buildActionMask = 2147483647;
files = (
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */,
+ DF6DE52529659443002EC085 /* InfoPlist.strings in Resources */,
+ DF6DE50A2965907A002EC085 /* Localizable.strings in Resources */,
51A9A5E62380C8B20033AADF /* ShareFolderPickerFolderCell.xib in Resources */,
51A9A5E42380C8880033AADF /* ShareFolderPickerAccountCell.xib in Resources */,
513C5CEC232571C2003D4054 /* MainInterface.storyboard in Resources */,
@@ -3432,7 +3503,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DF6DE5202965924D002EC085 /* Localizable.strings in Resources */,
653813502680E2DA007A082C /* ShareViewController.xib in Resources */,
+ DF6DE51C2965924C002EC085 /* InfoPlist.strings in Resources */,
653813512680E2DA007A082C /* icon.icns in Resources */,
653813522680E2DA007A082C /* SafariExt.js in Resources */,
);
@@ -3443,6 +3516,7 @@
buildActionMask = 2147483647;
files = (
6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */,
+ DF6DE5182965924C002EC085 /* InfoPlist.strings in Resources */,
6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */,
6581C74020CED60100F4AD34 /* netnewswire-subscribe-to-feed.js in Resources */,
);
@@ -3461,6 +3535,7 @@
5137C2E526F3F52D009EFEDB /* Sepia.nnwtheme in Resources */,
65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */,
65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */,
+ DF6DE5152965924C002EC085 /* InfoPlist.strings in Resources */,
65ED4058235DEF6C0081F399 /* main.js in Resources */,
65ED40A1235DEFF00081F399 /* container-migration.plist in Resources */,
65ED4059235DEF6C0081F399 /* AccountsAddLocal.xib in Resources */,
@@ -3491,6 +3566,7 @@
65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */,
65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */,
65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */,
+ DFB616AD2965300400A359AB /* Localizable.strings in Resources */,
65ED406E235DEF6C0081F399 /* AddWebFeedSheet.xib in Resources */,
51077C5927A86D16000C71DB /* Hyperlegible.nnwtheme in Resources */,
51DEE81326FB9233006DAA56 /* Appanoose.nnwtheme in Resources */,
@@ -3503,6 +3579,7 @@
buildActionMask = 2147483647;
files = (
65ED4096235DEF770081F399 /* ToolbarItemIcon.pdf in Resources */,
+ DF6DE5192965924C002EC085 /* InfoPlist.strings in Resources */,
65ED4097235DEF770081F399 /* SafariExtensionViewController.xib in Resources */,
65ED4098235DEF770081F399 /* netnewswire-subscribe-to-feed.js in Resources */,
);
@@ -3522,10 +3599,10 @@
511D43D2231FA62C00FB1562 /* GlobalKeyboardShortcuts.plist in Resources */,
84C9FCA12262A1B300D921D6 /* Main.storyboard in Resources */,
51BB7C312335ACDE008E8144 /* page.html in Resources */,
- 512392C324E3451400F11704 /* TwitterAdd.storyboard in Resources */,
516A093723609A3600EAE89B /* SettingsComboTableViewCell.xib in Resources */,
51077C5A27A86D16000C71DB /* Hyperlegible.nnwtheme in Resources */,
DDF9E1D928EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */,
+ DFD86796295D553D0070D62D /* Localizable.strings in Resources */,
51DEE81A26FBFF84006DAA56 /* Promenade.nnwtheme in Resources */,
1768140B2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig in Resources */,
5103A9B424216A4200410853 /* blank.html in Resources */,
@@ -3543,6 +3620,7 @@
514219582353C28900E07E2C /* main_ios.js in Resources */,
DFCE4F9528EF278300405869 /* Thanks.md in Resources */,
51DEE81426FB9233006DAA56 /* Appanoose.nnwtheme in Resources */,
+ DF6DE5102965924C002EC085 /* InfoPlist.strings in Resources */,
51E36E8C239D6765006F47A5 /* AddFeedSelectFolderTableViewCell.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -3561,6 +3639,7 @@
84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */,
5137C2E426F3F52D009EFEDB /* Sepia.nnwtheme in Resources */,
517630042336215100E15FFF /* main.js in Resources */,
+ DFB616AC2965300400A359AB /* Localizable.strings in Resources */,
65ED40A0235DEFF00081F399 /* container-migration.plist in Resources */,
5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */,
51D0214626ED617100FF2E0F /* core.css in Resources */,
@@ -3569,6 +3648,7 @@
DFCE4F9128EF26F100405869 /* About.plist in Resources */,
84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */,
B27EEBF9244D15F3000932E6 /* stylesheet.css in Resources */,
+ DF6DE5142965924C002EC085 /* InfoPlist.strings in Resources */,
5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */,
844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */,
DDF9E1D728EDF2FC000BC355 /* notificationSoundBlip.mp3 in Resources */,
@@ -3590,7 +3670,7 @@
51333D3B2468615D00EB5C91 /* AddRedditFeedSheet.xib in Resources */,
BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */,
5103A9982421643300410853 /* blank.html in Resources */,
- B20180AB28E3B76F0059686A /* Localizable.stringsdict in Resources */,
+ DF93DB2B296A319000586C0E /* Localizable.stringsdict in Resources */,
515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */,
84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */,
51DEE81226FB9233006DAA56 /* Appanoose.nnwtheme in Resources */,
@@ -3611,24 +3691,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 1701E1BF25689B44009453D8 /* SwiftGen Localization */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- );
- name = "SwiftGen Localization";
- outputFileListPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "if ! command -v swiftgen &> /dev/null\nthen\n echo \"swiftgen could not be found\"\n exit\nfi\n\nswiftgen run strings -t structured-swift5 \"$PROJECT_DIR/Widget/Resources/en.lproj/Localizable.strings\" \"$PROJECT_DIR/Widget/Resources/Localizable.stringsdict\" --output \"$PROJECT_DIR/Widget/Resources/Localized.swift\";\n";
- };
515D50802326D02600EE1167 /* Run Script: Verify No Build Settings */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 12;
@@ -3814,7 +3876,6 @@
1768146C2564BD8100D98635 /* WidgetDeepLinks.swift in Sources */,
1768143E2564BCC800D98635 /* TodayWidget.swift in Sources */,
1768142D2564BCA800D98635 /* TimelineProvider.swift in Sources */,
- 1701E1E725689D1E009453D8 /* Localized.swift in Sources */,
176814652564BD7F00D98635 /* WidgetData.swift in Sources */,
1768145E2564BD7B00D98635 /* WidgetDataDecoder.swift in Sources */,
176814132564BC8A00D98635 /* WidgetBundle.swift in Sources */,
@@ -4563,6 +4624,7 @@
isa = PBXVariantGroup;
children = (
1701E1B62568983D009453D8 /* en */,
+ DF93DB33296A4A8F00586C0E /* en-GB */,
);
name = Localizable.strings;
sourceTree = "";
@@ -4571,6 +4633,7 @@
isa = PBXVariantGroup;
children = (
510C416324E5CDE3008226FD /* Base */,
+ DFB616B729653A0600A359AB /* en-GB */,
);
name = ShareViewController.xib;
sourceTree = "";
@@ -4579,6 +4642,8 @@
isa = PBXVariantGroup;
children = (
511D43EE231FBDE800FB1562 /* Base */,
+ DF332716295BBBBF00BFD911 /* en */,
+ DFB616B929653A0700A359AB /* en-GB */,
);
name = LaunchScreenPad.storyboard;
sourceTree = "";
@@ -4588,6 +4653,7 @@
children = (
51314706235C41FC00387FDC /* Base */,
51314714235C420900387FDC /* en */,
+ DFB616BB29653A0700A359AB /* en-GB */,
);
name = Intents.intentdefinition;
sourceTree = "";
@@ -4596,6 +4662,7 @@
isa = PBXVariantGroup;
children = (
51333D3A2468615D00EB5C91 /* Base */,
+ DFB616B129653A0600A359AB /* en-GB */,
);
name = AddRedditFeedSheet.xib;
sourceTree = "";
@@ -4604,14 +4671,25 @@
isa = PBXVariantGroup;
children = (
513C5CEB232571C2003D4054 /* Base */,
+ DFB616BC29653A0700A359AB /* en-GB */,
);
name = MainInterface.storyboard;
sourceTree = "";
};
+ 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 514A8980244FD63F0085E65D /* Base */,
+ DFB616B229653A0600A359AB /* en-GB */,
+ );
+ name = AddTwitterFeedSheet.xib;
+ sourceTree = "";
+ };
6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */ = {
isa = PBXVariantGroup;
children = (
6581C73C20CED60100F4AD34 /* Base */,
+ DFB616B629653A0600A359AB /* en-GB */,
);
name = SafariExtensionViewController.xib;
sourceTree = "";
@@ -4620,6 +4698,7 @@
isa = PBXVariantGroup;
children = (
848363012262A3BC00DA1D35 /* Base */,
+ DFB616B329653A0600A359AB /* en-GB */,
);
name = AddWebFeedSheet.xib;
sourceTree = "";
@@ -4628,6 +4707,7 @@
isa = PBXVariantGroup;
children = (
848363042262A3CC00DA1D35 /* Base */,
+ DFB616B429653A0600A359AB /* en-GB */,
);
name = AddFolderSheet.xib;
sourceTree = "";
@@ -4636,6 +4716,7 @@
isa = PBXVariantGroup;
children = (
848363072262A3DD00DA1D35 /* Base */,
+ DFB616AE29653A0600A359AB /* en-GB */,
);
name = Main.storyboard;
sourceTree = "";
@@ -4644,6 +4725,7 @@
isa = PBXVariantGroup;
children = (
8483630A2262A3F000DA1D35 /* Base */,
+ DFB616B029653A0600A359AB /* en-GB */,
);
name = RenameSheet.xib;
sourceTree = "";
@@ -4652,6 +4734,7 @@
isa = PBXVariantGroup;
children = (
8483630D2262A3FE00DA1D35 /* Base */,
+ DFB616AF29653A0600A359AB /* en-GB */,
);
name = MainWindow.storyboard;
sourceTree = "";
@@ -4660,6 +4743,7 @@
isa = PBXVariantGroup;
children = (
84C9FC8122629E4800D921D6 /* Base */,
+ DFB616B529653A0600A359AB /* en-GB */,
);
name = Preferences.storyboard;
sourceTree = "";
@@ -4668,6 +4752,8 @@
isa = PBXVariantGroup;
children = (
84C9FCA02262A1B300D921D6 /* Base */,
+ DF332714295BBBB900BFD911 /* en */,
+ DFB616BA29653A0700A359AB /* en-GB */,
);
name = Main.storyboard;
sourceTree = "";
@@ -4676,10 +4762,131 @@
isa = PBXVariantGroup;
children = (
84C9FCA32262A1B800D921D6 /* Base */,
+ DF332718295BBBC200BFD911 /* en */,
+ DFB616B829653A0600A359AB /* en-GB */,
);
name = LaunchScreenPhone.storyboard;
sourceTree = "";
};
+ DF6DE5082965907A002EC085 /* Localizable.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE5092965907A002EC085 /* en */,
+ DF6DE5112965924C002EC085 /* en-GB */,
+ );
+ name = Localizable.strings;
+ sourceTree = "";
+ };
+ DF6DE50B2965924C002EC085 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE50C2965924C002EC085 /* en-GB */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+ DF6DE50E2965924C002EC085 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE50F2965924C002EC085 /* en-GB */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+ DF6DE5122965924C002EC085 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE5132965924C002EC085 /* en-GB */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+ DF6DE5162965924C002EC085 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE5172965924C002EC085 /* en-GB */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+ DF6DE51A2965924C002EC085 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE51B2965924C002EC085 /* en-GB */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+ DF6DE51E2965924C002EC085 /* Localizable.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE51F2965924D002EC085 /* en-GB */,
+ DF6DE5222965937C002EC085 /* en */,
+ );
+ name = Localizable.strings;
+ sourceTree = "";
+ };
+ DF6DE52329659443002EC085 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE52429659443002EC085 /* en-GB */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+ DF6DE5262965971A002EC085 /* InfoPlist.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF6DE5272965971A002EC085 /* en-GB */,
+ );
+ name = InfoPlist.strings;
+ sourceTree = "";
+ };
+ DF93DB2D296A319000586C0E /* Localizable.stringsdict */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF93DB2C296A319000586C0E /* en */,
+ DF93DB2E296A324100586C0E /* en-GB */,
+ );
+ name = Localizable.stringsdict;
+ sourceTree = "";
+ };
+ DF93DB31296A42BD00586C0E /* Localizable.stringsdict */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DF93DB30296A42BD00586C0E /* en */,
+ DF93DB34296A4A9300586C0E /* en-GB */,
+ );
+ name = Localizable.stringsdict;
+ sourceTree = "";
+ };
+ DFB616A72965300400A359AB /* Localizable.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DFB616A82965300400A359AB /* en */,
+ DFB616BE29653A0700A359AB /* en-GB */,
+ );
+ name = Localizable.strings;
+ sourceTree = "";
+ };
+ DFB616AA2965300400A359AB /* Localizable.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DFB616AB2965300400A359AB /* en */,
+ DFB616BD29653A0700A359AB /* en-GB */,
+ );
+ name = Localizable.strings;
+ sourceTree = "";
+ };
+ DFD86798295D553D0070D62D /* Localizable.strings */ = {
+ isa = PBXVariantGroup;
+ children = (
+ DFD86797295D553D0070D62D /* en */,
+ DFB616BF29653A0700A359AB /* en-GB */,
+ );
+ name = Localizable.strings;
+ sourceTree = "";
+ };
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
diff --git a/Shared/AccountType+Helpers.swift b/Shared/AccountType+Helpers.swift
index dd5d04083..80d8bae78 100644
--- a/Shared/AccountType+Helpers.swift
+++ b/Shared/AccountType+Helpers.swift
@@ -25,31 +25,34 @@ extension AccountType {
case .onMyMac:
let defaultName: String
#if os(macOS)
- defaultName = NSLocalizedString("On My Mac", comment: "Account name")
+ defaultName = NSLocalizedString("account.name.mac", comment: "On My Mac")
#else
if UIDevice.current.userInterfaceIdiom == .pad {
- defaultName = NSLocalizedString("On My iPad", comment: "Account name")
+ defaultName = NSLocalizedString("account.name.ipad", comment: "On My iPad")
} else {
- defaultName = NSLocalizedString("On My iPhone", comment: "Account name")
+ defaultName = NSLocalizedString("account.name.iphone", comment: "On My iPhone")
}
#endif
return defaultName
+
+ /* The below account names are not localized as they are product names. */
+
case .bazQux:
- return NSLocalizedString("BazQux", comment: "Account name")
+ return "BazQux"
case .cloudKit:
- return NSLocalizedString("iCloud", comment: "Account name")
+ return "iCloud"
case .feedbin:
- return NSLocalizedString("Feedbin", comment: "Account name")
+ return "Feedbin"
case .feedly:
- return NSLocalizedString("Feedly", comment: "Account name")
+ return "Feedly"
case .freshRSS:
- return NSLocalizedString("FreshRSS", comment: "Account name")
+ return "FreshRSS"
case .inoreader:
- return NSLocalizedString("Inoreader", comment: "Account name")
+ return "Inoreader"
case .newsBlur:
- return NSLocalizedString("NewsBlur", comment: "Account name")
+ return "NewsBlur"
case .theOldReader:
- return NSLocalizedString("The Old Reader", comment: "Account name")
+ return "The Old Reader"
}
}
diff --git a/Shared/Activity/ActivityManager.swift b/Shared/Activity/ActivityManager.swift
index b0c5d0784..d3108b261 100644
--- a/Shared/Activity/ActivityManager.swift
+++ b/Shared/Activity/ActivityManager.swift
@@ -70,7 +70,7 @@ class ActivityManager {
guard nextUnreadActivity == nil else { return }
nextUnreadActivity = NSUserActivity(activityType: ActivityType.nextUnread.rawValue)
- nextUnreadActivity!.title = NSLocalizedString("See first unread article", comment: "First Unread")
+ nextUnreadActivity!.title = NSLocalizedString("activity.title.see-first-unread-article", comment: "See first unread article")
#if os(iOS)
nextUnreadActivity!.suggestedInvocationPhrase = nextUnreadActivity!.title
@@ -165,7 +165,7 @@ private extension ActivityManager {
func makeSelectFeedActivity(feed: Feed) -> NSUserActivity {
let activity = NSUserActivity(activityType: ActivityType.selectFeed.rawValue)
- let localizedText = NSLocalizedString("See articles in “%@”", comment: "See articles in Folder")
+ let localizedText = NSLocalizedString("activity.title.see-article-in.folder.%@", comment: "See articles in “%@”")
let title = NSString.localizedStringWithFormat(localizedText as NSString, feed.nameForDisplay) as String
activity.title = title
diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift
index 9ecd6a7b7..b44bb2374 100644
--- a/Shared/Article Rendering/ArticleRenderer.swift
+++ b/Shared/Article Rendering/ArticleRenderer.swift
@@ -210,7 +210,7 @@ private extension ArticleRenderer {
d["preferred_link"] = article.preferredLink ?? ""
if let externalLink = article.externalLink, externalLink != article.preferredLink {
- d["external_link_label"] = NSLocalizedString("Link:", comment: "Link")
+ d["external_link_label"] = NSLocalizedString("label.text.link", comment: "Link: ")
d["external_link_stripped"] = externalLink.strippingHTTPOrHTTPSScheme
d["external_link"] = externalLink
} else {
diff --git a/Shared/Article Rendering/ArticleTextSize.swift b/Shared/Article Rendering/ArticleTextSize.swift
index bbc433974..defc24eb8 100644
--- a/Shared/Article Rendering/ArticleTextSize.swift
+++ b/Shared/Article Rendering/ArticleTextSize.swift
@@ -35,15 +35,15 @@ enum ArticleTextSize: Int, CaseIterable, Identifiable {
func description() -> String {
switch self {
case .small:
- return NSLocalizedString("Small", comment: "Small")
+ return NSLocalizedString("label.text.small", comment: "Small")
case .medium:
- return NSLocalizedString("Medium", comment: "Medium")
+ return NSLocalizedString("label.text.medium", comment: "Medium")
case .large:
- return NSLocalizedString("Large", comment: "Large")
+ return NSLocalizedString("label.text.large", comment: "Large")
case .xlarge:
- return NSLocalizedString("Extra Large", comment: "X-Large")
+ return NSLocalizedString("label.text.extra-large", comment: "X-Large")
case .xxlarge:
- return NSLocalizedString("Extra Extra Large", comment: "XX-Large")
+ return NSLocalizedString("label.text.extra-extra-large", comment: "XX-Large")
}
}
diff --git a/Shared/ArticleStyles/ArticleTheme.swift b/Shared/ArticleStyles/ArticleTheme.swift
index c2d4e31cd..afd5a1d80 100644
--- a/Shared/ArticleStyles/ArticleTheme.swift
+++ b/Shared/ArticleStyles/ArticleTheme.swift
@@ -20,6 +20,7 @@ struct ArticleTheme: Equatable {
static let defaultTheme = ArticleTheme()
static let nnwThemeSuffix = ".nnwtheme"
+ // Don't localize the theme names.
private static let defaultThemeName = "NetNewsWire"
private static let unknownValue = NSLocalizedString("Unknown", comment: "Unknown Value")
diff --git a/Shared/ArticleStyles/ArticleThemesManager.swift b/Shared/ArticleStyles/ArticleThemesManager.swift
index 71d24842d..92e2554c7 100644
--- a/Shared/ArticleStyles/ArticleThemesManager.swift
+++ b/Shared/ArticleStyles/ArticleThemesManager.swift
@@ -167,8 +167,11 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable
menu.addItem(defaultMenuItem)
menu.addItem(NSMenuItem.separator())
- let rancheroHeading = NSMenuItem(title: "Built-in Themes", action: nil, keyEquivalent: "")
- rancheroHeading.attributedTitle = NSAttributedString(string: "Built-in Themes", attributes: [NSAttributedString.Key.foregroundColor : NSColor.secondaryLabelColor, NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 12)])
+ let rancheroString = NSLocalizedString("label.text.themes-builtin", comment: "Built-in Themes")
+ let otherString = NSLocalizedString("label.text.themes-builtin", comment: "Other Themes")
+
+ let rancheroHeading = NSMenuItem(title: rancheroString, action: nil, keyEquivalent: "")
+ rancheroHeading.attributedTitle = NSAttributedString(string: rancheroString, attributes: [NSAttributedString.Key.foregroundColor : NSColor.secondaryLabelColor, NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 12)])
rancheroHeading.isEnabled = false
menu.addItem(rancheroHeading)
@@ -185,8 +188,8 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable
menu.addItem(NSMenuItem.separator())
- let thirdPartyHeading = NSMenuItem(title: "Other Themes", action: nil, keyEquivalent: "")
- thirdPartyHeading.attributedTitle = NSAttributedString(string: "Other Themes", attributes: [NSAttributedString.Key.foregroundColor : NSColor.secondaryLabelColor, NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 12)])
+ let thirdPartyHeading = NSMenuItem(title: otherString, action: nil, keyEquivalent: "")
+ thirdPartyHeading.attributedTitle = NSAttributedString(string: otherString, attributes: [NSAttributedString.Key.foregroundColor : NSColor.secondaryLabelColor, NSAttributedString.Key.font: NSFont.boldSystemFont(ofSize: 12)])
thirdPartyHeading.isEnabled = false
menu.addItem(thirdPartyHeading)
diff --git a/Shared/Commands/DeleteCommand.swift b/Shared/Commands/DeleteCommand.swift
index 991b7b430..7ec5fb393 100644
--- a/Shared/Commands/DeleteCommand.swift
+++ b/Shared/Commands/DeleteCommand.swift
@@ -259,11 +259,11 @@ private extension Node {
private struct DeleteActionName {
- private static let deleteFeed = NSLocalizedString("Delete Feed", comment: "command")
- private static let deleteFeeds = NSLocalizedString("Delete Feeds", comment: "command")
- private static let deleteFolder = NSLocalizedString("Delete Folder", comment: "command")
- private static let deleteFolders = NSLocalizedString("Delete Folders", comment: "command")
- private static let deleteFeedsAndFolders = NSLocalizedString("Delete Feeds and Folders", comment: "command")
+ private static let deleteFeed = NSLocalizedString("button.title.delete-feed", comment: "Delete Feed")
+ private static let deleteFeeds = NSLocalizedString("button.title.delete-feeds", comment: "Delete Feeds")
+ private static let deleteFolder = NSLocalizedString("button.title.delete-folder", comment: "Delete Folder")
+ private static let deleteFolders = NSLocalizedString("button.title.delete-folders", comment: "Delete Folders")
+ private static let deleteFeedsAndFolders = NSLocalizedString("button.title.delete-feeds-and-folders", comment: "Delete Feeds and Folders")
static func name(for nodes: [Node]) -> String? {
diff --git a/Shared/Commands/MarkStatusCommand.swift b/Shared/Commands/MarkStatusCommand.swift
index f4af7869a..45502075a 100644
--- a/Shared/Commands/MarkStatusCommand.swift
+++ b/Shared/Commands/MarkStatusCommand.swift
@@ -111,10 +111,10 @@ private extension MarkStatusCommand {
Account.UserInfoKey.statusFlag: flag])
}
- static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command")
- static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command")
- static private let markStarredActionName = NSLocalizedString("Mark Starred", comment: "command")
- static private let markUnstarredActionName = NSLocalizedString("Mark Unstarred", comment: "command")
+ static private let markReadActionName = NSLocalizedString("button.title.mark-read", comment: "Mark Read")
+ static private let markUnreadActionName = NSLocalizedString("button.title.mark-unread", comment: "Mark Unread")
+ static private let markStarredActionName = NSLocalizedString("button.title.mark-starred", comment: "Mark Starred")
+ static private let markUnstarredActionName = NSLocalizedString("button.title.mark-unstarred", comment: "Mark Unstarred")
static func actionName(_ statusKey: ArticleStatus.Key, _ flag: Bool) -> String {
diff --git a/Shared/ExtensionPoints/ExtensionPointManager.swift b/Shared/ExtensionPoints/ExtensionPointManager.swift
index 39e177fcf..1cac7f967 100644
--- a/Shared/ExtensionPoints/ExtensionPointManager.swift
+++ b/Shared/ExtensionPoints/ExtensionPointManager.swift
@@ -21,7 +21,7 @@ public enum ExtensionPointManagerError: LocalizedError {
public var localizedDescription: String {
switch self {
case .unableToCreate:
- return NSLocalizedString("Unable to create extension.", comment: "Unable to create extension")
+ return NSLocalizedString("error.message.unable-to-create-extension", comment: "Unable to create extension.")
}
}
}
diff --git a/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift b/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift
index 0f4731e01..de21775fe 100644
--- a/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift
+++ b/Shared/ExtensionPoints/RedditFeedProvider-Extensions.swift
@@ -13,7 +13,7 @@ extension RedditFeedProvider: ExtensionPoint {
static var isSinglton = false
static var isDeveloperBuildRestricted = true
- static var title = NSLocalizedString("Reddit", comment: "Reddit")
+ static var title = "Reddit" // not localized - product name
static var image = AppAssets.extensionPointReddit
static var description: NSAttributedString = {
return RedditFeedProvider.makeAttrString("This extension enables you to subscribe to Reddit URLs as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.")
diff --git a/Shared/ExtensionPoints/SendToMarsEditCommand.swift b/Shared/ExtensionPoints/SendToMarsEditCommand.swift
index c921d71c3..2f24bf82f 100644
--- a/Shared/ExtensionPoints/SendToMarsEditCommand.swift
+++ b/Shared/ExtensionPoints/SendToMarsEditCommand.swift
@@ -14,10 +14,10 @@ final class SendToMarsEditCommand: ExtensionPoint, SendToCommand {
static var isSinglton = true
static var isDeveloperBuildRestricted = false
- static var title = NSLocalizedString("MarsEdit", comment: "MarsEdit")
+ static var title = "MarsEdit" // not localized
static var image = AppAssets.extensionPointMarsEdit
static var description: NSAttributedString = {
- let attrString = SendToMarsEditCommand.makeAttrString("This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.")
+ let attrString = SendToMarsEditCommand.makeAttrString(NSLocalizedString("label.text.marsedit.explainer", comment: "This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work."))
let range = NSRange(location: 81, length: 8)
attrString.beginEditing()
attrString.addAttribute(NSAttributedString.Key.link, value: "https://red-sweater.com/marsedit/", range: range)
diff --git a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift
index 1c8bc031b..c87314bda 100644
--- a/Shared/ExtensionPoints/SendToMicroBlogCommand.swift
+++ b/Shared/ExtensionPoints/SendToMicroBlogCommand.swift
@@ -16,10 +16,10 @@ final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand {
static var isSinglton = true
static var isDeveloperBuildRestricted = false
- static var title: String = NSLocalizedString("Micro.blog", comment: "Micro.blog")
+ static var title: String = "Micro.blog" // not localized
static var image = AppAssets.extensionPointMicroblog
static var description: NSAttributedString = {
- let attrString = SendToMicroBlogCommand.makeAttrString("This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.")
+ let attrString = SendToMicroBlogCommand.makeAttrString(NSLocalizedString("label.text.micro-blog-expaliner", comment: "This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work."))
let range = NSRange(location: 81, length: 10)
attrString.beginEditing()
attrString.addAttribute(NSAttributedString.Key.link, value: "https://micro.blog", range: range)
diff --git a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift
index 0e538066d..89b170936 100644
--- a/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift
+++ b/Shared/ExtensionPoints/TwitterFeedProvider-Extensions.swift
@@ -13,7 +13,7 @@ extension TwitterFeedProvider: ExtensionPoint {
static var isSinglton = false
static var isDeveloperBuildRestricted = true
- static var title = NSLocalizedString("Twitter", comment: "Twitter")
+ static var title = "Twitter" // not localized - product name
static var image = AppAssets.extensionPointTwitter
static var description: NSAttributedString = {
return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URLs as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.")
diff --git a/Shared/Localizations/LocalizedNetNewsWireError.swift b/Shared/Localizations/LocalizedNetNewsWireError.swift
index e033e6626..5cee252d0 100644
--- a/Shared/Localizations/LocalizedNetNewsWireError.swift
+++ b/Shared/Localizations/LocalizedNetNewsWireError.swift
@@ -21,26 +21,46 @@ public enum LocalizedNetNewsWireError: LocalizedError {
case userNameAndPasswordRequired
+ case userNamePasswordAndURLRequired
+
+ case userNameRequired
+
case invalidUsernameOrPassword
+ case invalidURL
+
case keychainError
case duplicateDefaultTheme
+
+ case networkError
+
+ case unrecognizedAccount
public var errorDescription: String? {
switch self {
case .duplicateAccount:
- return NSLocalizedString("There is already an account of that type with that username created.", comment: "Error message: duplicate account with same username.")
+ return NSLocalizedString("alert.error.duplicate-account-username", comment: "There is already an account of that type with that username created.")
case .iCloudDriveMissing:
- return NSLocalizedString("Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.", comment: "Error message: The user cannot enable the iCloud account becasue iCloud or iCloud Drive isn't enabled in Settings.")
+ return NSLocalizedString("alert.error.cloudkit-missing", comment: "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.")
case .userNameAndPasswordRequired:
- return NSLocalizedString("Username and password required", comment: "Error message: The user must provide a username and password.")
+ return NSLocalizedString("alert.error.username-and-password-required", comment: "Error message: The user must provide a username and password.")
+ case .userNamePasswordAndURLRequired:
+ return NSLocalizedString("alert.error.username-password-url-required", comment: "The user must provide a username, password, and URL.")
+ case .userNameRequired:
+ return NSLocalizedString("alert.error.username-required", comment: "Username required.")
case .invalidUsernameOrPassword:
- return NSLocalizedString("Invalid username or password", comment: "Error message: The user provided an invalid username or password.")
+ return NSLocalizedString("alert.error.invalid-username-or-password", comment: "Error message: The user provided an invalid username or password.")
+ case .invalidURL:
+ return NSLocalizedString("alert.error.invalid-api-url", comment: "Invalid API URL.")
case .keychainError:
- return NSLocalizedString("Keychain error while storing credentials.", comment: "Error message: Unable to save due a Keychain error.")
+ return NSLocalizedString("alert.error.keychain-error", comment: "Error message: Unable to save due a Keychain error.")
case .duplicateDefaultTheme:
- return NSLocalizedString("You cannot import a theme that shares the same name as a provided theme.", comment: "Error message: cannot import theme as this is a duplicate of a provided theme.")
+ return NSLocalizedString("alert.error.theme-duplicate-of-provided", comment: "Error message: This theme shares the same name as a provided theme and cannot be imported.")
+ case .networkError:
+ return NSLocalizedString("alert.error.network-error", comment: "Network error. Please try later.")
+ case .unrecognizedAccount:
+ return NSLocalizedString("alert.error.unrecognized-account", comment: "The account type in invalid.")
}
}
}
diff --git a/Shared/SmartFeeds/SearchFeedDelegate.swift b/Shared/SmartFeeds/SearchFeedDelegate.swift
index 2f026d9f8..90b784d8a 100644
--- a/Shared/SmartFeeds/SearchFeedDelegate.swift
+++ b/Shared/SmartFeeds/SearchFeedDelegate.swift
@@ -22,7 +22,7 @@ struct SearchFeedDelegate: SmartFeedDelegate {
return nameForDisplayPrefix + searchString
}
- let nameForDisplayPrefix = NSLocalizedString("Search: ", comment: "Search smart feed title prefix")
+ let nameForDisplayPrefix = NSLocalizedString("textfield.placeholder.search", comment: "Search: ")
let searchString: String
let fetchType: FetchType
var smallIcon: IconImage? = AppAssets.searchFeedImage
diff --git a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift
index 5ac8e03bc..d8de2851a 100644
--- a/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift
+++ b/Shared/SmartFeeds/SearchTimelineFeedDelegate.swift
@@ -22,7 +22,7 @@ struct SearchTimelineFeedDelegate: SmartFeedDelegate {
return nameForDisplayPrefix + searchString
}
- let nameForDisplayPrefix = NSLocalizedString("Search: ", comment: "Search smart feed title prefix")
+ let nameForDisplayPrefix = NSLocalizedString("textfield.placeholder.search", comment: "Search: ")
let searchString: String
let fetchType: FetchType
var smallIcon: IconImage? = AppAssets.searchFeedImage
diff --git a/Shared/SmartFeeds/SmartFeedsController.swift b/Shared/SmartFeeds/SmartFeedsController.swift
index 75288e496..41fbc6700 100644
--- a/Shared/SmartFeeds/SmartFeedsController.swift
+++ b/Shared/SmartFeeds/SmartFeedsController.swift
@@ -17,7 +17,7 @@ final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable {
}
public static let shared = SmartFeedsController()
- let nameForDisplay = NSLocalizedString("Smart Feeds", comment: "Smart Feeds group title")
+ let nameForDisplay = NSLocalizedString("smartfeeds.title", comment: "Smart Feeds group title")
var smartFeeds = [Feed]()
let todayFeed = SmartFeed(delegate: TodayFeedDelegate())
diff --git a/Shared/SmartFeeds/StarredFeedDelegate.swift b/Shared/SmartFeeds/StarredFeedDelegate.swift
index bb6b9a59f..012e7a58e 100644
--- a/Shared/SmartFeeds/StarredFeedDelegate.swift
+++ b/Shared/SmartFeeds/StarredFeedDelegate.swift
@@ -20,7 +20,7 @@ struct StarredFeedDelegate: SmartFeedDelegate {
return FeedIdentifier.smartFeed(String(describing: StarredFeedDelegate.self))
}
- let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title")
+ let nameForDisplay = NSLocalizedString("smartfeed.title.starred", comment: "Starred pseudo-feed title")
let fetchType: FetchType = .starred(nil)
var smallIcon: IconImage? {
return AppAssets.starredFeedImage
diff --git a/Shared/SmartFeeds/TodayFeedDelegate.swift b/Shared/SmartFeeds/TodayFeedDelegate.swift
index ad6e47977..8d2aa361a 100644
--- a/Shared/SmartFeeds/TodayFeedDelegate.swift
+++ b/Shared/SmartFeeds/TodayFeedDelegate.swift
@@ -18,7 +18,7 @@ struct TodayFeedDelegate: SmartFeedDelegate {
return FeedIdentifier.smartFeed(String(describing: TodayFeedDelegate.self))
}
- let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title")
+ let nameForDisplay = NSLocalizedString("smartfeed.title.today", comment: "Today pseudo-feed title")
let fetchType = FetchType.today(nil)
var smallIcon: IconImage? {
return AppAssets.todayFeedImage
diff --git a/Shared/SmartFeeds/UnreadFeed.swift b/Shared/SmartFeeds/UnreadFeed.swift
index 418671fc7..b0fe3a44f 100644
--- a/Shared/SmartFeeds/UnreadFeed.swift
+++ b/Shared/SmartFeeds/UnreadFeed.swift
@@ -30,7 +30,7 @@ final class UnreadFeed: PseudoFeed {
return FeedIdentifier.smartFeed(String(describing: UnreadFeed.self))
}
- let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title")
+ let nameForDisplay = NSLocalizedString("smartfeed.title.allunread", comment: "All Unread pseudo-feed title")
let fetchType = FetchType.unread(nil)
var unreadCount = 0 {
diff --git a/Shared/Timer/RefreshInterval.swift b/Shared/Timer/RefreshInterval.swift
index 603ed2cac..178462d9d 100644
--- a/Shared/Timer/RefreshInterval.swift
+++ b/Shared/Timer/RefreshInterval.swift
@@ -41,19 +41,19 @@ enum RefreshInterval: Int, CaseIterable, Identifiable {
func description() -> String {
switch self {
case .manually:
- return NSLocalizedString("Manually", comment: "Manually")
+ return NSLocalizedString("button.title.manually", comment: "Manually")
case .every10Minutes:
- return NSLocalizedString("Every 10 Minutes", comment: "Every 10 Minutes")
+ return NSLocalizedString("button.title.10-minutes", comment: "Every 10 Minutes")
case .every30Minutes:
- return NSLocalizedString("Every 30 Minutes", comment: "Every 30 Minutes")
+ return NSLocalizedString("button.title.30-minutes", comment: "Every 30 Minutes")
case .everyHour:
- return NSLocalizedString("Every Hour", comment: "Every Hour")
+ return NSLocalizedString("button.title.every-hour", comment: "Every Hour")
case .every2Hours:
- return NSLocalizedString("Every 2 Hours", comment: "Every 2 Hours")
+ return NSLocalizedString("button.title.2-hours", comment: "Every 2 Hours")
case .every4Hours:
- return NSLocalizedString("Every 4 Hours", comment: "Every 4 Hours")
+ return NSLocalizedString("button.title.4-hours", comment: "Every 4 Hours")
case .every8Hours:
- return NSLocalizedString("Every 8 Hours", comment: "Every 8 Hours")
+ return NSLocalizedString("button.title.8-hours", comment: "Every 8 Hours")
}
}
diff --git a/Shared/UserNotifications/UserNotificationManager.swift b/Shared/UserNotifications/UserNotificationManager.swift
index 2a20f693b..231641caa 100644
--- a/Shared/UserNotifications/UserNotificationManager.swift
+++ b/Shared/UserNotifications/UserNotificationManager.swift
@@ -88,9 +88,9 @@ private extension UserNotificationManager {
}
func registerCategoriesAndActions() {
- let readAction = UNNotificationAction(identifier: "MARK_AS_READ", title: NSLocalizedString("Mark as Read", comment: "Mark as Read"), options: [])
- let starredAction = UNNotificationAction(identifier: "MARK_AS_STARRED", title: NSLocalizedString("Mark as Starred", comment: "Mark as Starred"), options: [])
- let openAction = UNNotificationAction(identifier: "OPEN_ARTICLE", title: NSLocalizedString("Open", comment: "Open"), options: [.foreground])
+ let readAction = UNNotificationAction(identifier: "MARK_AS_READ", title: NSLocalizedString("button.title.mark-as-read", comment: "Mark as Read"), options: [])
+ let starredAction = UNNotificationAction(identifier: "MARK_AS_STARRED", title: NSLocalizedString("button.title.mark-as-starred", comment: "Mark as Starred"), options: [])
+ let openAction = UNNotificationAction(identifier: "OPEN_ARTICLE", title: NSLocalizedString("button.title.open", comment: "Open"), options: [.foreground])
let newArticleCategory =
UNNotificationCategory(identifier: "NEW_ARTICLE_NOTIFICATION_CATEGORY",
diff --git a/Technotes/Localization.md b/Technotes/Localization.md
new file mode 100644
index 000000000..8efc1b86e
--- /dev/null
+++ b/Technotes/Localization.md
@@ -0,0 +1,70 @@
+# Localization
+
+NetNewsWire is an Internationalized and Localized app.
+
+## Internationalization
+
+`Internationalized` means that, at code-level, we don't use locale-dependent strings. Instead, in code we use a key-based approach. Generally, this takes the form of:
+
+`UIKit` or `AppKit` — using `NSLocalizedString`:
+
+```swift
+let messageFormat = NSLocalizedString("alert.title.open-articles-in-browser.%ld", comment: "Are you sure you want to open %ld articles in your browser?")
+alert.messageText = String.localizedStringWithFormat(messageFormat, urlStrings.count)
+```
+
+or, in `SwiftUI` — using the `Text` struct:
+
+```swift
+Text("alert.title.remove-account.\(viewModel.accountToDelete?.nameForDisplay ?? "")", comment: "Are you sure you want to remove “%@“?")
+```
+
+### Key Format
+
+All keys are lower cased and follow the `dot.separated` UI element name -> `hyphen-separated` string descriptor -> `dot.separated` variable list format, e.g.:
+
+```
+alert.title.open-articles-in-browser.%ld
+button.title.close
+```
+
+### Comments
+
+Whether using `NSLocalizedString` or `Text`, a `comment` must be provided. This will generally be a reference to the string in English. However, where a key contains multiple variables, the ordering of the variables must be specified in the comment.
+
+
+## Localization
+
+All of NetNewsWire's strings in code are localized in external resources — `.strings` or `.stringsdict`. Each target has its own `Localizable.strings` (and, where necessary, `.stringsdict`) files. All Storyboards are also localized.
+
+### Adding New Strings
+
+If you are developing a new feature that introduces new strings to the code base, follow these general guidelines:
+
+- Check if there is an existing string in `Localizable.strings` that meets your needs. If there is, use that.
+- If there isn't:
+ - Add your string in code following the key and comment rules above
+ - Run `Export Localizations`
+ - Open the `en.xcloc` file and provide the new translations.
+ - Save the `en.xcloc` file.
+ - Run `Import Localizations`
+ - Select `en.xcloc` and Import.
+
+### Updating Existing Translations
+
+Update the Development Language translation first:
+
+- Run `Export Localizations`
+- Open the `en.xcloc` file and provide the new translations.
+- Save the `en.xcloc` file.
+- Run `Import Localizations`
+- Select `en.xcloc` and Import.
+
+Then update other lanaguages:
+
+- Run `Export Localizations`
+- Open the `en-GB.xcloc` file and provide the new translations.
+- Save the `en-GB.xcloc` file.
+- Run `Import Localizations`
+- Select `en-GB.xcloc` and Import.
+
diff --git a/Widget/Resources/Localized.swift b/Widget/Resources/Localized.swift
deleted file mode 100644
index f8a05b221..000000000
--- a/Widget/Resources/Localized.swift
+++ /dev/null
@@ -1,86 +0,0 @@
-// swiftlint:disable all
-// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
-
-import Foundation
-
-// swiftlint:disable superfluous_disable_command file_length implicit_return
-
-// MARK: - Strings
-
-// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length
-// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
-internal enum L10n {
- /// Plural format key: "%#@localized_count@"
- internal static func localizedCount(_ p1: Int) -> String {
- return L10n.tr("Localizable", "LocalizedCount", p1)
- }
- /// Your smart feeds, summarized.
- internal static let smartFeedSummaryWidgetDescription = L10n.tr("Localizable", "SmartFeedSummary_Widget_Description")
- /// Your Smart Feed Summary
- internal static let smartFeedSummaryWidgetTitle = L10n.tr("Localizable", "SmartFeedSummary_Widget_Title")
- /// Starred
- internal static let starred = L10n.tr("Localizable", "Starred")
- /// A sneak peek at your starred articles.
- internal static let starredWidgetDescription = L10n.tr("Localizable", "Starred_Widget_Description")
- /// When you mark articles as Starred, they'll appear here.
- internal static let starredWidgetNoItems = L10n.tr("Localizable", "Starred_Widget_NoItems")
- /// Starred
- internal static let starredWidgetNoItemsTitle = L10n.tr("Localizable", "Starred_Widget_NoItemsTitle")
- /// Your Starred Articles
- internal static let starredWidgetTitle = L10n.tr("Localizable", "Starred_Widget_Title")
- /// Plural format key: "%#@starred_count@"
- internal static func starredCount(_ p1: Int) -> String {
- return L10n.tr("Localizable", "StarredCount", p1)
- }
- /// Today
- internal static let today = L10n.tr("Localizable", "Today")
- /// A sneak peek at recently published unread articles.
- internal static let todayWidgetDescription = L10n.tr("Localizable", "Today_Widget_Description")
- /// There are no recent unread articles left to read.
- internal static let todayWidgetNoItems = L10n.tr("Localizable", "Today_Widget_NoItems")
- /// Today
- internal static let todayWidgetNoItemsTitle = L10n.tr("Localizable", "Today_Widget_NoItemsTitle")
- /// Your Today Articles
- internal static let todayWidgetTitle = L10n.tr("Localizable", "Today_Widget_Title")
- /// Plural format key: "%#@today_count@"
- internal static func todayCount(_ p1: Int) -> String {
- return L10n.tr("Localizable", "TodayCount", p1)
- }
- /// Unread
- internal static let unread = L10n.tr("Localizable", "Unread")
- /// A sneak peek at your unread articles.
- internal static let unreadWidgetDescription = L10n.tr("Localizable", "Unread_Widget_Description")
- /// There are no unread articles left to read.
- internal static let unreadWidgetNoItems = L10n.tr("Localizable", "Unread_Widget_NoItems")
- /// Unread
- internal static let unreadWidgetNoItemsTitle = L10n.tr("Localizable", "Unread_Widget_NoItemsTitle")
- /// Your Unread Articles
- internal static let unreadWidgetTitle = L10n.tr("Localizable", "Unread_Widget_Title")
- /// Plural format key: "%#@unread_count@"
- internal static func unreadCount(_ p1: Int) -> String {
- return L10n.tr("Localizable", "UnreadCount", p1)
- }
-}
-// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
-// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
-
-// MARK: - Implementation Details
-
-extension L10n {
- private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
- let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table)
- return String(format: format, locale: Locale.current, arguments: args)
- }
-}
-
-// swiftlint:disable convenience_type
-private final class BundleToken {
- static let bundle: Bundle = {
- #if SWIFT_PACKAGE
- return Bundle.module
- #else
- return Bundle(for: BundleToken.self)
- #endif
- }()
-}
-// swiftlint:enable convenience_type
diff --git a/Widget/Resources/en-GB.lproj/Localizable.strings b/Widget/Resources/en-GB.lproj/Localizable.strings
new file mode 100644
index 000000000..222640afe
--- /dev/null
+++ b/Widget/Resources/en-GB.lproj/Localizable.strings
@@ -0,0 +1,51 @@
+/* Starred */
+"label.text.starred" = "Starred";
+
+/* Today */
+"label.text.today" = "Today";
+
+/* Smart Feed Summary Widget */
+"label.text.unread" = "Unread";
+
+/* Your smart feeds, summarized. */
+"widget.description.smart-feed-summary" = "Your smart feeds, summarised.";
+
+/* A sneak peek at your starred articles. */
+"widget.description.starred" = "A sneak peek at your starred articles.";
+
+/* When you mark articles as Starred, they'll appear here. */
+"widget.description.starred-no-items" = "When you mark articles as Starred, they'll appear here.";
+
+/* A sneak peek at recently published unread articles. */
+"widget.description.today" = "A sneak peek at recently published unread articles.";
+
+/* There are no recent unread articles left to read. */
+"widget.description.today-no-items" = "There are no recent unread articles left to read.";
+
+/* A sneak peek at your unread articles. */
+"widget.description.unread" = "A sneak peek at your unread articles.";
+
+/* There are no unread articles left to read. */
+"widget.description.unread-no-items" = "There are no unread articles left to read.";
+
+/* Your Smart Feed Summary */
+"widget.title.smart-feed-summary" = "Your Smart Feed Summary";
+
+/* Your Starred Articles */
+"widget.title.starred" = "Your Starred Articles";
+
+/* Starred Widget */
+"widget.title.starred-no-items" = "Starred";
+
+/* Your Today Articles */
+"widget.title.today" = "Your Today Articles";
+
+/* Today Widget */
+"widget.title.today-no-items" = "Today";
+
+/* Bundle */
+"widget.title.unread" = "Your Unread Articles";
+
+/* Unread Widget */
+"widget.title.unread-no-items" = "Unread";
+
diff --git a/Widget/Resources/Localizable.stringsdict b/Widget/Resources/en-GB.lproj/Localizable.stringsdict
similarity index 66%
rename from Widget/Resources/Localizable.stringsdict
rename to Widget/Resources/en-GB.lproj/Localizable.stringsdict
index 59c3ad26e..a4e956d25 100644
--- a/Widget/Resources/Localizable.stringsdict
+++ b/Widget/Resources/en-GB.lproj/Localizable.stringsdict
@@ -2,25 +2,7 @@
- UnreadCount
-
- NSStringLocalizedFormatKey
- %#@unread_count@
- unread_count
-
- NSStringFormatSpecTypeKey
- NSStringPluralRuleType
- NSStringFormatValueTypeKey
- u
- zero
- No more unread articles
- one
- + 1 more unread article
- other
- + %u more unread articles
-
-
- StarredCount
+ starred.count.%lld
NSStringLocalizedFormatKey
%#@starred_count@
@@ -29,47 +11,49 @@
NSStringFormatSpecTypeKey
NSStringPluralRuleType
NSStringFormatValueTypeKey
- u
- zero
- No more starred articles
+ lld
one
+ 1 more starred article
other
- + %u more starred articles
+ + %lld more starred articles
+ zero
+ No more starred articles
- TodayCount
+ today.count.%lld
NSStringLocalizedFormatKey
%#@today_count@
- today_count
+ today.count
NSStringFormatSpecTypeKey
NSStringPluralRuleType
NSStringFormatValueTypeKey
- u
- zero
- No more recent articles
+ lld
one
+ 1 more recent unread article
other
- + %u more recent unread articles
+ + %lld more recent unread articles
+ zero
+ No more recent articles
- LocalizedCount
+ unread.count.%lld
NSStringLocalizedFormatKey
- %#@localized_count@
- localized_count
+ %#@unread_count@
+ unread_count
NSStringFormatSpecTypeKey
NSStringPluralRuleType
NSStringFormatValueTypeKey
- u
- zero
- 0
+ lld
+ one
+ + 1 more unread article
other
- %u
+ + %lld more unread articles
+ zero
+ No more unread articles
diff --git a/Widget/Resources/en.lproj/Localizable.strings b/Widget/Resources/en.lproj/Localizable.strings
index fed487392..d78aa49d5 100644
--- a/Widget/Resources/en.lproj/Localizable.strings
+++ b/Widget/Resources/en.lproj/Localizable.strings
@@ -7,31 +7,31 @@
*/
/* Bundle */
-"Unread_Widget_Title" = "Your Unread Articles";
-"Unread_Widget_Description" = "A sneak peek at your unread articles.";
+"widget.title.unread" = "Your Unread Articles";
+"widget.description.unread" = "A sneak peek at your unread articles.";
-"Today_Widget_Title" = "Your Today Articles";
-"Today_Widget_Description" = "A sneak peek at recently published unread articles.";
+"widget.title.today" = "Your Today Articles";
+"widget.description.today" = "A sneak peek at recently published unread articles.";
-"Starred_Widget_Title" = "Your Starred Articles";
-"Starred_Widget_Description" = "A sneak peek at your starred articles.";
+"widget.title.starred" = "Your Starred Articles";
+"widget.description.starred" = "A sneak peek at your starred articles.";
-"SmartFeedSummary_Widget_Title" = "Your Smart Feed Summary";
-"SmartFeedSummary_Widget_Description" = "Your smart feeds, summarized.";
+"widget.title.smart-feed-summary" = "Your Smart Feed Summary";
+"widget.description.smart-feed-summary" = "Your smart feeds, summarized.";
/* Unread Widget */
-"Unread_Widget_NoItemsTitle" = "Unread";
-"Unread_Widget_NoItems" = "There are no unread articles left to read.";
+"widget.title.unread-no-items" = "Unread";
+"widget.description.unread-no-items" = "There are no unread articles left to read.";
/* Today Widget */
-"Today_Widget_NoItemsTitle" = "Today";
-"Today_Widget_NoItems" = "There are no recent unread articles left to read.";
+"widget.title.today-no-items" = "Today";
+"widget.description.today-no-items" = "There are no recent unread articles left to read.";
/* Starred Widget */
-"Starred_Widget_NoItemsTitle" = "Starred";
-"Starred_Widget_NoItems" = "When you mark articles as Starred, they'll appear here.";
+"widget.title.starred-no-items" = "Starred";
+"widget.description.starred-no-items" = "When you mark articles as Starred, they'll appear here.";
/* Smart Feed Summary Widget */
-"Unread" = "Unread";
-"Today" = "Today ";
-"Starred" = "Starred";
+"label.text.unread" = "Unread";
+"label.text.today" = "Today";
+"label.text.starred" = "Starred";
diff --git a/Widget/Resources/en.lproj/Localizable.stringsdict b/Widget/Resources/en.lproj/Localizable.stringsdict
new file mode 100644
index 000000000..06dad2aeb
--- /dev/null
+++ b/Widget/Resources/en.lproj/Localizable.stringsdict
@@ -0,0 +1,60 @@
+
+
+
+
+ unread.count.%lld
+
+ NSStringLocalizedFormatKey
+ %#@unread_count@
+ unread_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ lld
+ zero
+ No more unread articles
+ one
+ + 1 more unread article
+ other
+ + %lld more unread articles
+
+
+ starred.count.%lld
+
+ NSStringLocalizedFormatKey
+ %#@starred_count@
+ starred_count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ lld
+ zero
+ No more starred articles
+ one
+ + 1 more starred article
+ other
+ + %lld more starred articles
+
+
+ today.count.%lld
+
+ NSStringLocalizedFormatKey
+ %#@today_count@
+ today.count
+
+ NSStringFormatSpecTypeKey
+ NSStringPluralRuleType
+ NSStringFormatValueTypeKey
+ lld
+ zero
+ No more recent articles
+ one
+ + 1 more recent unread article
+ other
+ + %lld more recent unread articles
+
+
+
+
diff --git a/Widget/Shared Views/ArticleItemView.swift b/Widget/Shared Views/ArticleItemView.swift
index 87c06bea9..f7a11a22d 100644
--- a/Widget/Shared Views/ArticleItemView.swift
+++ b/Widget/Shared Views/ArticleItemView.swift
@@ -11,8 +11,6 @@ import RSWeb
struct ArticleItemView: View {
-
-
var article: LatestArticle
var deepLink: URL
@State private var iconImage: Image?
@@ -30,7 +28,7 @@ struct ArticleItemView: View {
// Title and Feed Name
VStack(alignment: .leading) {
- Text(article.articleTitle ?? "Untitled")
+ Text(verbatim: article.articleTitle ?? "Untitled")
.font(.footnote)
.bold()
.lineLimit(1)
@@ -38,12 +36,12 @@ struct ArticleItemView: View {
.padding(.top, -3)
HStack {
- Text(article.feedTitle)
+ Text(verbatim: article.feedTitle)
.font(.caption)
.lineLimit(1)
.foregroundColor(.secondary)
Spacer()
- Text(pubDate(article.pubDate))
+ Text(verbatim: pubDate(article.pubDate))
.font(.caption)
.lineLimit(1)
.foregroundColor(.secondary)
diff --git a/Widget/Widget Views/SmartFeedSummaryWidget.swift b/Widget/Widget Views/SmartFeedSummaryWidget.swift
index b2cd3da6b..7e1acfa17 100644
--- a/Widget/Widget Views/SmartFeedSummaryWidget.swift
+++ b/Widget/Widget Views/SmartFeedSummaryWidget.swift
@@ -26,7 +26,7 @@ struct SmartFeedSummaryWidgetView: View {
VStack(alignment: .leading, spacing: 2) {
HStack {
todayImage
- Text(L10n.today).bold().font(.body)
+ Text("label.text.today", comment: "Today").bold().font(.body)
Spacer()
Text(formattedCount(entry.widgetData.currentTodayCount)).bold()
@@ -34,14 +34,14 @@ struct SmartFeedSummaryWidgetView: View {
HStack {
unreadImage
- Text(L10n.unread).bold().font(.body)
+ Text("label.text.unread", comment: "Unread").bold().font(.body)
Spacer()
Text(formattedCount(entry.widgetData.currentUnreadCount)).bold()
}
HStack {
starredImage
- Text(L10n.starred).bold().font(.body)
+ Text("label.text.starred", comment: "Starred").bold().font(.body)
Spacer()
Text(formattedCount(entry.widgetData.currentStarredCount)).bold()
}
diff --git a/Widget/Widget Views/StarredWidget.swift b/Widget/Widget Views/StarredWidget.swift
index cd3b3e72d..52b03b7f6 100644
--- a/Widget/Widget Views/StarredWidget.swift
+++ b/Widget/Widget Views/StarredWidget.swift
@@ -61,7 +61,7 @@ struct StarredWidgetView : View {
HStack {
Spacer()
if entry.widgetData.currentStarredCount - maxCount() > 0 {
- Text(L10n.starredCount(entry.widgetData.currentStarredCount - maxCount()))
+ Text(String(format: NSLocalizedString("starred.count.%lld", comment: "Number of starred articles beyond what are currently displayed in the widget."), locale: .current, starredCount()))
.font(.caption2)
.bold()
.foregroundColor(.secondary)
@@ -97,6 +97,10 @@ struct StarredWidgetView : View {
return entry.widgetData.currentStarredCount >= 3 ? (3 - reduceAccessibilityCount) : entry.widgetData.currentStarredCount
}
+ func starredCount() -> Int {
+ entry.widgetData.currentStarredCount - maxCount()
+ }
+
var inboxZero: some View {
VStack(alignment: .center) {
Spacer()
@@ -107,11 +111,11 @@ struct StarredWidgetView : View {
.foregroundColor(.yellow)
- Text(L10n.starredWidgetNoItemsTitle)
+ Text("widget.title.starred-no-items", comment: "Starred")
.font(.headline)
.foregroundColor(.primary)
- Text(L10n.starredWidgetNoItems)
+ Text("widget.description.starred-no-items", comment: "When you mark articles as Starred, they'll appear here.")
.font(.caption)
.foregroundColor(.gray)
Spacer()
diff --git a/Widget/Widget Views/TodayWidget.swift b/Widget/Widget Views/TodayWidget.swift
index 5fa91c089..5eaa24f0f 100644
--- a/Widget/Widget Views/TodayWidget.swift
+++ b/Widget/Widget Views/TodayWidget.swift
@@ -61,10 +61,12 @@ struct TodayWidgetView : View {
HStack {
Spacer()
if entry.widgetData.currentTodayCount - maxCount() > 0 {
- Text(L10n.todayCount(entry.widgetData.currentTodayCount - maxCount()))
+ Text(String(format: NSLocalizedString("today.count.%lld", comment: "Number of today articles beyond what are currently displayed in the widget."), locale: .current, todayCount()))
.font(.caption2)
.bold()
.foregroundColor(.secondary)
+
+
}
}
}
@@ -84,6 +86,10 @@ struct TodayWidgetView : View {
.foregroundColor(.orange)
}
+ func todayCount() -> Int {
+ entry.widgetData.currentTodayCount - maxCount()
+ }
+
func maxCount() -> Int {
var reduceAccessibilityCount: Int = 0
if SizeCategories().isSizeCategoryLarge(category: sizeCategory) {
@@ -106,11 +112,11 @@ struct TodayWidgetView : View {
.foregroundColor(.orange)
- Text(L10n.todayWidgetNoItemsTitle)
+ Text("widget.title.today-no-items", comment: "Today")
.font(.headline)
.foregroundColor(.primary)
- Text(L10n.todayWidgetNoItems)
+ Text("widget.description.today-no-items", comment: "There are no recent unread articles left to read.")
.font(.caption)
.foregroundColor(.gray)
Spacer()
diff --git a/Widget/Widget Views/UnreadWidget.swift b/Widget/Widget Views/UnreadWidget.swift
index 626044b6b..c94859ff2 100644
--- a/Widget/Widget Views/UnreadWidget.swift
+++ b/Widget/Widget Views/UnreadWidget.swift
@@ -61,7 +61,7 @@ struct UnreadWidgetView : View {
HStack {
Spacer()
if entry.widgetData.currentUnreadCount - maxCount() > 0 {
- Text(L10n.unreadCount(entry.widgetData.currentUnreadCount - maxCount()))
+ Text(String(format: NSLocalizedString("unread.count.%lld", comment: "Number of unread articles beyond what are currently displayed in the widget."), locale: .current, unreadCount()))
.font(.caption2)
.bold()
.foregroundColor(.secondary)
@@ -95,6 +95,10 @@ struct UnreadWidgetView : View {
return entry.widgetData.unreadArticles.count >= 3 ? (3 - reduceAccessibilityCount) : entry.widgetData.unreadArticles.count
}
+ func unreadCount() -> Int {
+ entry.widgetData.currentUnreadCount - maxCount()
+ }
+
var inboxZero: some View {
VStack(alignment: .center) {
Spacer()
@@ -104,11 +108,11 @@ struct UnreadWidgetView : View {
.foregroundColor(.accentColor)
.frame(width: 30)
- Text(L10n.unreadWidgetNoItemsTitle)
+ Text("widget.title.unread-no-items", comment: "Unread")
.font(.headline)
.foregroundColor(.primary)
- Text(L10n.unreadWidgetNoItems)
+ Text("widget.description.unread-no-items", comment: "There are no unread articles left to read.")
.font(.caption)
.foregroundColor(.gray)
Spacer()
diff --git a/Widget/WidgetBundle.swift b/Widget/WidgetBundle.swift
index 6a4907ad9..5e6ec0c95 100644
--- a/Widget/WidgetBundle.swift
+++ b/Widget/WidgetBundle.swift
@@ -22,8 +22,8 @@ struct UnreadWidget: Widget {
.background(Color("WidgetBackground"))
})
- .configurationDisplayName(L10n.unreadWidgetTitle)
- .description(L10n.unreadWidgetDescription)
+ .configurationDisplayName(Text("widget.title.unread", comment: "Your Unread Articles"))
+ .description(Text("widget.description.unread", comment: "A sneak peek at your unread articles."))
.supportedFamilies([.systemMedium, .systemLarge])
}
@@ -40,8 +40,8 @@ struct TodayWidget: Widget {
.background(Color("WidgetBackground"))
})
- .configurationDisplayName(L10n.todayWidgetTitle)
- .description(L10n.todayWidgetDescription)
+ .configurationDisplayName(Text("widget.title.today", comment: "Your Today Articles"))
+ .description(Text("widget.description.today", comment: "A sneak peek at recently published unread articles."))
.supportedFamilies([.systemMedium, .systemLarge])
}
@@ -58,8 +58,8 @@ struct StarredWidget: Widget {
.background(Color("WidgetBackground"))
})
- .configurationDisplayName(L10n.starredWidgetTitle)
- .description(L10n.starredWidgetDescription)
+ .configurationDisplayName(Text("widget.title.starred", comment: "Your Starred Articles"))
+ .description(Text("widget.description.starred", comment: "A sneak peek at your starred articles."))
.supportedFamilies([.systemMedium, .systemLarge])
}
@@ -75,8 +75,8 @@ struct SmartFeedSummaryWidget: Widget {
return StaticConfiguration(kind: kind, provider: Provider(), content: { entry in
SmartFeedSummaryWidgetView(entry: entry)
})
- .configurationDisplayName(L10n.smartFeedSummaryWidgetTitle)
- .description(L10n.smartFeedSummaryWidgetDescription)
+ .configurationDisplayName(Text("widget.title.smart-feed-summary", comment: "Your Smart Feed Summary"))
+ .description(Text("widget.description.smart-feed-summary", comment: "Your smart feeds, summarized."))
.supportedFamilies([.accessoryRectangular])
}
}
diff --git a/Widget/en-GB.lproj/InfoPlist.strings b/Widget/en-GB.lproj/InfoPlist.strings
new file mode 100644
index 000000000..c3b56e72a
--- /dev/null
+++ b/Widget/en-GB.lproj/InfoPlist.strings
@@ -0,0 +1,6 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "NetNewsWire Widget";
+
+/* Bundle name */
+"CFBundleName" = "NetNewsWire iOS Widget Extension";
+
diff --git a/iOS/Account/CloudKitAddAccountView.swift b/iOS/Account/CloudKitAddAccountView.swift
index e06cbc591..7de98f92e 100644
--- a/iOS/Account/CloudKitAddAccountView.swift
+++ b/iOS/Account/CloudKitAddAccountView.swift
@@ -25,13 +25,13 @@ struct CloudKitAddAccountView: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
- Button(action: { dismiss() }, label: { Text("Cancel", comment: "Button title") })
+ Button(action: { dismiss() }, label: { Text("button.title.cancel", comment: "Cancel") })
}
}
- .alert(Text("Error", comment: "Alert title: Error"), isPresented: $accountError.1) {
- Button(action: {}, label: { Text("Dismiss", comment: "Button title") })
+ .alert(Text("alert.title.error", comment: "Error"), isPresented: $accountError.1) {
+
} message: {
- Text(accountError.0?.localizedDescription ?? "Unknown Error")
+ Text(verbatim: accountError.0?.localizedDescription ?? "Unknown Error")
}
.dismissOnExternalContextLaunch()
.dismissOnAccountAdd()
@@ -48,7 +48,7 @@ struct CloudKitAddAccountView: View {
} label: {
HStack {
Spacer()
- Text("Use iCloud", comment: "Button title")
+ Text("button.title.use-cloudkit", comment: "Use iCloud")
Spacer()
}
}
@@ -58,9 +58,9 @@ struct CloudKitAddAccountView: View {
VStack(spacing: 4) {
if !AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) {
// The explainer is only shown when a CloudKit account doesn't exist.
- Text("NetNewsWire will use your iCloud account to sync your subscriptions across your Mac and iOS devices.", comment: "iCloud account explanatory text")
+ Text("label.text.cloudkit-explainer", comment: "NetNewsWire will use your iCloud account to sync your subscriptions across your Mac and iOS devices.")
}
- Text("[iCloud Syncing Limitations & Solutions](https://netnewswire.com/help/iCloud)", comment: "Link which opens webpage describing iCloud syncing limitations.")
+ Text("link.markdown.icloud-limitations", comment: "Link which opens webpage describing iCloud syncing limitations.")
}.multilineTextAlignment(.center)
}
diff --git a/iOS/Account/FeedbinAddAccountView.swift b/iOS/Account/FeedbinAddAccountView.swift
index 51db90293..23e4a0580 100644
--- a/iOS/Account/FeedbinAddAccountView.swift
+++ b/iOS/Account/FeedbinAddAccountView.swift
@@ -36,21 +36,16 @@ struct FeedbinAddAccountView: View {
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
- Button(action: { dismiss() }, label: { Text("Cancel", comment: "Button title") })
+ Button(action: { dismiss() }, label: { Text("button.title.cancel", comment: "Cancel") })
.disabled(showProgressIndicator)
}
ToolbarItem(placement: .navigationBarTrailing) {
if showProgressIndicator { ProgressView() }
}
}
- .alert(Text("Error", comment: "Alert title: Error"), isPresented: $accountError.1) {
- Button(role: .cancel) {
- //
- } label: {
- Text("Dismiss", comment: "Button title")
- }
+ .alert(Text("alert.title.error", comment: "Error"), isPresented: $accountError.1) {
} message: {
- Text(accountError.0?.localizedDescription ?? "Error")
+ Text(verbatim: accountError.0?.localizedDescription ?? "Error")
}
.navigationTitle(Text(verbatim: account?.type.localizedAccountName() ?? "Feedbin"))
.navigationBarTitleDisplayMode(.inline)
@@ -62,11 +57,11 @@ struct FeedbinAddAccountView: View {
var accountDetails: some View {
Section {
- TextField("Email", text: $accountEmail, prompt: Text("Email Address", comment: "Textfield for the user to enter their account email address."))
+ TextField("Email", text: $accountEmail, prompt: Text("textfield.placeholder.email-address", comment: "Email Address"))
.autocorrectionDisabled()
.autocapitalization(.none)
.textContentType(.username)
- SecureField("Password", text: $accountPassword, prompt: Text("Password", comment: "Textfield for the user to enter their account password."))
+ SecureField("Password", text: $accountPassword, prompt: Text("textfield.placeholder.password", comment: "Password"))
.textContentType(.password)
}
}
@@ -92,9 +87,9 @@ struct FeedbinAddAccountView: View {
HStack{
Spacer()
if account == nil {
- Text("Add Account", comment: "Button title")
+ Text("button.title.add-account", comment: "Add Account")
} else {
- Text("Update Credentials", comment: "Button title")
+ Text("button.title.update-credentials", comment: "Update Credentials")
}
Spacer()
}
@@ -105,7 +100,7 @@ struct FeedbinAddAccountView: View {
var feedbinAccountExplainer: some View {
if account == nil {
- return Text("Sign in to your Feedbin account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a Feedbin account? [Sign Up Here](https://feedbin.com/signup)", comment: "Explanatory text describing the Feedbin account.")
+ return Text("label.text.feedbin-explainer", comment: "Sign in to your Feedbin account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a Feedbin account? [Sign Up Here](https://feedbin.com/signup)")
.multilineTextAlignment(.center)
}
return Text("").multilineTextAlignment(.center)
diff --git a/iOS/Account/LocalAddAccountView.swift b/iOS/Account/LocalAddAccountView.swift
index 4ca855080..c823296d7 100644
--- a/iOS/Account/LocalAddAccountView.swift
+++ b/iOS/Account/LocalAddAccountView.swift
@@ -24,7 +24,7 @@ struct LocalAddAccountView: View {
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
- Button(action: { dismiss() }, label: { Text("Cancel", comment: "Button title") })
+ Button(action: { dismiss() }, label: { Text("button.title.cancel", comment: "Cancel") })
}
}
.navigationTitle(deviceAccountName())
@@ -37,7 +37,7 @@ struct LocalAddAccountView: View {
var accountNameSection: some View {
TextField("Name",
text: $accountName,
- prompt: Text("Name", comment: "Textfield placeholder for the name of the account."))
+ prompt: Text("textfield.placeholder.name", comment: "Name"))
.autocorrectionDisabled()
.autocapitalization(.none)
}
@@ -49,7 +49,7 @@ struct LocalAddAccountView: View {
} label: {
HStack {
Spacer()
- Text("Add Account", comment: "Button title")
+ Text("button.title.add-account", comment: "Add Account")
Spacer()
}
}
@@ -58,7 +58,7 @@ struct LocalAddAccountView: View {
var accountFooterView: some View {
HStack {
Spacer()
- Text("Local accounts do not sync your feeds across devices.", comment: "Explanatory text describing the local account.")
+ Text("label.text.local-account-explainer", comment: "Local accounts do not sync your feeds across devices")
.multilineTextAlignment(.center)
Spacer()
}
@@ -72,10 +72,7 @@ struct LocalAddAccountView: View {
}
private func deviceAccountName() -> Text {
- if UIDevice.current.userInterfaceIdiom == .pad {
- return Text("On My iPad", comment: "Account name for iPad")
- }
- return Text("On My iPhone", comment: "Account name for iPhone")
+ Text(verbatim: AccountType.onMyMac.localizedAccountName())
}
}
diff --git a/iOS/Account/NewsBlurAddAccountView.swift b/iOS/Account/NewsBlurAddAccountView.swift
index 5a47bad44..db83bcede 100644
--- a/iOS/Account/NewsBlurAddAccountView.swift
+++ b/iOS/Account/NewsBlurAddAccountView.swift
@@ -31,7 +31,7 @@ struct NewsBlurAddAccountView: View, Logging {
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
- Button(action: { dismiss() }, label: { Text("Cancel", comment: "Button title") })
+ Button(action: { dismiss() }, label: { Text("button.title.cancel", comment: "Cancel") })
.disabled(showProgressIndicator)
}
ToolbarItem(placement: .navigationBarTrailing) {
@@ -43,14 +43,9 @@ struct NewsBlurAddAccountView: View, Logging {
.task {
retreiveCredentials()
}
- .alert(Text("Error", comment: "Alert title: Error"), isPresented: $accountError.1) {
- Button(role: .cancel) {
- //
- } label: {
- Text("Dismiss", comment: "Button title")
- }
+ .alert(Text("alert.title.error", comment: "Error"), isPresented: $accountError.1) {
} message: {
- Text(accountError.0?.localizedDescription ?? "")
+ Text(verbatim: accountError.0?.localizedDescription ?? "")
}
.interactiveDismissDisabled(showProgressIndicator)
.dismissOnExternalContextLaunch()
@@ -70,11 +65,11 @@ struct NewsBlurAddAccountView: View, Logging {
var accountDetails: some View {
Section {
- TextField("Email", text: $accountUserName, prompt: Text("Username or Email", comment: "Textfield for the user to enter their account username or email."))
+ TextField("Email", text: $accountUserName, prompt: Text("textfield.placeholder.username-or-email", comment: "Username or Email"))
.autocorrectionDisabled()
.autocapitalization(.none)
.textContentType(.username)
- SecureField("Password", text: $accountPassword, prompt: Text("Password", comment: "Textfield for the user to enter their account password."))
+ SecureField("Password", text: $accountPassword, prompt: Text("textfield.placeholder.password", comment: "Password"))
.textContentType(.password)
}
}
@@ -100,9 +95,9 @@ struct NewsBlurAddAccountView: View, Logging {
HStack{
Spacer()
if account == nil {
- Text("Add Account", comment: "Button title")
+ Text("button.title.add-account", comment: "Add Account")
} else {
- Text("Update Credentials", comment: "Button title")
+ Text("button.title.update-credentials", comment: "Update Credentials")
}
Spacer()
}
@@ -113,7 +108,7 @@ struct NewsBlurAddAccountView: View, Logging {
var newsBlurAccountExplainer: some View {
if account == nil {
- return Text("Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a NewsBlur account? [Sign Up Here](https://newsblur.com)", comment: "Explanatory text describing the NewsBlur account")
+ return Text("label.text.newsblur-explainer", comment: "Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a NewsBlur account? [Sign Up Here](https://newsblur.com)")
.multilineTextAlignment(.center)
}
return Text("").multilineTextAlignment(.center)
diff --git a/iOS/Account/ReaderAPIAddAccountView.swift b/iOS/Account/ReaderAPIAddAccountView.swift
index c0842d14f..0474c40c8 100644
--- a/iOS/Account/ReaderAPIAddAccountView.swift
+++ b/iOS/Account/ReaderAPIAddAccountView.swift
@@ -43,21 +43,17 @@ struct ReaderAPIAddAccountView: View {
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
- Button(action: { dismiss() }, label: { Text("Cancel", comment: "Button title") })
+ Button(action: { dismiss() }, label: { Text("button.title.cancel", comment: "Cancel") })
.disabled(showProgressIndicator)
}
ToolbarItem(placement: .navigationBarTrailing) {
if showProgressIndicator { ProgressView() }
}
}
- .alert(Text("Error", comment: "Alert title: Error"), isPresented: $accountError.1) {
- Button(role: .cancel) {
- //
- } label: {
- Text("Dismiss", comment: "Button title")
- }
+ .alert(Text("alert.title.error", comment: "Error"), isPresented: $accountError.1) {
+
} message: {
- Text(accountError.0?.localizedDescription ?? "")
+ Text(verbatim: accountError.0?.localizedDescription ?? "")
}
.interactiveDismissDisabled(showProgressIndicator)
.dismissOnExternalContextLaunch()
@@ -69,15 +65,15 @@ struct ReaderAPIAddAccountView: View {
if accountType == nil { return Text("").multilineTextAlignment(.center) }
switch accountType! {
case .bazQux:
- return Text("Sign in to your BazQux account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a BazQux account? [Sign Up Here](https://bazqux.com)", comment: "Explanatory text describing the BazQux account").multilineTextAlignment(.center)
+ return Text("label.text.bazqux-explainer", comment: "Sign in to your BazQux account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a BazQux account? [Sign Up Here](https://bazqux.com)").multilineTextAlignment(.center)
case .inoreader:
- return Text("Sign in to your InoReader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an InoReader account? [Sign Up Here](https://www.inoreader.com)", comment: "Explanatory text describing the Inoreader account").multilineTextAlignment(.center)
+ return Text("label.text.inoreader-explainer", comment: "Sign in to your InoReader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an InoReader account? [Sign Up Here](https://www.inoreader.com)").multilineTextAlignment(.center)
case .theOldReader:
- return Text("Sign in to your The Old Reader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a The Old Reader account? [Sign Up Here](https://theoldreader.com)", comment: "Explanatory text describing The Old Reader account").multilineTextAlignment(.center)
+ return Text("label.text.theoldreader-explainer", comment: "Sign in to your The Old Reader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a The Old Reader account? [Sign Up Here](https://theoldreader.com)").multilineTextAlignment(.center)
case .freshRSS:
- return Text("Sign in to your FreshRSS instance and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an FreshRSS instance? [Sign Up Here](https://freshrss.org)", comment: "Explanatory text describing the FreshRSS account").multilineTextAlignment(.center)
+ return Text("label.text.freshrss-explainer", comment: "Sign in to your FreshRSS instance and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an FreshRSS instance? [Sign Up Here](https://freshrss.org)").multilineTextAlignment(.center)
default:
- return Text("").multilineTextAlignment(.center)
+ return Text(verbatim: "").multilineTextAlignment(.center)
}
}
@@ -85,14 +81,14 @@ struct ReaderAPIAddAccountView: View {
var accountDetails: some View {
Section {
- TextField("Username", text: $accountUserName)
+ TextField("Username", text: $accountUserName, prompt: Text("textfield.placeholder.username", comment: "Username"))
.autocorrectionDisabled()
.autocapitalization(.none)
.textContentType(.username)
- SecureField("Password", text: $accountSecret)
+ SecureField("Password", text: $accountSecret, prompt: Text("textfield.placeholder.password", comment: "Password"))
.textContentType(.password)
if accountType == .freshRSS && accountCredentials == nil {
- TextField("FreshRSS URL", text: $accountAPIUrl, prompt: Text("fresh.rss.net/api/greader.php"))
+ TextField("FreshRSS URL", text: $accountAPIUrl, prompt: Text(verbatim: "fresh.rss.net/api/greader.php"))
.autocorrectionDisabled()
.autocapitalization(.none)
}
@@ -120,9 +116,9 @@ struct ReaderAPIAddAccountView: View {
HStack {
Spacer()
if accountCredentials == nil {
- Text("Add Account", comment: "Button title")
+ Text("button.title.add-account", comment: "Add Account")
} else {
- Text("Update Credentials", comment: "Button title")
+ Text("button.title.update-credentials", comment: "Update Credentials")
}
Spacer()
}
diff --git a/iOS/Add/AddFeedViewController.swift b/iOS/Add/AddFeedViewController.swift
index 23f036519..b1cc09d70 100644
--- a/iOS/Add/AddFeedViewController.swift
+++ b/iOS/Add/AddFeedViewController.swift
@@ -41,7 +41,7 @@ class AddFeedViewController: UITableViewController {
switch addFeedType {
case .reddit:
- navigationItem.title = NSLocalizedString("Add Reddit Feed", comment: "Add Reddit Feed")
+ navigationItem.title = NSLocalizedString("navigation.title.add-reddit-feed", comment: "Add Reddit Feed")
navigationItem.leftBarButtonItem = nil
default:
break
diff --git a/iOS/Add/Reddit/RedditEnterDetailTableViewController.swift b/iOS/Add/Reddit/RedditEnterDetailTableViewController.swift
index be1e75073..d05b2eade 100644
--- a/iOS/Add/Reddit/RedditEnterDetailTableViewController.swift
+++ b/iOS/Add/Reddit/RedditEnterDetailTableViewController.swift
@@ -21,7 +21,7 @@ class RedditEnterDetailTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
- nextBarButtonItem.title = NSLocalizedString("Next", comment: "Next")
+ nextBarButtonItem.title = NSLocalizedString("button.title.next", comment: "Next")
nextBarButtonItem.style = .plain
nextBarButtonItem.target = self
nextBarButtonItem.action = #selector(nextScene)
diff --git a/iOS/AppDefaults.swift b/iOS/AppDefaults.swift
index 418d16877..456b584ec 100644
--- a/iOS/AppDefaults.swift
+++ b/iOS/AppDefaults.swift
@@ -18,11 +18,11 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
var description: String {
switch self {
case .automatic:
- return NSLocalizedString("Automatic", comment: "Automatic")
+ return NSLocalizedString("appdefaults.colorpalette.automatic", comment: "Automatic")
case .light:
- return NSLocalizedString("Light", comment: "Light")
+ return NSLocalizedString("appdefaults.colorpalette.light", comment: "Light")
case .dark:
- return NSLocalizedString("Dark", comment: "Dark")
+ return NSLocalizedString("appdefaults.colorpalette.dark", comment: "Dark")
}
}
diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift
index 7592430f0..4cca26407 100644
--- a/iOS/AppDelegate.swift
+++ b/iOS/AppDelegate.swift
@@ -118,6 +118,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
#if DEBUG
syncTimer!.update()
+ if let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.path {
+ print("Documents Directory: \(documentsPath)")
+ }
#endif
return true
@@ -253,15 +256,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
private func initializeHomeScreenQuickActions() {
- let unreadTitle = NSLocalizedString("First Unread", comment: "First Unread")
+ let unreadTitle = NSLocalizedString("homescreen.action.first-unread", comment: "First Unread")
let unreadIcon = UIApplicationShortcutIcon(systemImageName: "chevron.down.circle")
let unreadItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.FirstUnread", localizedTitle: unreadTitle, localizedSubtitle: nil, icon: unreadIcon, userInfo: nil)
- let searchTitle = NSLocalizedString("Search", comment: "Search")
+ let searchTitle = NSLocalizedString("homescreen.action.search", comment: "Search")
let searchIcon = UIApplicationShortcutIcon(systemImageName: "magnifyingglass")
let searchItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowSearch", localizedTitle: searchTitle, localizedSubtitle: nil, icon: searchIcon, userInfo: nil)
- let addTitle = NSLocalizedString("Add Feed", comment: "Add Feed")
+ let addTitle = NSLocalizedString("homescreen.action.add-feed", comment: "Add Feed")
let addIcon = UIApplicationShortcutIcon(systemImageName: "plus")
let addItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowAdd", localizedTitle: addTitle, localizedSubtitle: nil, icon: addIcon, userInfo: nil)
diff --git a/iOS/Article/ArticleExtractorButton.swift b/iOS/Article/ArticleExtractorButton.swift
index 517c7ec00..b2c512385 100644
--- a/iOS/Article/ArticleExtractorButton.swift
+++ b/iOS/Article/ArticleExtractorButton.swift
@@ -44,13 +44,13 @@ enum ArticleExtractorButtonState {
get {
switch buttonState {
case .error:
- return NSLocalizedString("Error - Reader View", comment: "Error - Reader View")
+ return NSLocalizedString("button.accessibility.title.error-readerview", comment: "Error - Reader View")
case .animated:
- return NSLocalizedString("Processing - Reader View", comment: "Processing - Reader View")
+ return NSLocalizedString("button.accessibility.title.processing-readerview", comment: "Processing - Reader View")
case .on:
- return NSLocalizedString("Selected - Reader View", comment: "Selected - Reader View")
+ return NSLocalizedString("button.accessibility.title.selected-readerview", comment: "Selected - Reader View")
case .off:
- return NSLocalizedString("Reader View", comment: "Reader View")
+ return NSLocalizedString("button.accessibility.title.readerview", comment: "Reader View")
}
}
set {
diff --git a/iOS/Article/ArticleSearchBar.swift b/iOS/Article/ArticleSearchBar.swift
index c69cfdd4c..f39c7f1ab 100644
--- a/iOS/Article/ArticleSearchBar.swift
+++ b/iOS/Article/ArticleSearchBar.swift
@@ -60,10 +60,10 @@ import UIKit
private func updateUI() {
if resultsCount > 0 {
- let format = NSLocalizedString("%d of %d", comment: "Results selection and count")
+ let format = NSLocalizedString("label.search-results.%i.%i", comment: "In English, this shows the current search result selection out of the total number of search results. Example: 3 of 5. The variables are ordered as current selection, total search results count.")
resultsLabel.text = String.localizedStringWithFormat(format, selectedResult, resultsCount)
} else {
- resultsLabel.text = NSLocalizedString("No results", comment: "No results")
+ resultsLabel.text = NSLocalizedString("label.text.no-results", comment: "No results")
}
nextButton.isEnabled = selectedResult < resultsCount
@@ -101,7 +101,7 @@ private extension ArticleSearchBar {
addSubview(background)
let doneButton = UIButton()
- doneButton.setTitle(NSLocalizedString("Done", comment: "Done"), for: .normal)
+ doneButton.setTitle(NSLocalizedString("button.title.done", comment: "Done"), for: .normal)
doneButton.setTitleColor(UIColor.label, for: .normal)
doneButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
doneButton.isAccessibilityElement = true
@@ -129,14 +129,14 @@ private extension ArticleSearchBar {
prevButton = UIButton(type: .system)
prevButton.setImage(UIImage(systemName: "chevron.up"), for: .normal)
- prevButton.accessibilityLabel = "Previous Result"
+ prevButton.accessibilityLabel = NSLocalizedString("button.title.previous-result", comment: "Previous Result")
prevButton.isAccessibilityElement = true
prevButton.addTarget(self, action: #selector(previousPressed), for: .touchUpInside)
addArrangedSubview(prevButton)
nextButton = UIButton(type: .system)
nextButton.setImage(UIImage(systemName: "chevron.down"), for: .normal)
- nextButton.accessibilityLabel = "Next Result"
+ nextButton.accessibilityLabel = NSLocalizedString("button.title.next-result", comment: "Next Result")
nextButton.isAccessibilityElement = true
nextButton.addTarget(self, action: #selector(nextPressed), for: .touchUpInside)
addArrangedSubview(nextButton)
diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift
index 7677c14e7..ac5aae104 100644
--- a/iOS/Article/ArticleViewController.swift
+++ b/iOS/Article/ArticleViewController.swift
@@ -220,19 +220,19 @@ class ArticleViewController: UIViewController, MainControllerIdentifiable, Loggi
if article.status.read {
readBarButtonItem.image = AppAssets.circleOpenImage
readBarButtonItem.isEnabled = article.isAvailableToMarkUnread
- readBarButtonItem.accLabelText = NSLocalizedString("Mark Article Unread", comment: "Mark Article Unread")
+ readBarButtonItem.accLabelText = NSLocalizedString("button.title.mark-article-unread", comment: "Mark Article Unread")
} else {
readBarButtonItem.image = AppAssets.circleClosedImage
readBarButtonItem.isEnabled = true
- readBarButtonItem.accLabelText = NSLocalizedString("Selected - Mark Article Unread", comment: "Selected - Mark Article Unread")
+ readBarButtonItem.accLabelText = NSLocalizedString("button.title.selected-mark-article-unread", comment: "Selected - Mark Article Unread")
}
if article.status.starred {
starBarButtonItem.image = AppAssets.starClosedImage
- starBarButtonItem.accLabelText = NSLocalizedString("Selected - Star Article", comment: "Selected - Star Article")
+ starBarButtonItem.accLabelText = NSLocalizedString("button.title.selected-star-article", comment: "Selected - Star Article")
} else {
starBarButtonItem.image = AppAssets.starOpenImage
- starBarButtonItem.accLabelText = NSLocalizedString("Star Article", comment: "Star Article")
+ starBarButtonItem.accLabelText = NSLocalizedString("button.title.star-article", comment: "Star Article")
}
configureAppearanceMenu()
@@ -291,7 +291,7 @@ class ArticleViewController: UIViewController, MainControllerIdentifiable, Loggi
if let currentWebViewController = currentWebViewController {
if currentWebViewController.isFullScreenAvailable {
- let fullScreenAction = UIAction(title: NSLocalizedString("Full Screen", comment: "Full Screen"),
+ let fullScreenAction = UIAction(title: NSLocalizedString("button.title.full-screen", comment: "Full Screen"),
image: UIImage(systemName: "arrow.up.backward.and.arrow.down.forward"),
identifier: nil,
discoverabilityTitle: nil,
@@ -299,10 +299,10 @@ class ArticleViewController: UIViewController, MainControllerIdentifiable, Loggi
state: .off) { [weak self] _ in
self?.currentWebViewController?.hideBars()
if AppDefaults.shared.hasUsedFullScreenPreviously == false {
- let alert = UIAlertController(title: NSLocalizedString("Exit Full Screen", comment: "Full Screen"),
- message: NSLocalizedString("To exit Full Screen mode tap the top of the screen.\n\nYou'll only see this message once.", comment: "Full screen explainer."),
+ let alert = UIAlertController(title: NSLocalizedString("alert.title.exit-full-screen", comment: "Full Screen"),
+ message: NSLocalizedString("alert.message.exit-full-screen", comment: "To exit Full Screen mode tap the top of the screen.\n\nYou'll only see this message once."),
preferredStyle: .alert)
- alert.addAction(UIAlertAction(title: NSLocalizedString("Dismiss", comment: "Dismiss"), style: .default, handler: { _ in
+ alert.addAction(UIAlertAction(title: NSLocalizedString("button.title.dismiss", comment: "Dismiss"), style: .default, handler: { _ in
AppDefaults.shared.hasUsedFullScreenPreviously = true
}))
self?.present(alert, animated: true, completion: nil)
@@ -313,13 +313,13 @@ class ArticleViewController: UIViewController, MainControllerIdentifiable, Loggi
}
appearanceBarButtonItem.image = AppAssets.articleAppearanceImage
- appearanceBarButtonItem.menu = UIMenu(title: NSLocalizedString("Article Appearance", comment: "Appearance"), children: appearanceChildren)
+ appearanceBarButtonItem.menu = UIMenu(title: NSLocalizedString("menu.title.articleappearance", comment: "Article Appearance menu title"), children: appearanceChildren)
}
private func configureArticleExtractorMenu() {
if let feed = article?.webFeed {
let extractorOn = feed.isArticleExtractorAlwaysOn ?? false
- let readerAction = UIAction(title: NSLocalizedString("Always Use Reader View", comment: "Always Use Reader View"),
+ let readerAction = UIAction(title: NSLocalizedString("button.title.always-use-reader-view", comment: "Button title: Always Use Reader View"),
image: AppAssets.articleExtractorOffSF,
identifier: nil,
discoverabilityTitle: nil,
diff --git a/iOS/Article/FindInArticleActivity.swift b/iOS/Article/FindInArticleActivity.swift
index 334a142fb..2ab0501a4 100644
--- a/iOS/Article/FindInArticleActivity.swift
+++ b/iOS/Article/FindInArticleActivity.swift
@@ -10,7 +10,7 @@ import UIKit
class FindInArticleActivity: UIActivity {
override var activityTitle: String? {
- NSLocalizedString("Find in Article", comment: "Find in Article")
+ NSLocalizedString("activity.title.find-in-article.titlecase", comment: "Find in Article")
}
override var activityType: UIActivity.ActivityType? {
diff --git a/iOS/Article/ImageViewController.swift b/iOS/Article/ImageViewController.swift
index fd1f17d79..e1b9f0616 100644
--- a/iOS/Article/ImageViewController.swift
+++ b/iOS/Article/ImageViewController.swift
@@ -39,8 +39,8 @@ class ImageViewController: UIViewController {
super.viewDidLoad()
closeButton.imageView?.contentMode = .scaleAspectFit
- closeButton.accessibilityLabel = NSLocalizedString("Close", comment: "Close")
- shareButton.accessibilityLabel = NSLocalizedString("Share", comment: "Share")
+ closeButton.accessibilityLabel = NSLocalizedString("button.title.close", comment: "Close")
+ shareButton.accessibilityLabel = NSLocalizedString("button.title.share", comment: "Share")
imageScrollView.setup()
imageScrollView.imageScrollViewDelegate = self
diff --git a/iOS/Article/OpenInSafariActivity.swift b/iOS/Article/OpenInSafariActivity.swift
index 05738081a..7b3dc4933 100644
--- a/iOS/Article/OpenInSafariActivity.swift
+++ b/iOS/Article/OpenInSafariActivity.swift
@@ -13,7 +13,7 @@ class OpenInBrowserActivity: UIActivity {
private var activityItems: [Any]?
override var activityTitle: String? {
- return NSLocalizedString("Open in Browser", comment: "Open in Browser")
+ return NSLocalizedString("activity.title.open-in-browser", comment: "Open in Browser")
}
override var activityImage: UIImage? {
diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift
index 65f10b7d0..73bc0cd0f 100644
--- a/iOS/Article/WebViewController.swift
+++ b/iOS/Article/WebViewController.swift
@@ -382,8 +382,8 @@ extension WebViewController: WKNavigationDelegate {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [.universalLinksOnly : false], completionHandler: nil)
} else {
- let alert = UIAlertController(title: NSLocalizedString("Error", comment: "Error"), message: NSLocalizedString("This device cannot send emails.", comment: "This device cannot send emails."), preferredStyle: .alert)
- alert.addAction(.init(title: NSLocalizedString("Dismiss", comment: "Dismiss"), style: .cancel, handler: nil))
+ let alert = UIAlertController(title: NSLocalizedString("alert.title.error", comment: "Alert Error"), message: NSLocalizedString("alert.message.device-cannot-send-email", comment: "This device cannot send emails."), preferredStyle: .alert)
+ alert.addAction(.init(title: NSLocalizedString("button.title.dismiss", comment: "Dismiss"), style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
} else if components?.scheme == "tel" {
@@ -727,7 +727,7 @@ private extension WebViewController {
func prevArticleAction() -> UIAction? {
guard coordinator.isPrevArticleAvailable else { return nil }
- let title = NSLocalizedString("Previous Article", comment: "Previous Article")
+ let title = NSLocalizedString("button.title.previous-article", comment: "Previous Article")
return UIAction(title: title, image: AppAssets.prevArticleImage) { [weak self] action in
self?.coordinator.selectPrevArticle()
}
@@ -735,7 +735,7 @@ private extension WebViewController {
func nextArticleAction() -> UIAction? {
guard coordinator.isNextArticleAvailable else { return nil }
- let title = NSLocalizedString("Next Article", comment: "Next Article")
+ let title = NSLocalizedString("button.title.next-article", comment: "Next Article")
return UIAction(title: title, image: AppAssets.nextArticleImage) { [weak self] action in
self?.coordinator.selectNextArticle()
}
@@ -744,7 +744,7 @@ private extension WebViewController {
func toggleReadAction() -> UIAction? {
guard let article = article, !article.status.read || article.isAvailableToMarkUnread else { return nil }
- let title = article.status.read ? NSLocalizedString("Mark as Unread", comment: "Mark as Unread") : NSLocalizedString("Mark as Read", comment: "Mark as Read")
+ let title = article.status.read ? NSLocalizedString("button.title.mark-as-unread.titlecase", comment: "Mark as Unread") : NSLocalizedString("button.title.mark-as-read.titlecase", comment: "Mark as Read")
let readImage = article.status.read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage
return UIAction(title: title, image: readImage) { [weak self] action in
self?.coordinator.toggleReadForCurrentArticle()
@@ -753,7 +753,7 @@ private extension WebViewController {
func toggleStarredAction() -> UIAction {
let starred = article?.status.starred ?? false
- let title = starred ? NSLocalizedString("Mark as Unstarred", comment: "Mark as Unstarred") : NSLocalizedString("Mark as Starred", comment: "Mark as Starred")
+ let title = starred ? NSLocalizedString("button.title.mark-as-unstarred.titlecase", comment: "Mark as Unstarred") : NSLocalizedString("button.title.mark-as-starred.titlecase", comment: "Mark as Starred")
let starredImage = starred ? AppAssets.starOpenImage : AppAssets.starClosedImage
return UIAction(title: title, image: starredImage) { [weak self] action in
self?.coordinator.toggleStarredForCurrentArticle()
@@ -762,7 +762,7 @@ private extension WebViewController {
func nextUnreadArticleAction() -> UIAction? {
guard coordinator.isAnyUnreadAvailable else { return nil }
- let title = NSLocalizedString("Next Unread Article", comment: "Next Unread Article")
+ let title = NSLocalizedString("button.title.next-unread-article", comment: "Next Unread Article")
return UIAction(title: title, image: AppAssets.nextUnreadArticleImage) { [weak self] action in
self?.coordinator.selectNextUnread()
}
@@ -770,7 +770,7 @@ private extension WebViewController {
func toggleArticleExtractorAction() -> UIAction {
let extracted = articleExtractorButtonState == .on
- let title = extracted ? NSLocalizedString("Show Feed Article", comment: "Show Feed Article") : NSLocalizedString("Show Reader View", comment: "Show Reader View")
+ let title = extracted ? NSLocalizedString("button.title.show-feed-article", comment: "Show Feed Article") : NSLocalizedString("button.title.show-reader-view", comment: "Show Reader View")
let extractorImage = extracted ? AppAssets.articleExtractorOffSF : AppAssets.articleExtractorOnSF
return UIAction(title: title, image: extractorImage) { [weak self] action in
self?.toggleArticleExtractor()
@@ -778,7 +778,7 @@ private extension WebViewController {
}
func shareAction() -> UIAction {
- let title = NSLocalizedString("Share", comment: "Share")
+ let title = NSLocalizedString("button.title.share", comment: "Share")
return UIAction(title: title, image: AppAssets.shareImage) { [weak self] action in
self?.showActivityDialog()
}
diff --git a/iOS/Base.lproj/LaunchScreenPad.storyboard b/iOS/Base.lproj/LaunchScreenPad.storyboard
index 06c951431..c7cbea4ec 100644
--- a/iOS/Base.lproj/LaunchScreenPad.storyboard
+++ b/iOS/Base.lproj/LaunchScreenPad.storyboard
@@ -1,9 +1,11 @@
-
+
-
+
+
+
@@ -14,8 +16,8 @@
-
+
@@ -33,7 +35,7 @@
-
+
@@ -41,7 +43,7 @@
-
+
@@ -49,7 +51,7 @@
-
+
@@ -65,12 +67,19 @@
-
+
+
+
+
+
+
+
+
@@ -83,7 +92,7 @@
-
+
@@ -113,7 +122,10 @@
-
-
+
+
+
+
+
diff --git a/iOS/Base.lproj/LaunchScreenPhone.storyboard b/iOS/Base.lproj/LaunchScreenPhone.storyboard
index 61a86ee99..e77de49db 100644
--- a/iOS/Base.lproj/LaunchScreenPhone.storyboard
+++ b/iOS/Base.lproj/LaunchScreenPhone.storyboard
@@ -1,9 +1,11 @@
-
+
-
+
+
+
@@ -14,8 +16,8 @@
-
+
@@ -33,7 +35,7 @@
-
+
@@ -41,7 +43,7 @@
-
+
@@ -49,7 +51,7 @@
-
+
@@ -65,12 +67,19 @@
-
+
+
+
+
+
+
+
+
@@ -83,7 +92,7 @@
-
+
@@ -113,7 +122,10 @@
-
-
+
+
+
+
+
diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard
index d807aea09..1e5b5040b 100644
--- a/iOS/Base.lproj/Main.storyboard
+++ b/iOS/Base.lproj/Main.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -19,7 +19,7 @@
-
+
@@ -33,6 +33,9 @@
+
+
+
@@ -42,6 +45,9 @@
+
+
+
@@ -51,6 +57,9 @@
+
+
+
@@ -62,6 +71,9 @@
+
+
+
@@ -74,6 +86,9 @@
+
+
+
@@ -83,6 +98,9 @@
+
+
+
@@ -92,6 +110,9 @@
+
+
+
@@ -141,6 +162,9 @@
+
+
+
@@ -151,7 +175,11 @@
-
+
+
+
+
+
@@ -203,6 +231,9 @@
+
+
+
@@ -213,13 +244,19 @@
+
+
+
-
+
+
+
+
@@ -227,9 +264,15 @@
+
+
+
+
+
+
@@ -341,24 +384,36 @@
+
+
+
diff --git a/iOS/Inspector/AccountInspectorView.swift b/iOS/Inspector/AccountInspectorView.swift
index feeb2a008..c67450946 100644
--- a/iOS/Inspector/AccountInspectorView.swift
+++ b/iOS/Inspector/AccountInspectorView.swift
@@ -72,14 +72,14 @@ struct AccountInspectorView: View {
TextField(text: Binding(
get: { account.name ?? account.defaultName },
set: { account.name = $0 }),
- prompt: Text(account.defaultName)) {
- Text("Name", comment: "Textfield for the user to enter account name.")
+ prompt: Text(verbatim: account.defaultName)) {
+ Text("textfield.placeholder.name", comment: "Name")
}
Toggle(isOn: Binding(get: {
account.isActive
}, set: { account.isActive = $0 })) {
- Text("Active", comment: "Toggle denoting if the account is active.")
+ Text("toggle.account.active", comment: "Active")
}
}
}
@@ -91,7 +91,7 @@ struct AccountInspectorView: View {
} label: {
HStack {
Spacer()
- Text("Credentials", comment: "Button title")
+ Text("button.title.credentials", comment: "Credentials")
Spacer()
}
}
@@ -105,26 +105,26 @@ struct AccountInspectorView: View {
} label: {
HStack {
Spacer()
- Text("Remove Account", comment: "Button title")
+ Text("button.title.remove-account", comment: "Remove Account")
Spacer()
}
}
- .alert(Text("Are you sure you want to remove “\(account.nameForDisplay)”?", comment: "Alert title: confirm account removal"), isPresented: $showRemoveAccountAlert) {
+ .alert(Text("alert.title.remove-account.\(account.nameForDisplay)", comment: "Are you sure you want to remove “%@“?"), isPresented: $showRemoveAccountAlert) {
Button(role: .destructive) {
AccountManager.shared.deleteAccount(account)
dismiss()
} label: {
- Text("Remove Account", comment: "Button title")
+ Text("button.title.remove-account", comment: "Remove Account")
}
Button(role: .cancel) {
//
} label: {
- Text("Cancel", comment: "Button title")
+ Text("button.title.cancel", comment: "Cancel")
}
} message: {
- Text("This action cannot be undone.", comment: "Alert message: remove account confirmation")
+ Text("alert.message.cannot-undo-action", comment: "You can't undo this action.")
}
}
}
@@ -132,7 +132,7 @@ struct AccountInspectorView: View {
var cloudKitLimitations: some View {
HStack {
Spacer()
- Text("[iCloud Syncing Limitations & Solutions](https://netnewswire.com/help/iCloud)", comment: "Link to the NetNewsWire iCloud syncing limitations and soltutions website.")
+ Text("link.markdown.icloud-limitations", comment: "Link to the NetNewsWire iCloud syncing limitations and soltutions website.")
Spacer()
}
}
diff --git a/iOS/Inspector/ExtensionInspectorView.swift b/iOS/Inspector/ExtensionInspectorView.swift
index b489bf8ad..15e85ce3c 100644
--- a/iOS/Inspector/ExtensionInspectorView.swift
+++ b/iOS/Inspector/ExtensionInspectorView.swift
@@ -26,24 +26,24 @@ struct ExtensionInspectorView: View {
Button(role: .destructive) {
showDeactivateConfirmation = true
} label: {
- Text("Deactivate Extension", comment: "Button title")
+ Text("button.title.deactivate-extension", comment: "Deactivate Extension")
}
- .alert(Text("Are you sure you want to deactivate “\(extensionPoint?.title ?? "")?", comment: "Alert title: confirm deactivate extension") , isPresented: $showDeactivateConfirmation) {
+ .alert(Text("alert.title.deactivate-extension.\(extensionPoint?.title ?? "")", comment: "Are you sure you want to deactivate “%@“?"), isPresented: $showDeactivateConfirmation) {
Button(role: .destructive) {
ExtensionPointManager.shared.deactivateExtensionPoint(extensionPoint!.extensionPointID)
dismiss()
} label: {
- Text("Deactivate Extension", comment: "Button title")
+ Text("button.title.deactivate-extension", comment: "Deactivate Extension")
}
Button(role: .cancel) {
//
} label: {
- Text("Cancel", comment: "Button title")
+ Text("button.title.cancel", comment: "Cancel")
}
} message: {
- Text("This action cannot be undone.", comment: "Alert message: remove account confirmation")
+ Text("alert.message.cannot-undo-action", comment: "You can't undo this action.")
}
Spacer()
}
diff --git a/iOS/Inspector/WebFeedInspectorView.swift b/iOS/Inspector/WebFeedInspectorView.swift
index ef7a24653..1cf127e46 100644
--- a/iOS/Inspector/WebFeedInspectorView.swift
+++ b/iOS/Inspector/WebFeedInspectorView.swift
@@ -29,19 +29,19 @@ struct WebFeedInspectorView: View {
prompt: nil)
Toggle(isOn: Binding(get: { webFeed.isNotifyAboutNewArticles ?? false }, set: { webFeed.isNotifyAboutNewArticles = $0 })) {
- Text("Notify About New Articles", comment: "Toggle denoting whether the user has enabled new article notifications for this feed.")
+ Text("toggle.title.notify-about-new-articles", comment: "New Article Notifications")
}
if webFeed.isFeedProvider == false {
Toggle(isOn: Binding(
get: { webFeed.isArticleExtractorAlwaysOn ?? false },
set: { webFeed.isArticleExtractorAlwaysOn = $0 })) {
- Text("Always Show Reader View", comment: "Toggle denoting whether the user has enabled Reader view for this feed.")
+ Text("toggle.title.always-show-reader-view", comment: "Always Show Reader View")
}
}
}
- Section(header: Text("Home Page", comment: "Home Page section header in the Feed inspector.")) {
+ Section(header: Text("label.text.home-page", comment: "Home Page")) {
HStack {
Text(webFeed.homePageURL?.decodedURLString ?? "")
Spacer()
@@ -54,7 +54,7 @@ struct WebFeedInspectorView: View {
}
}
- Section(header: Text("Feed URL", comment: "Feed URL section header in the Feed inspector.")) {
+ Section(header: Text("label.text.feed-url", comment: "Feed URL")) {
Text(webFeed.url.description)
}
}
diff --git a/iOS/Intents/AddWebFeedIntentHandler.swift b/iOS/Intents/AddWebFeedIntentHandler.swift
index 94a61bc8f..c32c2ab10 100644
--- a/iOS/Intents/AddWebFeedIntentHandler.swift
+++ b/iOS/Intents/AddWebFeedIntentHandler.swift
@@ -15,7 +15,7 @@ public enum AddWebFeedIntentHandlerError: LocalizedError {
public var errorDescription: String? {
switch self {
case .communicationFailure:
- return NSLocalizedString("Unable to communicate with NetNewsWire.", comment: "Communication failure")
+ return NSLocalizedString("errordescription.localized.communication-failure", comment: "Unable to communicate with NetNewsWire.")
}
}
diff --git a/iOS/Intents/en-GB.lproj/Intents.strings b/iOS/Intents/en-GB.lproj/Intents.strings
new file mode 100644
index 000000000..9840bea62
--- /dev/null
+++ b/iOS/Intents/en-GB.lproj/Intents.strings
@@ -0,0 +1,57 @@
+/* (No Comment) */
+"4xjRes" = "You must supply a URL.";
+
+/* (No Comment) */
+"5CYbGL" = "There are ${count} options matching ‘${folderName}’.";
+
+/* (No Comment) */
+"Ac5RHN" = "Title";
+
+/* (No Comment) */
+"BCHr23" = "URL";
+
+/* (No Comment) */
+"CSrgUY" = "Account Name";
+
+/* (No Comment) */
+"dkSFD2" = "Add ${url} to ${folderName} in ${accountName}";
+
+/* (No Comment) */
+"ef5kBt" = "A valid Folder Name is required.";
+
+/* (No Comment) */
+"ExjqcE" = "Unable to communicate with NetNewsWire.";
+
+/* (No Comment) */
+"HHiZUh" = "Just to confirm, you wanted ‘${accountName}’?";
+
+/* (No Comment) */
+"IbqUVS" = "There are ${count} options matching ‘${accountName}’.";
+
+/* (No Comment) */
+"IGNcSh" = "What is the ${title}of the feed?";
+
+/* (No Comment) */
+"IuAbef" = "Add a web feed";
+
+/* (No Comment) */
+"JGkCuS" = "A valid Account Name is required.";
+
+/* (No Comment) */
+"jLLidQ" = "What is the ${url} you would like add?";
+
+/* (No Comment) */
+"k5GTo0" = "Just to confirm, you wanted ‘${folderName}’?";
+
+/* (No Comment) */
+"kaKsEY" = "Add ${url} to ${accountName}";
+
+/* (No Comment) */
+"oV681v" = "Add Web Feed";
+
+/* (No Comment) */
+"uSfloN" = "Unable to communicate with NetNewsWire.";
+
+/* (No Comment) */
+"zXhMPF" = "Folder Name";
+
diff --git a/iOS/Intents/en.lproj/Intents.strings b/iOS/Intents/en.lproj/Intents.strings
index 187512780..2fd5772f3 100644
--- a/iOS/Intents/en.lproj/Intents.strings
+++ b/iOS/Intents/en.lproj/Intents.strings
@@ -1,30 +1,15 @@
-"4xjRes" = "You must supply a URL.";
-
-"8Dh9Yy" = "No feed was found at the specified URL.";
-
-"BCHr23" = "URL";
-
-"CSrgUY" = "Account Name";
-
-"HHiZUh" = "Just to confirm, you wanted ‘${accountName}’?";
-
-"IbqUVS" = "There are ${count} options matching ‘${accountName}’.";
-
-"IuAbef" = "Add a feed";
-
-"JGkCuS" = "An account name is required.";
-
-"UGGPkp" = "You are already subscribed to this feed in this account.";
-
+/* (No Comment) */
"dkSFD2" = "Add${url}to ${accountName}";
-"drQfaI" = "No feed was found at the specified URL.";
+/* (No Comment) */
+"IuAbef" = "Add a feed";
-"fWs3li" = "Which one?";
+/* (No Comment) */
+"JGkCuS" = "An account name is required.";
+/* (No Comment) */
"jLLidQ" = "What is the ${url}you would like add?";
+/* (No Comment) */
"oV681v" = "Add Feed";
-"srME8b" = "You are already subscribed to this feed in this account.";
-
diff --git a/iOS/IntentsExtension/en-GB.lproj/InfoPlist.strings b/iOS/IntentsExtension/en-GB.lproj/InfoPlist.strings
new file mode 100644
index 000000000..8c03764b2
--- /dev/null
+++ b/iOS/IntentsExtension/en-GB.lproj/InfoPlist.strings
@@ -0,0 +1,6 @@
+/* Bundle display name */
+"CFBundleDisplayName" = "NetNewsWire iOS Intents Extension";
+
+/* Bundle name */
+"CFBundleName" = "NetNewsWire iOS Intents Extension";
+
diff --git a/iOS/IntentsExtension/en-GB.lproj/Localizable.strings b/iOS/IntentsExtension/en-GB.lproj/Localizable.strings
new file mode 100644
index 000000000..e1ecae87c
--- /dev/null
+++ b/iOS/IntentsExtension/en-GB.lproj/Localizable.strings
@@ -0,0 +1,3 @@
+/* Unable to communicate with NetNewsWire. */
+"errordescription.localized.communication-failure" = "Unable to communicate with NetNewsWire.";
+
diff --git a/iOS/IntentsExtension/en.lproj/Localizable.strings b/iOS/IntentsExtension/en.lproj/Localizable.strings
new file mode 100644
index 000000000..e1ecae87c
--- /dev/null
+++ b/iOS/IntentsExtension/en.lproj/Localizable.strings
@@ -0,0 +1,3 @@
+/* Unable to communicate with NetNewsWire. */
+"errordescription.localized.communication-failure" = "Unable to communicate with NetNewsWire.";
+
diff --git a/iOS/KeyboardManager.swift b/iOS/KeyboardManager.swift
index 0c3788be7..780bf53d8 100644
--- a/iOS/KeyboardManager.swift
+++ b/iOS/KeyboardManager.swift
@@ -129,43 +129,43 @@ private extension KeyboardManager {
static func globalAuxilaryKeyCommands() -> [UIKeyCommand] {
var keys = [UIKeyCommand]()
- let addNewFeedTitle = NSLocalizedString("New Web Feed", comment: "New Web Feed")
+ let addNewFeedTitle = NSLocalizedString("keyboard.command.new-web-feed", comment: "New Web Feed")
keys.append(KeyboardManager.createKeyCommand(title: addNewFeedTitle, action: "addNewFeed:", input: "n", modifiers: [.command]))
- let addNewFolderTitle = NSLocalizedString("New Folder", comment: "New Folder")
+ let addNewFolderTitle = NSLocalizedString("keyboard.command.new-folder", comment: "New Folder")
keys.append(KeyboardManager.createKeyCommand(title: addNewFolderTitle, action: "addNewFolder:", input: "n", modifiers: [.command, .shift]))
- let refreshTitle = NSLocalizedString("Refresh", comment: "Refresh")
+ let refreshTitle = NSLocalizedString("keyboard.command.refresh", comment: "Refresh")
keys.append(KeyboardManager.createKeyCommand(title: refreshTitle, action: "refresh:", input: "r", modifiers: [.command]))
- let nextUnreadTitle = NSLocalizedString("Next Unread", comment: "Next Unread")
+ let nextUnreadTitle = NSLocalizedString("keyboard.command.next-unread", comment: "Next Unread")
keys.append(KeyboardManager.createKeyCommand(title: nextUnreadTitle, action: "nextUnread:", input: "/", modifiers: [.command]))
- let goToTodayTitle = NSLocalizedString("Go To Today", comment: "Go To Today")
+ let goToTodayTitle = NSLocalizedString("keyboard.command.go-to-today", comment: "Go To Today")
keys.append(KeyboardManager.createKeyCommand(title: goToTodayTitle, action: "goToToday:", input: "1", modifiers: [.command]))
- let goToAllUnreadTitle = NSLocalizedString("Go To All Unread", comment: "Go To All Unread")
+ let goToAllUnreadTitle = NSLocalizedString("keyboard.command.go-to-all-unread", comment: "Go To All Unread")
keys.append(KeyboardManager.createKeyCommand(title: goToAllUnreadTitle, action: "goToAllUnread:", input: "2", modifiers: [.command]))
- let goToStarredTitle = NSLocalizedString("Go To Starred", comment: "Go To Starred")
+ let goToStarredTitle = NSLocalizedString("keyboard.command.go-to-starred", comment: "Go To Starred")
keys.append(KeyboardManager.createKeyCommand(title: goToStarredTitle, action: "goToStarred:", input: "3", modifiers: [.command]))
- let gotoSettings = NSLocalizedString("Go To Settings", comment: "Go To Settings")
+ let gotoSettings = NSLocalizedString("keyboard.command.go-to-settings", comment: "Go To Settings")
keys.append(KeyboardManager.createKeyCommand(title: gotoSettings, action: "goToSettings:", input: ",", modifiers: [.command]))
- let articleSearchTitle = NSLocalizedString("Article Search", comment: "Article Search")
+ let articleSearchTitle = NSLocalizedString("keyboard.command.article-search", comment: "Article Search")
keys.append(KeyboardManager.createKeyCommand(title: articleSearchTitle, action: "articleSearch:", input: "f", modifiers: [.command, .alternate]))
- let markAllAsReadTitle = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
+ let markAllAsReadTitle = NSLocalizedString("keyboard.command.mark-all-as-read", comment: "Mark All as Read")
keys.append(KeyboardManager.createKeyCommand(title: markAllAsReadTitle, action: "markAllAsRead:", input: "k", modifiers: [.command]))
- let cleanUp = NSLocalizedString("Clean Up", comment: "Clean Up")
+ let cleanUp = NSLocalizedString("keyboard.command.clean-up", comment: "Clean Up")
keys.append(KeyboardManager.createKeyCommand(title: cleanUp, action: "cleanUp:", input: "'", modifiers: [.command]))
- let toggleReadFeedsFilter = NSLocalizedString("Toggle Read Feeds Filter", comment: "Toggle Read Feeds Filter")
+ let toggleReadFeedsFilter = NSLocalizedString("keyboard.command.toggle-read-feeds-filter", comment: "Toggle Read Feeds Filter")
keys.append(KeyboardManager.createKeyCommand(title: toggleReadFeedsFilter, action: "toggleReadFeedsFilter:", input: "f", modifiers: [.command, .shift]))
- let toggleReadArticlesFilter = NSLocalizedString("Toggle Read Articles Filter", comment: "Toggle Read Articles Filter")
+ let toggleReadArticlesFilter = NSLocalizedString("keyboard.command.toggle-read-articles-filter", comment: "Toggle Read Articles Filter")
keys.append(KeyboardManager.createKeyCommand(title: toggleReadArticlesFilter, action: "toggleReadArticlesFilter:", input: "h", modifiers: [.command, .shift]))
return keys
@@ -174,13 +174,13 @@ private extension KeyboardManager {
static func hardcodeFeedKeyCommands() -> [UIKeyCommand] {
var keys = [UIKeyCommand]()
- let nextUpTitle = NSLocalizedString("Select Next Up", comment: "Select Next Up")
+ let nextUpTitle = NSLocalizedString("keyboard.command.select-next-up", comment: "Select Next Up")
keys.append(KeyboardManager.createKeyCommand(title: nextUpTitle, action: "selectNextUp:", input: UIKeyCommand.inputUpArrow, modifiers: []))
- let nextDownTitle = NSLocalizedString("Select Next Down", comment: "Select Next Down")
+ let nextDownTitle = NSLocalizedString("keyboard.command.select-next-down", comment: "Select Next Down")
keys.append(KeyboardManager.createKeyCommand(title: nextDownTitle, action: "selectNextDown:", input: UIKeyCommand.inputDownArrow, modifiers: []))
- let getFeedInfo = NSLocalizedString("Get Feed Info", comment: "Get Feed Info")
+ let getFeedInfo = NSLocalizedString("keyboard.command.get-feed-info", comment: "Get Feed Info")
keys.append(KeyboardManager.createKeyCommand(title: getFeedInfo, action: "showFeedInspector:", input: "i", modifiers: .command))
return keys
@@ -189,31 +189,31 @@ private extension KeyboardManager {
static func hardcodeArticleKeyCommands() -> [UIKeyCommand] {
var keys = [UIKeyCommand]()
- let openInBrowserTitle = NSLocalizedString("Open In Browser", comment: "Open In Browser")
+ let openInBrowserTitle = NSLocalizedString("keyboard.command.open-in-browser", comment: "Open In Browser")
keys.append(KeyboardManager.createKeyCommand(title: openInBrowserTitle, action: "openInBrowser:", input: UIKeyCommand.inputRightArrow, modifiers: [.command]))
- let toggleReadTitle = NSLocalizedString("Toggle Read Status", comment: "Toggle Read Status")
+ let toggleReadTitle = NSLocalizedString("keyboard.command.toggle-read-status", comment: "Toggle Read Status")
keys.append(KeyboardManager.createKeyCommand(title: toggleReadTitle, action: "toggleRead:", input: "u", modifiers: [.command, .shift]))
- let markAboveAsReadTitle = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read")
+ let markAboveAsReadTitle = NSLocalizedString("keyboard.command.mark-above-as-read", comment: "Mark Above as Read")
keys.append(KeyboardManager.createKeyCommand(title: markAboveAsReadTitle, action: "markAboveAsRead:", input: "k", modifiers: [.command, .control]))
- let markBelowAsReadTitle = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read")
+ let markBelowAsReadTitle = NSLocalizedString("keyboard.command.mark-below-as-read", comment: "Mark Below as Read")
keys.append(KeyboardManager.createKeyCommand(title: markBelowAsReadTitle, action: "markBelowAsRead:", input: "k", modifiers: [.command, .shift]))
- let toggleStarredTitle = NSLocalizedString("Toggle Starred Status", comment: "Toggle Starred Status")
+ let toggleStarredTitle = NSLocalizedString("keyboard.command.toggle-starred-status", comment: "Toggle Starred Status")
keys.append(KeyboardManager.createKeyCommand(title: toggleStarredTitle, action: "toggleStarred:", input: "l", modifiers: [.command, .shift]))
- let findInArticleTitle = NSLocalizedString("Find in Article", comment: "Find in Article")
+ let findInArticleTitle = NSLocalizedString("keyboard.command.find-in-article", comment: "Find in Article")
keys.append(KeyboardManager.createKeyCommand(title: findInArticleTitle, action: "beginFind:", input: "f", modifiers: [.command]))
- let getFeedInfo = NSLocalizedString("Get Feed Info", comment: "Get Feed Info")
+ let getFeedInfo = NSLocalizedString("keyboard.command.get-feed-info", comment: "Get Feed Info")
keys.append(KeyboardManager.createKeyCommand(title: getFeedInfo, action: "showFeedInspector:", input: "i", modifiers: .command))
- let toggleSidebar = NSLocalizedString("Toggle Sidebar", comment: "Toggle Sidebar")
+ let toggleSidebar = NSLocalizedString("keyboard.command.toggle-sidebar", comment: "Toggle Sidebar")
keys.append(KeyboardManager.createKeyCommand(title: toggleSidebar, action: "toggleSidebar:", input: "s", modifiers: [.command, .control]))
- let toggleReaderView = NSLocalizedString("Toggle Reader View", comment: "Toggle Reader View")
+ let toggleReaderView = NSLocalizedString("keyboard.command.toggle-reader-view", comment: "Toggle Reader View")
keys.append(KeyboardManager.createKeyCommand(title: toggleReaderView, action: "toggleReaderView:", input: "r", modifiers: [.command, .shift]))
return keys
diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
index 13eeac341..1161a41a2 100644
--- a/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
+++ b/iOS/MasterFeed/Cell/MasterFeedTableViewCell.swift
@@ -125,10 +125,10 @@ protocol MasterFeedTableViewCellDelegate: AnyObject {
UIView.animate(withDuration: duration) {
if self.isDisclosureExpanded {
- self.disclosureButton?.accessibilityLabel = NSLocalizedString("Collapse Folder", comment: "Collapse Folder")
+ self.disclosureButton?.accessibilityLabel = NSLocalizedString("label.accessibility.collapse-folder", comment: "Collapse Folder")
self.disclosureButton?.imageView?.transform = CGAffineTransform(rotationAngle: 1.570796)
} else {
- self.disclosureButton?.accessibilityLabel = NSLocalizedString("Expand Folder", comment: "Expand Folder")
+ self.disclosureButton?.accessibilityLabel = NSLocalizedString("label.accessibility.expand-folder", comment: "Expand Folder")
self.disclosureButton?.imageView?.transform = CGAffineTransform(rotationAngle: 0)
}
}
diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift
index 9bfe0e3e4..a32c6a7ba 100644
--- a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift
+++ b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift
@@ -27,9 +27,9 @@ protocol MasterFeedTableViewSectionHeaderDelegate {
set {}
get {
if disclosureExpanded {
- return NSLocalizedString("Expanded", comment: "Disclosure button expanded state for accessibility")
+ return NSLocalizedString("label.accessibility.expanded", comment: "Disclosure button expanded state for accessibility")
}
- return NSLocalizedString("Collapsed", comment: "Disclosure button collapsed state for accessibility")
+ return NSLocalizedString("label.accessibility.collapsed", comment: "Disclosure button collapsed state for accessibility")
}
}
diff --git a/iOS/MasterFeed/MasterFeedViewController.swift b/iOS/MasterFeed/MasterFeedViewController.swift
index bc720ec89..9a1b033cb 100644
--- a/iOS/MasterFeed/MasterFeedViewController.swift
+++ b/iOS/MasterFeed/MasterFeedViewController.swift
@@ -268,7 +268,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
var actions = [UIContextualAction]()
// Set up the delete action
- let deleteTitle = NSLocalizedString("Delete", comment: "Delete")
+ let deleteTitle = NSLocalizedString("action.title.delete", comment: "Delete")
let deleteAction = UIContextualAction(style: .normal, title: deleteTitle) { [weak self] (action, view, completion) in
self?.delete(indexPath: indexPath)
completion(true)
@@ -277,7 +277,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
actions.append(deleteAction)
// Set up the rename action
- let renameTitle = NSLocalizedString("Rename", comment: "Rename")
+ let renameTitle = NSLocalizedString("action.title.rename", comment: "Rename")
let renameAction = UIContextualAction(style: .normal, title: renameTitle) { [weak self] (action, view, completion) in
self?.rename(indexPath: indexPath)
completion(true)
@@ -286,7 +286,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
actions.append(renameAction)
if let webFeed = coordinator.nodeFor(indexPath)?.representedObject as? WebFeed {
- let moreTitle = NSLocalizedString("More", comment: "More")
+ let moreTitle = NSLocalizedString("action.title.more", comment: "More")
let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completion) in
if let self = self {
@@ -317,7 +317,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
alert.addAction(action)
}
- let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
+ let cancelTitle = NSLocalizedString("action.title.cancel", comment: "Cancel")
alert.addAction(UIAlertAction(title: cancelTitle, style: .cancel) { _ in
completion(true)
})
@@ -518,7 +518,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
return
}
- let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
+ let title = NSLocalizedString("button.title.mark-all-as-read", comment: "Mark All as Read")
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
self?.coordinator.markAllAsReadInTimeline()
}
@@ -638,7 +638,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
var menuItems: [UIAction] = []
- let addWebFeedActionTitle = NSLocalizedString("Add Web Feed", comment: "Add Web Feed")
+ let addWebFeedActionTitle = NSLocalizedString("button.title.add-web-feed", comment: "Add Web Feed")
let addWebFeedAction = UIAction(title: addWebFeedActionTitle, image: AppAssets.plus) { _ in
self.coordinator.showAddWebFeed()
}
@@ -646,7 +646,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
if AccountManager.shared.activeAccounts.contains(where: { $0.type == .onMyMac || $0.type == .cloudKit }) {
if ExtensionPointManager.shared.isRedditEnabled {
- let addRedditFeedActionTitle = NSLocalizedString("Add Reddit Feed", comment: "Add Reddit Feed")
+ let addRedditFeedActionTitle = NSLocalizedString("button.title.add-reddit-feed", comment: "Add Reddit Feed")
let addRedditFeedAction = UIAction(title: addRedditFeedActionTitle, image: AppAssets.contextMenuReddit.tinted(color: .label)) { _ in
self.coordinator.showAddRedditFeed()
}
@@ -654,14 +654,14 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
}
}
- let addWebFolderActionTitle = NSLocalizedString("Add Folder", comment: "Add Folder")
+ let addWebFolderActionTitle = NSLocalizedString("button.title.add-folder", comment: "Add Folder")
let addWebFolderAction = UIAction(title: addWebFolderActionTitle, image: AppAssets.folderOutlinePlus) { _ in
self.coordinator.showAddFolder()
}
menuItems.append(addWebFolderAction)
- let contextMenu = UIMenu(title: NSLocalizedString("Add Item", comment: "Add Item"), image: nil, identifier: nil, options: [], children: menuItems.reversed())
+ let contextMenu = UIMenu(title: NSLocalizedString("button.title.add-item", comment: "Add Item"), image: nil, identifier: nil, options: [], children: menuItems.reversed())
self.addNewItemButton.menu = contextMenu
}
@@ -694,8 +694,8 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
}
private func showTwitterDeprecationAlert() {
- let alert = UIAlertController(title: NSLocalizedString("Twitter Integration Removed", comment: "Twitter Integration Removed"),
- message: NSLocalizedString("Twitter has ended free access to the parts of the Twitter API that we need.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.", comment: "Twitter deprecation message"),
+ let alert = UIAlertController(title: NSLocalizedString("alert.title.twitter-integration-removed", comment: "Twitter Integration Removed"),
+ message: NSLocalizedString("alert.message.twitter-integration-removed", comment: "Twitter deprecation message"),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
@@ -774,12 +774,12 @@ private extension MasterFeedViewController {
func setFilterButtonToActive() {
filterButton?.image = AppAssets.filterActiveImage
- filterButton?.accLabelText = NSLocalizedString("Selected - Filter Read Feeds", comment: "Selected - Filter Read Feeds")
+ filterButton?.accLabelText = NSLocalizedString("button.accessibility.title.selected-filter-read-feeds", comment: "Selected - Filter Read Feeds")
}
func setFilterButtonToInactive() {
filterButton?.image = AppAssets.filterInactiveImage
- filterButton?.accLabelText = NSLocalizedString("Filter Read Feeds", comment: "Filter Read Feeds")
+ filterButton?.accLabelText = NSLocalizedString("button.accessibility.title.filter-read-feeds", comment: "Filter Read Feeds")
}
func resetEstimatedRowHeight() {
@@ -1028,7 +1028,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Open Home Page", comment: "Open Home Page")
+ let title = NSLocalizedString("button.title.open-home-page", comment: "Open Home Page")
let action = UIAction(title: title, image: AppAssets.safariImage) { [weak self] action in
self?.coordinator.showBrowserForFeed(indexPath)
}
@@ -1040,7 +1040,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Open Home Page", comment: "Open Home Page")
+ let title = NSLocalizedString("button.title.open-home-page", comment: "Open Home Page")
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
self?.coordinator.showBrowserForFeed(indexPath)
completion(true)
@@ -1054,7 +1054,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Copy Feed URL", comment: "Copy Feed URL")
+ let title = NSLocalizedString("button.title.copy-feed-url", comment: "Copy Feed URL")
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
UIPasteboard.general.url = url
}
@@ -1067,7 +1067,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Copy Feed URL", comment: "Copy Feed URL")
+ let title = NSLocalizedString("button.title.copy-feed-url", comment: "Copy Feed URL")
let action = UIAlertAction(title: title, style: .default) { action in
UIPasteboard.general.url = url
completion(true)
@@ -1082,7 +1082,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Copy Home Page URL", comment: "Copy Home Page URL")
+ let title = NSLocalizedString("button.title.copy-home-pageurl", comment: "Copy Home Page URL")
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
UIPasteboard.general.url = url
}
@@ -1096,7 +1096,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Copy Home Page URL", comment: "Copy Home Page URL")
+ let title = NSLocalizedString("button.title.copy-home-pageurl", comment: "Copy Home Page URL")
let action = UIAlertAction(title: title, style: .default) { action in
UIPasteboard.general.url = url
completion(true)
@@ -1111,7 +1111,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Mark All as Read", comment: "Command")
+ let title = NSLocalizedString("button.title.mark-all-as-read.titlecase", comment: "Mark All as Read")
let cancel = {
completion(true)
}
@@ -1127,7 +1127,7 @@ private extension MasterFeedViewController {
}
func deleteAction(indexPath: IndexPath) -> UIAction {
- let title = NSLocalizedString("Delete", comment: "Delete")
+ let title = NSLocalizedString("button.title.delete", comment: "Delete Feed")
let action = UIAction(title: title, image: AppAssets.trashImage, attributes: .destructive) { [weak self] action in
self?.delete(indexPath: indexPath)
@@ -1136,7 +1136,7 @@ private extension MasterFeedViewController {
}
func renameAction(indexPath: IndexPath) -> UIAction {
- let title = NSLocalizedString("Rename", comment: "Rename")
+ let title = NSLocalizedString("button.title.rename", comment: "Rename")
let action = UIAction(title: title, image: AppAssets.editImage) { [weak self] action in
self?.rename(indexPath: indexPath)
}
@@ -1148,7 +1148,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Get Info", comment: "Get Info")
+ let title = NSLocalizedString("button.title.get-info", comment: "Get Info")
let action = UIAction(title: title, image: AppAssets.infoImage) { [weak self] action in
self?.coordinator.showFeedInspector(for: webFeed)
}
@@ -1156,7 +1156,7 @@ private extension MasterFeedViewController {
}
func getAccountInfoAction(account: Account) -> UIAction {
- let title = NSLocalizedString("Get Info", comment: "Get Info")
+ let title = NSLocalizedString("button.title.get-info", comment: "Get Info")
let action = UIAction(title: title, image: AppAssets.infoImage) { [weak self] action in
self?.coordinator.showAccountInspector(for: account)
}
@@ -1164,7 +1164,7 @@ private extension MasterFeedViewController {
}
func deactivateAccountAction(account: Account) -> UIAction {
- let title = NSLocalizedString("Deactivate", comment: "Deactivate")
+ let title = NSLocalizedString("button.title.deactivate", comment: "Deactivate")
let action = UIAction(title: title, image: AppAssets.deactivateImage) { action in
account.isActive = false
}
@@ -1176,7 +1176,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Get Info", comment: "Get Info")
+ let title = NSLocalizedString("button.title.get-info", comment: "Get Info")
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
self?.coordinator.showFeedInspector(for: webFeed)
completion(true)
@@ -1191,7 +1191,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Mark All as Read", comment: "Command")
+ let title = NSLocalizedString("button.title.mark-all-as-read.titlecase", comment: "Mark All as Read")
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
if let articles = try? feed.fetchUnreadArticles() {
@@ -1299,7 +1299,7 @@ private extension MasterFeedViewController {
return nil
}
- let title = NSLocalizedString("Mark All as Read", comment: "Command")
+ let title = NSLocalizedString("button.title.mark-all-as-read.titlecase", comment: "Mark All as Read")
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
// If you don't have this delay the screen flashes when it executes this code
@@ -1414,15 +1414,15 @@ private extension MasterFeedViewController {
func rename(indexPath: IndexPath) {
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return }
- let formatString = NSLocalizedString("Rename “%@”", comment: "Rename feed")
+ let formatString = NSLocalizedString("alert.title.rename-feed.%@", comment: "Rename feed. The variable provided is the feed name. In English: Rename “%@”")
let title = NSString.localizedStringWithFormat(formatString as NSString, feed.nameForDisplay) as String
let alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert)
- let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
+ let cancelTitle = NSLocalizedString("button.title.cancel", comment: "Cancel")
alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel))
- let renameTitle = NSLocalizedString("Rename", comment: "Rename")
+ let renameTitle = NSLocalizedString("button.title.rename", comment: "Rename")
let renameAction = UIAlertAction(title: renameTitle, style: .default) { [weak self] action in
guard let name = alertController.textFields?[0].text, !name.isEmpty else {
@@ -1456,7 +1456,7 @@ private extension MasterFeedViewController {
alertController.addTextField() { textField in
textField.text = feed.nameForDisplay
- textField.placeholder = NSLocalizedString("Name", comment: "Name")
+ textField.placeholder = NSLocalizedString("textfield.placeholder.name", comment: "Name")
}
self.present(alertController, animated: true) {
@@ -1471,21 +1471,21 @@ private extension MasterFeedViewController {
let title: String
let message: String
if feed is Folder {
- title = NSLocalizedString("Delete Folder", comment: "Delete folder")
- let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the “%@” folder?", comment: "Folder delete text")
+ title = NSLocalizedString("alert.title.delete-folder", comment: "Delete folder")
+ let localizedInformativeText = NSLocalizedString("alert.message.delete-folder.%@", comment: "Asks the user for confirmation that they wish to delete a folder. The variable provided is the folder name. In English, the message is: Are you sure you want to delete the “%@” folder?")
message = NSString.localizedStringWithFormat(localizedInformativeText as NSString, feed.nameForDisplay) as String
} else {
- title = NSLocalizedString("Delete Feed", comment: "Delete feed")
- let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the “%@” feed?", comment: "Feed delete text")
+ title = NSLocalizedString("button.title.delete-feed", comment: "Delete feed")
+ let localizedInformativeText = NSLocalizedString("alert.message.delete-feed.%@", comment: "Asks the user for confirmation that they wish to delete a folder. The variable provided is the folder name. In English, the message is: Are you sure you want to delete the “%@” feed?")
message = NSString.localizedStringWithFormat(localizedInformativeText as NSString, feed.nameForDisplay) as String
}
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
- let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
+ let cancelTitle = NSLocalizedString("button.title.cancel", comment: "Cancel")
alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel))
- let deleteTitle = NSLocalizedString("Delete", comment: "Delete")
+ let deleteTitle = NSLocalizedString("button.title.delete", comment: "Delete")
let deleteAction = UIAlertAction(title: deleteTitle, style: .destructive) { [weak self] action in
self?.performDelete(indexPath: indexPath)
}
diff --git a/iOS/MasterFeed/RefreshProgressView.swift b/iOS/MasterFeed/RefreshProgressView.swift
index 0e72d7738..05c030d2f 100644
--- a/iOS/MasterFeed/RefreshProgressView.swift
+++ b/iOS/MasterFeed/RefreshProgressView.swift
@@ -141,12 +141,12 @@ private extension RefreshProgressModel {
let relativeDateTimeFormatter = RelativeDateTimeFormatter()
relativeDateTimeFormatter.dateTimeStyle = .named
let refreshed = relativeDateTimeFormatter.localizedString(for: accountLastArticleFetchEndTime, relativeTo: Date())
- let localizedRefreshText = NSLocalizedString("Updated %@", comment: "Updated")
+ let localizedRefreshText = NSLocalizedString("label.text.updatedat.%@", comment: "Relative time that the account was last refreshed. The variable is a named relative time. Example: Updated 8 minutes ago")
let refreshText = NSString.localizedStringWithFormat(localizedRefreshText as NSString, refreshed) as String
label = refreshText
} else {
- label = NSLocalizedString("Updated Just Now", comment: "Updated Just Now")
+ label = NSLocalizedString("label.text.updatednow", comment: "Text indicating that feeds have just been updated. Example: Updated Just Now")
}
} else {
diff --git a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift
index c016d0523..b5711e1e2 100644
--- a/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift
+++ b/iOS/MasterTimeline/Cell/MasterTimelineCellData.swift
@@ -11,7 +11,7 @@ import Articles
@MainActor struct MasterTimelineCellData {
- private static let noText = NSLocalizedString("(No Text)", comment: "No Text")
+ private static let noText = NSLocalizedString("label.text.no-text", comment: "(No Text)")
let title: String
let attributedTitle: NSAttributedString
diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift
index eb628a9ed..23ddbbc72 100644
--- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift
+++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift
@@ -263,8 +263,8 @@ private extension MasterTimelineTableViewCell {
}
func updateAccessiblityLabel() {
- let starredStatus = cellData.starred ? "\(NSLocalizedString("Starred", comment: "Starred article for accessibility")), " : ""
- let unreadStatus = cellData.read ? "" : "\(NSLocalizedString("Unread", comment: "Unread")), "
+ let starredStatus = cellData.starred ? "\(NSLocalizedString("label.text.starred", comment: "Starred")), " : ""
+ let unreadStatus = cellData.read ? "" : "\(NSLocalizedString("label.text.unread", comment: "Unread")), "
let label = starredStatus + unreadStatus + "\(cellData.feedName), \(cellData.title), \(cellData.summary), \(cellData.dateString)"
accessibilityLabel = label
}
diff --git a/iOS/MasterTimeline/MarkAsReadAlertController.swift b/iOS/MasterTimeline/MarkAsReadAlertController.swift
index a0fb3a27a..217a44c17 100644
--- a/iOS/MasterTimeline/MarkAsReadAlertController.swift
+++ b/iOS/MasterTimeline/MarkAsReadAlertController.swift
@@ -46,11 +46,10 @@ extension UIBarButtonItem: MarkAsReadAlertControllerSourceType {}
completion: @escaping (UIAlertAction) -> Void) -> UIAlertController where T: MarkAsReadAlertControllerSourceType {
- let title = NSLocalizedString("Mark As Read", comment: "Mark As Read")
- let message = NSLocalizedString("You can turn this confirmation off in Settings.",
- comment: "You can turn this confirmation off in Settings.")
- let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
- let settingsTitle = NSLocalizedString("Open Settings", comment: "Open Settings")
+ let title = NSLocalizedString("alert.title.markasread", comment: "Mark As Read")
+ let message = NSLocalizedString("alert.message.markasread.turnoffconfirmation", comment: "Message that indicates a user can turn off the Mark As Read confirmation in Settings.")
+ let cancelTitle = NSLocalizedString("button.title.cancel", comment: "Cancel")
+ let settingsTitle = NSLocalizedString("button.title.open-settings", comment: "Open Settings")
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { _ in
diff --git a/iOS/MasterTimeline/MasterTimelineTitleView.swift b/iOS/MasterTimeline/MasterTimelineTitleView.swift
index 4b3c4f52e..3eaecd70e 100644
--- a/iOS/MasterTimeline/MasterTimelineTitleView.swift
+++ b/iOS/MasterTimeline/MasterTimelineTitleView.swift
@@ -22,7 +22,7 @@ import UIKit
set { }
get {
if let name = label.text {
- let unreadLabel = NSLocalizedString("unread", comment: "Unread label for accessibility")
+ let unreadLabel = NSLocalizedString("label.accessibility.unread", comment: "unread")
return "\(name) \(unreadCountView.unreadCount) \(unreadLabel)"
}
else {
diff --git a/iOS/MasterTimeline/MasterTimelineViewController.swift b/iOS/MasterTimeline/MasterTimelineViewController.swift
index 5c3174123..63cd74021 100644
--- a/iOS/MasterTimeline/MasterTimelineViewController.swift
+++ b/iOS/MasterTimeline/MasterTimelineViewController.swift
@@ -80,10 +80,11 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
- searchController.searchBar.placeholder = NSLocalizedString("Search Articles", comment: "Search Articles")
+ searchController.searchBar.placeholder = NSLocalizedString("searchbar.placeholder.searcharticles", comment: "Search Articles")
+
searchController.searchBar.scopeButtonTitles = [
- NSLocalizedString("Here", comment: "Here"),
- NSLocalizedString("All Articles", comment: "All Articles")
+ NSLocalizedString("searchbar.scope.here", comment: "Title used when describing the search when scoped to the current timeline."),
+ NSLocalizedString("searchbar.scope.allarticles", comment: "Title used when desribing the search when scoped to all articles.")
]
navigationItem.searchController = searchController
definesPresentationContext = true
@@ -164,7 +165,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
}
@IBAction func markAllAsRead(_ sender: Any) {
- let title = NSLocalizedString("Mark All as Read", comment: "Mark All as Read")
+ let title = NSLocalizedString("button.title.mark-all-as-read.titlecase", comment: "Mark All as Read")
if let source = sender as? UIBarButtonItem {
MarkAsReadAlertController.confirm(self, coordinator: coordinator, confirmTitle: title, sourceType: source) { [weak self] in
@@ -279,8 +280,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
// Set up the read action
let readTitle = article.status.read ?
- NSLocalizedString("Mark as Unread", comment: "Mark as Unread") :
- NSLocalizedString("Mark as Read", comment: "Mark as Read")
+ NSLocalizedString("button.title.mark-as-unread.titlecase", comment: "Mark as Unread. Used to mark an article unread") :
+ NSLocalizedString("button.title.mark-as-read.titlecase", comment: "Mark as Read. Used to mark an article Read")
let readAction = UIContextualAction(style: .normal, title: readTitle) { [weak self] (action, view, completion) in
self?.coordinator.toggleRead(article)
@@ -299,8 +300,8 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
// Set up the star action
let starTitle = article.status.starred ?
- NSLocalizedString("Unstar", comment: "Unstar") :
- NSLocalizedString("Star", comment: "Star")
+ NSLocalizedString("button.title.unstar", comment: "Unstar. Used when removing the starred status from an article") :
+ NSLocalizedString("button.title.star", comment: "Star. Used when marking an article as starred.")
let starAction = UIContextualAction(style: .normal, title: starTitle) { [weak self] (action, view, completion) in
self?.coordinator.toggleStar(article)
@@ -311,7 +312,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
starAction.backgroundColor = AppAssets.starColor
// Set up the read action
- let moreTitle = NSLocalizedString("More", comment: "More")
+ let moreTitle = NSLocalizedString("button.title.more", comment: "More")
let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completion) in
if let self = self {
@@ -346,7 +347,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
alert.addAction(action)
}
- let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
+ let cancelTitle = NSLocalizedString("button.title.cancel", comment: "Cancel")
alert.addAction(UIAlertAction(title: cancelTitle, style: .cancel) { _ in
completion(true)
})
@@ -659,10 +660,10 @@ private extension MasterTimelineViewController {
if coordinator.isReadArticlesFiltered {
filterButton?.image = AppAssets.filterActiveImage
- filterButton?.accLabelText = NSLocalizedString("Selected - Filter Read Articles", comment: "Selected - Filter Read Articles")
+ filterButton?.accLabelText = NSLocalizedString("label.accessibility.selected-filter-read-artciles", comment: "Selected - Filter Read Articles")
} else {
filterButton?.image = AppAssets.filterInactiveImage
- filterButton?.accLabelText = NSLocalizedString("Filter Read Articles", comment: "Filter Read Articles")
+ filterButton?.accLabelText = NSLocalizedString("label.accessibility.filter-read-artciles", comment: "Filter Read Articles")
}
tableView.selectRow(at: nil, animated: false, scrollPosition: .top)
@@ -803,8 +804,8 @@ private extension MasterTimelineViewController {
guard !article.status.read || article.isAvailableToMarkUnread else { return nil }
let title = article.status.read ?
- NSLocalizedString("Mark as Unread", comment: "Mark as Unread") :
- NSLocalizedString("Mark as Read", comment: "Mark as Read")
+ NSLocalizedString("button.title.mark-as-unread.titlecase", comment: "Mark as Unread. Used to mark an article unread") :
+ NSLocalizedString("button.title.mark-as-read.titlecase", comment: "Mark as Read. Used to mark an article Read")
let image = article.status.read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage
let action = UIAction(title: title, image: image) { [weak self] action in
@@ -817,8 +818,8 @@ private extension MasterTimelineViewController {
func toggleArticleStarStatusAction(_ article: Article) -> UIAction {
let title = article.status.starred ?
- NSLocalizedString("Mark as Unstarred", comment: "Mark as Unstarred") :
- NSLocalizedString("Mark as Starred", comment: "Mark as Starred")
+ NSLocalizedString("button.title.mark-as-unstarred.titlecase", comment: "Mark as Unstarred. Used to mark an article as unstarred") :
+ NSLocalizedString("button.title.mark-as-starred.titlecase", comment: "Mark as Starred. Used to mark an article as starred")
let image = article.status.starred ? AppAssets.starOpenImage : AppAssets.starClosedImage
let action = UIAction(title: title, image: image) { [weak self] action in
@@ -832,8 +833,8 @@ private extension MasterTimelineViewController {
guard coordinator.canMarkAboveAsRead(for: article), let contentView = self.tableView.cellForRow(at: indexPath)?.contentView else {
return nil
}
-
- let title = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read")
+
+ let title = NSLocalizedString("button.title.mark-above-as-read.titlecase", comment: "Mark Above as Read. Used to mark articles above the current article as read.")
let image = AppAssets.markAboveAsReadImage
let action = UIAction(title: title, image: image) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
@@ -848,7 +849,7 @@ private extension MasterTimelineViewController {
return nil
}
- let title = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read")
+ let title = NSLocalizedString("button.title.mark-below-as-read.titlecase", comment: "Mark Below as Read. Used to mark articles below the current article as read.")
let image = AppAssets.markBelowAsReadImage
let action = UIAction(title: title, image: image) { [weak self] action in
MarkAsReadAlertController.confirm(self, coordinator: self?.coordinator, confirmTitle: title, sourceType: contentView) { [weak self] in
@@ -863,7 +864,7 @@ private extension MasterTimelineViewController {
return nil
}
- let title = NSLocalizedString("Mark Above as Read", comment: "Mark Above as Read")
+ let title = NSLocalizedString("button.title.mark-above-as-read.titlecase", comment: "Mark Above as Read. Used to mark articles above the current article as read.")
let cancel = {
completion(true)
}
@@ -882,7 +883,7 @@ private extension MasterTimelineViewController {
return nil
}
- let title = NSLocalizedString("Mark Below as Read", comment: "Mark Below as Read")
+ let title = NSLocalizedString("button.title.mark-below-as-read.titlecase", comment: "Mark Below as Read. Used to mark articles below the current article as read.")
let cancel = {
completion(true)
}
@@ -900,7 +901,7 @@ private extension MasterTimelineViewController {
guard let webFeed = article.webFeed,
!coordinator.timelineFeedIsEqualTo(webFeed) else { return nil }
- let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
+ let title = NSLocalizedString("button.title.go-to-feed.titlecase", comment: "Go To Feed. Use to navigate to the user to the article list for a feed.")
let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in
self?.coordinator.discloseWebFeed(webFeed, animations: [.scroll, .navigation])
}
@@ -911,7 +912,7 @@ private extension MasterTimelineViewController {
guard let webFeed = article.webFeed,
!coordinator.timelineFeedIsEqualTo(webFeed) else { return nil }
- let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
+ let title = NSLocalizedString("button.title.go-to-feed", comment: "Go To Feed. Use to navigate to the user to the article list for a feed.")
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
self?.coordinator.discloseWebFeed(webFeed, animations: [.scroll, .navigation])
completion(true)
@@ -931,7 +932,7 @@ private extension MasterTimelineViewController {
}
- let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
+ let localizedMenuText = NSLocalizedString("button.title.mark-all-as-read.%@", comment: "Mark All as Read in ”feed”. The variable name is the feed name.")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
let action = UIAction(title: title, image: AppAssets.markAllAsReadImage) { [weak self] action in
@@ -953,7 +954,7 @@ private extension MasterTimelineViewController {
return nil
}
- let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Mark All as Read in Feed")
+ let localizedMenuText = NSLocalizedString("button.title.mark-all-as-read.%@", comment: "Mark All as Read in ”feed”. The variable name is the feed name.")
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
let cancel = {
completion(true)
@@ -970,7 +971,7 @@ private extension MasterTimelineViewController {
func copyArticleURLAction(_ article: Article) -> UIAction? {
guard let url = article.preferredURL else { return nil }
- let title = NSLocalizedString("Copy Article URL", comment: "Copy Article URL")
+ let title = NSLocalizedString("button.title.copy-article-url", comment: "Copy Article URL")
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
UIPasteboard.general.url = url
}
@@ -979,7 +980,7 @@ private extension MasterTimelineViewController {
func copyExternalURLAction(_ article: Article) -> UIAction? {
guard let externalLink = article.externalLink, externalLink != article.preferredLink, let url = URL(string: externalLink) else { return nil }
- let title = NSLocalizedString("Copy External URL", comment: "Copy External URL")
+ let title = NSLocalizedString("button.title.copy-external-url", comment: "Copy External URL")
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
UIPasteboard.general.url = url
}
@@ -989,7 +990,7 @@ private extension MasterTimelineViewController {
func openInBrowserAction(_ article: Article) -> UIAction? {
guard let _ = article.preferredURL else { return nil }
- let title = NSLocalizedString("Open in Browser", comment: "Open in Browser")
+ let title = NSLocalizedString("button.title.open-in-browser", comment: "Open In Browser")
let action = UIAction(title: title, image: AppAssets.safariImage) { [weak self] action in
self?.coordinator.showBrowserForArticle(article)
}
@@ -999,7 +1000,7 @@ private extension MasterTimelineViewController {
func openInBrowserAlertAction(_ article: Article, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let _ = article.preferredURL else { return nil }
- let title = NSLocalizedString("Open in Browser", comment: "Open in Browser")
+ let title = NSLocalizedString("button.title.open-in-browser", comment: "Open In Browser")
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
self?.coordinator.showBrowserForArticle(article)
completion(true)
@@ -1020,7 +1021,7 @@ private extension MasterTimelineViewController {
func shareAction(_ article: Article, indexPath: IndexPath) -> UIAction? {
guard let url = article.preferredURL else { return nil }
- let title = NSLocalizedString("Share", comment: "Share")
+ let title = NSLocalizedString("button.title.share", comment: "Share")
let action = UIAction(title: title, image: AppAssets.shareImage) { [weak self] action in
self?.shareDialogForTableCell(indexPath: indexPath, url: url, title: article.title)
}
@@ -1029,7 +1030,7 @@ private extension MasterTimelineViewController {
func shareAlertAction(_ article: Article, indexPath: IndexPath, completion: @escaping (Bool) -> Void) -> UIAlertAction? {
guard let url = article.preferredURL else { return nil }
- let title = NSLocalizedString("Share", comment: "Share")
+ let title = NSLocalizedString("button.title.share", comment: "Share")
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
completion(true)
self?.shareDialogForTableCell(indexPath: indexPath, url: url, title: article.title)
diff --git a/iOS/Resources/en-GB.lproj/InfoPlist.strings b/iOS/Resources/en-GB.lproj/InfoPlist.strings
new file mode 100644
index 000000000..abfa2a762
--- /dev/null
+++ b/iOS/Resources/en-GB.lproj/InfoPlist.strings
@@ -0,0 +1,12 @@
+/* Bundle name */
+"CFBundleName" = "NetNewsWire";
+
+/* (No Comment) */
+"NetNewsWire Theme" = "NetNewsWire Theme";
+
+/* Privacy - Photo Library Additions Usage Description */
+"NSPhotoLibraryAddUsageDescription" = "Grant permission to save images from the article.";
+
+/* (No Comment) */
+"OPML" = "OPML";
+
diff --git a/iOS/Resources/en-GB.lproj/Localizable.strings b/iOS/Resources/en-GB.lproj/Localizable.strings
new file mode 100644
index 000000000..31a2d9add
--- /dev/null
+++ b/iOS/Resources/en-GB.lproj/Localizable.strings
@@ -0,0 +1,928 @@
+/* No comment provided by engineer. */
+"%@" = "%@";
+
+/* No comment provided by engineer. */
+"%@ (%@)" = "%1$@ (%2$@)";
+
+/* On My iPad */
+"account.name.ipad" = "On My iPad";
+
+/* On My iPhone */
+"account.name.iphone" = "On My iPhone";
+
+/* On My Mac */
+"account.name.mac" = "On My Mac";
+
+/* Cancel */
+"action.title.cancel" = "Cancel";
+
+/* Delete */
+"action.title.delete" = "Delete";
+
+/* More */
+"action.title.more" = "More";
+
+/* Rename */
+"action.title.rename" = "Rename";
+
+/* Choose an account to receive the imported feeds and folders */
+"actionsheet.title.choose-opml-destination" = "Choose an account to receive the imported feeds and folders";
+
+/* Choose an account with the subscriptions to export */
+"actionsheet.title.choose-opml-export-account" = "Choose an account with the subscriptions to export";
+
+/* Find in Article */
+"activity.title.find-in-article.titlecase" = "Find in Article";
+
+/* Open in Browser */
+"activity.title.open-in-browser" = "Open in Browser";
+
+/* See articles in “%@” */
+"activity.title.see-article-in.folder.%@" = "See articles in “%@”";
+
+/* See first unread article */
+"activity.title.see-first-unread-article" = "See first unread article";
+
+/* Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings. */
+"alert.error.cloudkit-missing" = "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.";
+
+/* There is already an account of that type with that username created. */
+"alert.error.duplicate-account-username" = "There is already an account of that type with that username created.";
+
+/* Invalid API URL. */
+"alert.error.invalid-api-url" = "Invalid API URL.";
+
+/* Error message: The user provided an invalid username or password. */
+"alert.error.invalid-username-or-password" = "A username or password is required.";
+
+/* Error message: Unable to save due a Keychain error. */
+"alert.error.keychain-error" = "Unable to save account credentials due to a Keychain error.";
+
+/* Network error. Please try later. */
+"alert.error.network-error" = "A network error has occurred. Please try later.";
+
+/* Error message: This theme shares the same name as a provided theme and cannot be imported. */
+"alert.error.theme-duplicate-of-provided" = "This theme shares the same name as a provided theme and cannot be imported.";
+
+/* The account type in invalid. */
+"alert.error.unrecognized-account" = "The account type is not recognised.";
+
+/* Error message: The user must provide a username and password. */
+"alert.error.username-and-password-required" = "A username and password are required.";
+
+/* The user must provide a username, password, and URL. */
+"alert.error.username-password-url-required" = "A username, password, and API URL are required.";
+
+/* Username required. */
+"alert.error.username-required" = "A username is required.";
+
+/* The variable is the author's home page. In English, the alert message is: Author‘s website:\n%@ */
+"alert.message.author-website.%@" = "Author's website:\n%@";
+
+/* The action cannot be undone.
+ You can't undo this action. */
+"alert.message.cannot-undo-action" = "You can't undo this action.";
+
+/* Asks the user for confirmation that they wish to delete a folder. The variable provided is the folder name. In English, the message is: Are you sure you want to delete the “%@” feed? */
+"alert.message.delete-feed.%@" = "Are you sure you want to delete the “%@” feed?";
+
+/* Asks the user for confirmation that they wish to delete a folder. The variable provided is the folder name. In English, the message is: Are you sure you want to delete the “%@” folder? */
+"alert.message.delete-folder.%@" = "Are you sure you want to delete the “%@” folder?";
+
+/* This device cannot send emails. */
+"alert.message.device-cannot-send-email" = "This device cannot send emails.";
+
+/* In English: The theme “%@” already exists. Do you want to overwrite it?
+ This message details that this theme is a duplicate and gives the user the option to overwrite the existing theme. In English, the message is: The theme “%@” already exists. Overwrite it? */
+"alert.message.duplicate-theme.%@" = "The theme “%@” already exists. Overwrite it?";
+
+/* To exit Full Screen mode tap the top of the screen.\n\nYou'll only see this message once. */
+"alert.message.exit-full-screen" = "To exit Full Screen mode tap the top of the screen.\n\nYou'll only see this message once.";
+
+/* Are you sure you want to import “%@” by %@ */
+"alert.message.import-theme.%@.%@" = "Are you sure you want to import “%@” by %@?";
+
+/* The theme “%@” has been imported. */
+"alert.message.imported-theme-successfully.%@" = "The theme “%@” has been imported.";
+
+/* Message that indicates a user can turn off the Mark As Read confirmation in Settings. */
+"alert.message.markasread.turnoffconfirmation" = "You can turn off this confirmation in Settings.";
+
+/* Subscriptions have been imported to your “%@“ account. */
+"alert.message.opml-import-success.%@" = "Subscriptions have been imported to your “%@“ account.";
+
+/* Your subscriptions have been exported successfully. */
+"alert.message.opml.opml-export-success" = "Your subscriptions have been exported successfully.";
+
+/* Error message when theme data is corrupted. The variable is a description provided by Apple. In English, the message is: This theme cannot be used because of data corruption in the Info.plist. %@. */
+"alert.message.theme-data-corrupted.%@" = "This theme cannot be used because of data corruption in the Info.plist. %@.";
+
+/* This alert message provides confirmation that the theme has been installed. In English, the message is: The theme “%@” has been installed. */
+"alert.message.theme-installed.%@" = "The theme “%@” has been installed.";
+
+/* Error message when a key is missing. In English, the message is: This theme cannot be used because the the key—“%@”—is not found in the Info.plist. */
+"alert.message.theme-key-missing.%@" = "This theme cannot be used because the the key—“%@”—is not found in the Info.plist.";
+
+/* Error message when a type is mismatched. In English, the message is: This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist. */
+"alert.message.theme-type-mismatch.%@" = "This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist.";
+
+/* Error message when a value is missing. In English, the message is: This theme cannot be used because the the value—“%@”—is not found in the Info.plist. */
+"alert.message.theme-value-missing.%@" = "This theme cannot be used because the the value—“%@”—is not found in the Info.plist.";
+
+/* Twitter Deprecation Message */
+"alert.message.twitter-deprecation-message" = "On February 1, 2023, Twitter announced the end of free access to the Twitter API, effective February 9.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.";
+
+/* Are you sure you want to deactivate “%@“? */
+"alert.title.deactivate-extension.%@" = "Are you sure you want to deactivate “%@“?";
+
+/* Are you sure you want to deactivate the %@ extension “%@“? Note: the ordering of the variables is extension type, extension name. */
+"alert.title.deactive-extension.%@.%@" = "Are you sure you want to deactivate the %@ extension “%@“? ";
+
+/* Delete folder */
+"alert.title.delete-folder" = "Delete Folder";
+
+/* In English: Are you sure you want to delete “%@”? */
+"alert.title.delete-theme.%@" = "Are you sure you want to delete “%@”?";
+
+/* Duplicate Theme */
+"alert.title.duplicate-theme" = "Duplicate Theme";
+
+/* Alert Error
+ Error */
+"alert.title.error" = "Error";
+
+/* Full Screen */
+"alert.title.exit-full-screen" = "Exit Full Screen";
+
+/* Import Theme */
+"alert.title.import-theme" = "Import Theme";
+
+/* Imported Successfully */
+"alert.title.imported-theme-succesfully" = "Imported Successfully";
+
+/* Install Theme */
+"alert.title.install-theme" = "Install Theme";
+
+/* Variable ordering is theme name; author name. In English, the alert title is: Install theme “%@” by %@? */
+"alert.title.install-theme.%@.%@" = "Install theme “%@” by %@?";
+
+/* Mark As Read */
+"alert.title.markasread" = "Mark As Read";
+
+/* Alert title: Exported Successfully */
+"alert.title.opml.opml-export-success" = "Exported Successfully";
+
+/* Alert title: Imported Successfully */
+"alert.title.opml.opml-import-success" = "Imported Successfully";
+
+/* Are you sure you want to remove “%@“? */
+"alert.title.remove-account.%@" = "Are you sure you want to remove “%@“?";
+
+/* Rename feed. The variable provided is the feed name. In English: Rename “%@” */
+"alert.title.rename-feed.%@" = "Rename “%@”";
+
+/* Theme installed */
+"alert.title.theme-installed" = "Theme Installed";
+
+/* Twitter Integration Removed */
+"alert.title.twitter-integration-removed" = "Twitter Integration Removed";
+
+/* Automatic */
+"appdefaults.colorpalette.automatic" = "Automatic";
+
+/* Dark */
+"appdefaults.colorpalette.dark" = "Dark";
+
+/* Light */
+"appdefaults.colorpalette.light" = "Light";
+
+/* Filter Read Feeds */
+"button.accessibility.title.filter-read-feeds" = "Filter Read Feeds";
+
+/* Selected - Filter Read Feeds */
+"button.accessibility.title.selected-filter-read-feeds" = "Selected - Filter Read Feeds";
+
+/* Error - Reader View */
+"button.accessibility.title.error-readerview" = "Error - Reader View";
+
+/* Processing - Reader View */
+"button.accessibility.title.processing-readerview" = "Processing - Reader View";
+
+/* Reader View */
+"button.accessibility.title.readerview" = "Reader View";
+
+/* Selected - Reader View */
+"button.accessibility.title.selected-readerview" = "Selected - Reader View";
+
+/* Every 2 Hours */
+"button.title.2-hours" = "Every 2 Hours";
+
+/* Every 4 Hours */
+"button.title.4-hours" = "Every 4 Hours";
+
+/* Every 8 Hours */
+"button.title.8-hours" = "Every 8 Hours";
+
+/* Every 10 Minutes */
+"button.title.10-minutes" = "Every 10 Minutes";
+
+/* Every 30 Minutes */
+"button.title.30-minutes" = "Every 30 Minutes";
+
+/* About */
+"button.title.about" = "About";
+
+/* Activate Account */
+"button.title.activate-account" = "Activate Account";
+
+/* Add Account */
+"button.title.add-account" = "Add Account";
+
+/* Add Folder */
+"button.title.add-folder" = "Add Folder";
+
+/* Add Item */
+"button.title.add-item" = "Add Item";
+
+/* Add Reddit Feed */
+"button.title.add-reddit-feed" = "Add Reddit Feed";
+
+/* Add Web Feed */
+"button.title.add-web-feed" = "Add Web Feed";
+
+/* Add Twitter Feed */
+"button.title.addtwitterfeed" = "Add Twitter Feed";
+
+/* Button title: Always Use Reader View */
+"button.title.always-use-reader-view" = "Always Use Reader View";
+
+/* Article Themes */
+"button.title.artice-themes" = "Article Themes";
+
+/* Button title
+ Cancel */
+"button.title.cancel" = "Cancel";
+
+/* Close */
+"button.title.close" = "Close";
+
+/* Copy Article URL */
+"button.title.copy-article-url" = "Copy Article URL";
+
+/* Copy External URL */
+"button.title.copy-external-url" = "Copy External URL";
+
+/* Copy Feed URL */
+"button.title.copy-feed-url" = "Copy Feed URL";
+
+/* Copy Home Page URL */
+"button.title.copy-home-pageurl" = "Copy Home Page URL";
+
+/* Credentials */
+"button.title.credentials" = "Credentials";
+
+/* Deactivate */
+"button.title.deactivate" = "Deactivate";
+
+/* Deactivate Account */
+"button.title.deactivate-account" = "Deactivate Account";
+
+/* Deactivate Extension */
+"button.title.deactivate-extension" = "Deactivate Extension";
+
+/* Button title: Default */
+"button.title.default" = "Default";
+
+/* Delete
+ Delete Feed */
+"button.title.delete" = "Delete";
+
+/* Delete feed */
+"button.title.delete-feed" = "Delete Feed";
+
+/* Delete Feeds */
+"button.title.delete-feeds" = "Delete Feeds";
+
+/* Delete Feeds and Folders */
+"button.title.delete-feeds-and-folders" = "Delete Feeds and Folders";
+
+/* Delete Folder */
+"button.title.delete-folder" = "Delete Folder";
+
+/* Delete Folders */
+"button.title.delete-folders" = "Delete Folders";
+
+/* Delete Theme */
+"button.title.delete-theme" = "Delete Theme";
+
+/* Dismiss */
+"button.title.dismiss" = "Dismiss";
+
+/* Display & Behaviors */
+"button.title.display-and-behaviors" = "Display & Behaviours";
+
+/* Done */
+"button.title.done" = "Done";
+
+/* Enable Extension */
+"button.title.enable-extension" = "Enable Extension";
+
+/* Every Hour */
+"button.title.every-hour" = "Every Hour";
+
+/* Export Subscriptions */
+"button.title.export-subscriptions" = "Export Subscriptions";
+
+/* Full Screen */
+"button.title.full-screen" = "Full Screen";
+
+/* Get Info */
+"button.title.get-info" = "Get Info";
+
+/* Go To Feed. Use to navigate to the user to the article list for a feed. */
+"button.title.go-to-feed" = "Go To Feed";
+
+/* Go To Feed. Use to navigate to the user to the article list for a feed. */
+"button.title.go-to-feed.titlecase" = "Go to Feed";
+
+/* Import Subscriptions */
+"button.title.import-subscriptions" = "Import Subscriptions";
+
+/* Import Theme */
+"button.title.import-theme" = "Import Theme";
+
+/* Manage Accounts */
+"button.title.manage-accounts" = "Manage Accounts";
+
+/* Manage Extensions */
+"button.title.manage-extensions" = "Manage Extensions";
+
+/* Manually */
+"button.title.manually" = "Manually";
+
+/* Mark Above as Read. Used to mark articles above the current article as read. */
+"button.title.mark-above-as-read.titlecase" = "Mark Above as Read";
+
+/* Mark All as Read */
+"button.title.mark-all-as-read" = "Mark All as Read";
+
+/* Mark All as Read in ”feed”. The variable name is the feed name. */
+"button.title.mark-all-as-read.%@" = "Mark All as Read in “%@“";
+
+/* Mark All as Read */
+"button.title.mark-all-as-read.titlecase" = "Mark All as Read";
+
+/* Mark Article Unread */
+"button.title.mark-article-unread" = "Mark Article Unread";
+
+/* Mark as Read */
+"button.title.mark-as-read" = "Mark as Read";
+
+/* Mark as Read
+ Mark as Read. Used to mark an article Read */
+"button.title.mark-as-read.titlecase" = "Mark as Read";
+
+/* Mark as Starred */
+"button.title.mark-as-starred" = "Mark as Starred";
+
+/* Mark as Starred
+ Mark as Starred. Used to mark an article as starred */
+"button.title.mark-as-starred.titlecase" = "Mark as Starred";
+
+/* Mark as Unread
+ Mark as Unread. Used to mark an article unread */
+"button.title.mark-as-unread.titlecase" = "Mark as Unread";
+
+/* Mark as Unstarred
+ Mark as Unstarred. Used to mark an article as unstarred */
+"button.title.mark-as-unstarred.titlecase" = "Mark as Unstarred";
+
+/* Mark Below as Read. Used to mark articles below the current article as read. */
+"button.title.mark-below-as-read.titlecase" = "Mark Below as Read";
+
+/* Mark Read */
+"button.title.mark-read" = "Mark Read";
+
+/* Mark Starred */
+"button.title.mark-starred" = "Mark Starred";
+
+/* Mark Unread */
+"button.title.mark-unread" = "Mark Unread";
+
+/* Mark Unstarred */
+"button.title.mark-unstarred" = "Mark Unstarred";
+
+/* More */
+"button.title.more" = "More";
+
+/* NetNewsWire Help */
+"button.title.netnewswire-help" = "NetNewsWire Help";
+
+/* NetNewsWire Website */
+"button.title.netnewswire-website" = "NetNewsWire Website";
+
+/* New Article Notifications */
+"button.title.new-article-notifications" = "New Article Notifications";
+
+/* Next */
+"button.title.next" = "Next";
+
+/* Next Article */
+"button.title.next-article" = "Next Article";
+
+/* Next Result */
+"button.title.next-result" = "Next Result";
+
+/* Next Unread Article */
+"button.title.next-unread-article" = "Next Unread Article";
+
+/* Open */
+"button.title.open" = "Open";
+
+/* Open Home Page */
+"button.title.open-home-page" = "Open Home Page";
+
+/* Open In Browser */
+"button.title.open-in-browser" = "Open In Browser";
+
+/* Open Settings */
+"button.title.open-settings" = "Open Settngs";
+
+/* Open System Settings */
+"button.title.open-system-settings" = "Open System Settings";
+
+/* Overwrite */
+"button.title.overwrite" = "Overwrite";
+
+/* Overwrite Theme */
+"button.title.overwrite-theme" = "Overwrite Theme";
+
+/* Previous Article */
+"button.title.previous-article" = "Previous Article";
+
+/* Previous Result */
+"button.title.previous-result" = "Previous Result";
+
+/* Remove Account */
+"button.title.remove-account" = "Remove Account";
+
+/* Rename */
+"button.title.rename" = "Rename";
+
+/* Selected - Mark Article Unread */
+"button.title.selected-mark-article-unread" = "Selected - Mark Article Unread";
+
+/* Selected - Star Article */
+"button.title.selected-star-article" = "Selected - Star Article";
+
+/* Share */
+"button.title.share" = "Share";
+
+/* Show Feed Article */
+"button.title.show-feed-article" = "Show Feed Article";
+
+/* Show Reader View */
+"button.title.show-reader-view" = "Show Read View";
+
+/* Show Website */
+"button.title.show-website" = "Show Website";
+
+/* Star. Used when marking an article as starred. */
+"button.title.star" = "Star";
+
+/* Star Article */
+"button.title.star-article" = "Star Article";
+
+/* Timeline Layout */
+"button.title.timeline-layout" = "Timeline Layout";
+
+/* Unstar. Used when removing the starred status from an article */
+"button.title.unstar" = "Unstar";
+
+/* Update Credentials */
+"button.title.update-credentials" = "Update Credentials";
+
+/* Use iCloud */
+"button.title.use-cloudkit" = "Use iCloud";
+
+/* Button title */
+"Cancel" = "Cancel";
+
+/* Button title */
+"Done" = "Done";
+
+/* Unable to create extension. */
+"error.message.unable-to-create-extension" = "Unable to create extension.";
+
+/* Unable to communicate with NetNewsWire. */
+"errordescription.localized.communication-failure" = "Unable to communicate with NetNewsWire.";
+
+/* Filter Read Feeds */
+"Filter Read Feeds" = "Filter Read Feeds";
+
+/* Add Feed */
+"homescreen.action.add-feed" = "Add Feed";
+
+/* First Unread */
+"homescreen.action.first-unread" = "First Unread";
+
+/* Search */
+"homescreen.action.search" = "Search";
+
+/* Article Search */
+"keyboard.command.article-search" = "Article Search";
+
+/* Clean Up */
+"keyboard.command.clean-up" = "Clean Up";
+
+/* Find in Article */
+"keyboard.command.find-in-article" = "Find in Article";
+
+/* Get Feed Info */
+"keyboard.command.get-feed-info" = "Get Feed Info";
+
+/* Go To All Unread */
+"keyboard.command.go-to-all-unread" = "Go To All Unread";
+
+/* Go To Settings */
+"keyboard.command.go-to-settings" = "Go To Settings";
+
+/* Go To Starred */
+"keyboard.command.go-to-starred" = "Go To Starred";
+
+/* Go To Today */
+"keyboard.command.go-to-today" = "Go To Today";
+
+/* Mark Above as Read */
+"keyboard.command.mark-above-as-read" = "Mark Above as Read";
+
+/* Mark All as Read */
+"keyboard.command.mark-all-as-read" = "Mark All as Read";
+
+/* Mark Below as Read */
+"keyboard.command.mark-below-as-read" = "Mark Below as Read";
+
+/* New Folder */
+"keyboard.command.new-folder" = "New Folder";
+
+/* New Web Feed */
+"keyboard.command.new-web-feed" = "New Web Feed";
+
+/* Next Unread */
+"keyboard.command.next-unread" = "Next Unread";
+
+/* Open In Browser */
+"keyboard.command.open-in-browser" = "Open In Browser";
+
+/* Refresh */
+"keyboard.command.refresh" = "Refresh";
+
+/* Select Next Down */
+"keyboard.command.select-next-down" = "Select Next Down";
+
+/* Select Next Up */
+"keyboard.command.select-next-up" = "Select Next Up";
+
+/* Toggle Read Articles Filter */
+"keyboard.command.toggle-read-articles-filter" = "Toggle Read Articles Filter";
+
+/* Toggle Read Feeds Filter */
+"keyboard.command.toggle-read-feeds-filter" = "Toggle Read Feeds Filter";
+
+/* Toggle Read Status */
+"keyboard.command.toggle-read-status" = "Toggle Read Status";
+
+/* Toggle Reader View */
+"keyboard.command.toggle-reader-view" = "Toggle Reader View";
+
+/* Toggle Sidebar */
+"keyboard.command.toggle-sidebar" = "Toggle Sidebar";
+
+/* Toggle Starred Status */
+"keyboard.command.toggle-starred-status" = "Toggle Starred Status";
+
+/* Collapse Folder */
+"label.accessibility.collapse-folder" = "Collapse Folder";
+
+/* Disclosure button collapsed state for accessibility */
+"label.accessibility.collapsed" = "Collapsed";
+
+/* Expand Folder */
+"label.accessibility.expand-folder" = "Expand Folder";
+
+/* Disclosure button expanded state for accessibility */
+"label.accessibility.expanded" = "Expanded";
+
+/* Filter Read Articles */
+"label.accessibility.filter-read-artciles" = "Filter Read Articles";
+
+/* Selected - Filter Read Articles */
+"label.accessibility.selected-filter-read-artciles" = "Selected - Filter Read Articles";
+
+/* Unread label for accessiblity */
+"label.accessibility.unread" = "Unread";
+
+/* Markdown formatted link to netnewswire.com */
+"label.markdown.netnewswire-website" = "[netnewswire.com](https://netnewswire.com)";
+
+/* Feed name */
+"label.placeholder.feed-name" = "Feed name";
+
+/* In English, this shows the current search result selection out of the total number of search results. Example: 3 of 5. The variables are ordered as current selection, total search results count. */
+"label.search-results.%i.%i" = "%1$i of %2$i";
+
+/* Add, delete, enable, or disable accounts and extensions. */
+"label.text.account-and-extensions-explainer" = "Add, delete, enable, or disable accounts and extensions.";
+
+/* Settings: Accounts & Extensions section header. */
+"label.text.accounts-and-extensions" = "Account & Extensions";
+
+/* Active Accounts */
+"label.text.active-accounts" = "Active Account";
+
+/* Active Extensions */
+"label.text.active-extensions" = "Active Extensions";
+
+/* Additional Contributors */
+"label.text.additional-contributors" = "Additional Contributors";
+
+/* Settings: Appearance section header. */
+"label.text.appearance" = "Appearance";
+
+/* Manage the look, feel, and behavior of NetNewsWire. */
+"label.text.appearance-explainer" = "Manage the look, feel, and behaviour of NetNewsWire.";
+
+/* Application */
+"label.text.application" = "Application";
+
+/* Articles */
+"label.text.articles" = "Articles";
+
+/* Sign in to your BazQux account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a BazQux account? [Sign Up Here](https://bazqux.com) */
+"label.text.bazqux-explainer" = "Sign in to your BazQux account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a BazQux account? \n\n[Sign Up Here](https://bazqux.com)";
+
+/* iCloud */
+"label.text.cloudkit-account" = "iCloud";
+
+/* Your iCloud account syncs your feeds across your Mac and iOS devices */
+"label.text.cloudkit-account-footer" = "Your iCloud account syncs your feeds across your Mac and iOS devices";
+
+/* NetNewsWire will use your iCloud account to sync your subscriptions across your Mac and iOS devices. */
+"label.text.cloudkit-explainer" = "NetNewsWire will use your iCloud account to sync your subscriptions across your Mac and iOS devices.";
+
+/* Default Themes */
+"label.text.default-themes" = "Default Themes";
+
+/* These themes cannot be deleted. */
+"label.text.default-themes-explainer" = "These themes cannot be deleted.";
+
+/* Device Permissions */
+"label.text.device-permissions" = "Device Permissions";
+
+/* Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more. */
+"label.text.device-permissions-explainer" = "Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.";
+
+/* XX-Large */
+"label.text.extra-extra-large" = "XX-Large";
+
+/* X-Large */
+"label.text.extra-large" = "X-Large";
+
+/* Feed Providers */
+"label.text.feed-providers" = "Feed Providers";
+
+/* Feed Providers allow you to subscribe to some pages as if they were RSS feeds */
+"label.text.feed-providers-explainer" = "Feed Providers allow you to subscribe to some pages as if they were RSS feeds.";
+
+/* Feed URL */
+"label.text.feed-url" = "Feed URL";
+
+/* Sign in to your Feedbin account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a Feedbin account? [Sign Up Here](https://feedbin.com/signup) */
+"label.text.feedbin-explainer" = "Sign in to your Feedbin account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a Feedbin account? \n\n[Sign Up Here](https://feedbin.com/signup)";
+
+/* Sign in to your FreshRSS instance and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an FreshRSS instance? [Sign Up Here](https://freshrss.org) */
+"label.text.freshrss-explainer" = "Sign in to your FreshRSS instance and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an FreshRSS instance? \n\n[Sign Up Here](https://freshrss.org)";
+
+/* Home Page */
+"label.text.home-page" = "Home Page";
+
+/* Icon Size */
+"label.text.icon-size" = "Icon Size";
+
+/* Inactive Accounts */
+"label.text.inactive-accounts" = "Inactive Account";
+
+/* Sign in to your InoReader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an InoReader account? [Sign Up Here](https://www.inoreader.com) */
+"label.text.inoreader-explainer" = "Sign in to your InoReader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an InoReader account? \n\n[Sign Up Here](https://www.inoreader.com)";
+
+/* Large */
+"label.text.large" = "Large";
+
+/* Link: */
+"label.text.link" = "Link:";
+
+/* Local Account */
+"label.text.local-account" = "Local Account";
+
+/* Local accounts do not sync your feeds across devices */
+"label.text.local-account-explainer" = "Local accounts do not sync your feeds across devices";
+
+/* Medium */
+"label.text.medium" = "Medium";
+
+/* By Brent Simmons and the Ranchero Software team. */
+"label.text.netnewswire-byline" = "By Brent Simmons and the Ranchero Software team.";
+
+/* Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a NewsBlur account? [Sign Up Here](https://newsblur.com) */
+"label.text.newsblur-explainer" = "Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a NewsBlur account?\n\n[Sign Up Here](https://newsblur.com)";
+
+/* No results */
+"label.text.no-results" = "No results";
+
+/* (No Text) */
+"label.text.no-text" = "(No Text)";
+
+/* Number of Lines */
+"label.text.number-of-lines" = "Number of Lines";
+
+/* Primary Contributors */
+"label.text.primary-contributors" = "Primary Contributors";
+
+/* Self-Hosted Accounts */
+"label.text.self-hosted-accounts" = "Self-Hosted Accounts";
+
+/* Self-hosted accounts sync your feeds across all your devices */
+"label.text.self-hosted-accounts-explainer" = "Self-hosted accounts sync your feeds across all your devices";
+
+/* Small */
+"label.text.small" = "Small";
+
+/* Starred */
+"label.text.starred" = "Starred";
+
+/* Thanks */
+"label.text.thanks" = "Thanks";
+
+/* Created by %@ */
+"label.text.theme-created-byline.%@" = "Created by %@";
+
+/* Sign in to your The Old Reader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a The Old Reader account? [Sign Up Here](https://theoldreader.com) */
+"label.text.theoldreader-explainer" = "Sign in to your The Old Reader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a The Old Reader account? \n\n[Sign Up Here](https://theoldreader.com)";
+
+/* Third Party Themes */
+"label.text.third-party-themes" = "Third Party Themes";
+
+/* Timeline */
+"label.text.timeline" = "Timeline";
+
+/* Unread */
+"label.text.unread" = "Unread";
+
+/* Relative time that the account was last refreshed. The variable is a named relative time. Example: Updated 8 minutes ago */
+"label.text.updatedat.%@" = "Updated %@";
+
+/* Text indicating that feeds have just been updated. Example: Updated Just Now */
+"label.text.updatednow" = "Updated Just Now";
+
+/* Always Dark */
+"label.text.use-dark-appearance" = "Always Dark";
+
+/* Use Device */
+"label.text.use-device-appearance" = "Use Device";
+
+/* Always Light */
+"label.text.use-light-appearance" = "Always Light";
+
+/* Web Account */
+"label.text.web-account" = "Web Account";
+
+/* Web accounts sync your feeds across all your devices */
+"label.text.web-account-explainer" = "Web accounts sync your feeds across all your devices";
+
+/* Link to the NetNewsWire iCloud syncing limitations and soltutions website.
+ Link which opens webpage describing iCloud syncing limitations. */
+"link.markdown.icloud-limitations" = "[iCloud Syncing Limitations & Solutions](https://netnewswire.com/help/iCloud)";
+
+/* Command */
+"Mark as Read Older Than" = "Mark as Read Older Than";
+
+/* Article Appearance menu title */
+"menu.title.articleappearance" = "Article Appearance";
+
+/* About */
+"navigation.title.about" = "About";
+
+/* Add Account */
+"navigation.title.add-account" = "Add Account";
+
+/* Add Extensions */
+"navigation.title.add-extensions" = "Add Extensions";
+
+/* Add Reddit Feed */
+"navigation.title.add-reddit-feed" = "Add Reddit Feed";
+
+/* Add Twitter Feed */
+"navigation.title.add-twitter-feed" = "Add Twitter Feed";
+
+/* Article Themes */
+"navigation.title.article-themes" = "Article Themes";
+
+/* Display & Behaviors */
+"navigation.title.display-and-behaviors" = "Display & Behaviours";
+
+/* Enter Name */
+"navigation.title.enter-name" = "Enter Name";
+
+/* Enter Search */
+"navigation.title.enter-search" = "Enter Search";
+
+/* Manage Accounts */
+"navigation.title.manage-accounts" = "Manage Accounts";
+
+/* Manage Extensions */
+"navigation.title.manage-extensions" = "Manage Extensions";
+
+/* New Article Notifications */
+"navigation.title.new-article-notifications" = "New Article Notifications";
+
+/* Settings */
+"navigation.title.settings" = "Settings";
+
+/* Timeline Layout */
+"navigation.title.timeline-layout" = "Timeline Layout";
+
+/* Search Articles */
+"searchbar.placeholder.searcharticles" = "Search Articles";
+
+/* Title used when desribing the search when scoped to all articles. */
+"searchbar.scope.allarticles" = "All Articles";
+
+/* Title used when describing the search when scoped to the current timeline. */
+"searchbar.scope.here" = "Here";
+
+/* Selected - Filter Read Feeds */
+"Selected - Filter Read Feeds" = "Selected - Filter Read Feeds";
+
+/* All Unread pseudo-feed title */
+"smartfeed.title.allunread" = "All Unread";
+
+/* Starred pseudo-feed title */
+"smartfeed.title.starred" = "Starred";
+
+/* Today pseudo-feed title */
+"smartfeed.title.today" = "Today";
+
+/* Smart Feeds group title */
+"smartfeeds.title" = "Smart Feeds";
+
+/* Email Address */
+"textfield.placeholder.email-address" = "Email Address";
+
+/* Name */
+"textfield.placeholder.name" = "Name";
+
+/* Password */
+"textfield.placeholder.password" = "Password";
+
+/* Screen Name */
+"textfield.placeholder.screen-name" = "Screen Name";
+
+/* Search: */
+"textfield.placeholder.search" = "Search:";
+
+/* Search Term or #hashtag */
+"textfield.placeholder.search-term-hashtag" = "Search Term or #hashtag";
+
+/* Username */
+"textfield.placeholder.username" = "Username";
+
+/* Username or Email */
+"textfield.placeholder.username-or-email" = "Username or Email";
+
+/* Active */
+"toggle.account.active" = "Active";
+
+/* Always Show Reader View */
+"toggle.title.always-show-reader-view" = "Always Show Reader View";
+
+/* Confirm Mark All as Read */
+"toggle.title.confirm-mark-all-as-read" = "Confirm Mark All as Read";
+
+/* Group by Feed */
+"toggle.title.group-by-feed" = "Group by Feed";
+
+/* New Article Notifications */
+"toggle.title.notify-about-new-articles" = "New Article Notifications";
+
+/* Open Links in NetNewsWire */
+"toggle.title.open-links-in-netnewswire" = "Open Links in NetNewsWire";
+
+/* Refresh to Clear Articles */
+"toggle.title.refresh-to-clear-articles" = "Refresh to Clear Articles";
+
+/* Sort Oldest to Newest */
+"toggle.title.sort-oldest-to-newest" = "Sort Oldest to Newest";
+
+/* Unread label for accessiblity */
+"unread" = "unread";
+
diff --git a/iOS/Resources/en.lproj/Localizable.strings b/iOS/Resources/en.lproj/Localizable.strings
new file mode 100644
index 000000000..0c25dbaec
--- /dev/null
+++ b/iOS/Resources/en.lproj/Localizable.strings
@@ -0,0 +1,904 @@
+/* On My iPad */
+"account.name.ipad" = "On My iPad";
+
+/* On My iPhone */
+"account.name.iphone" = "On My iPhone";
+
+/* On My Mac */
+"account.name.mac" = "On My Mac";
+
+/* Cancel */
+"action.title.cancel" = "Cancel";
+
+/* Delete */
+"action.title.delete" = "Delete";
+
+/* More */
+"action.title.more" = "More";
+
+/* Rename */
+"action.title.rename" = "Rename";
+
+/* Choose an account to receive the imported feeds and folders */
+"actionsheet.title.choose-opml-destination" = "Choose an account to receive the imported feeds and folders";
+
+/* Choose an account with the subscriptions to export */
+"actionsheet.title.choose-opml-export-account" = "Choose an account with the subscriptions to export";
+
+/* Find in Article */
+"activity.title.find-in-article.titlecase" = "Find in Article";
+
+/* Open in Browser */
+"activity.title.open-in-browser" = "Open in Browser";
+
+/* See articles in “%@” */
+"activity.title.see-article-in.folder.%@" = "See articles in “%@”";
+
+/* See first unread article */
+"activity.title.see-first-unread-article" = "See first unread article";
+
+/* Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings. */
+"alert.error.cloudkit-missing" = "Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.";
+
+/* There is already an account of that type with that username created. */
+"alert.error.duplicate-account-username" = "There is already an account of that type with that username created.";
+
+/* Invalid API URL. */
+"alert.error.invalid-api-url" = "Invalid API URL.";
+
+/* Error message: The user provided an invalid username or password. */
+"alert.error.invalid-username-or-password" = "A username or password is required.";
+
+/* Error message: Unable to save due a Keychain error. */
+"alert.error.keychain-error" = "Unable to save account credentials due to a Keychain error.";
+
+/* Network error. Please try later. */
+"alert.error.network-error" = "A network error has occurred. Please try later.";
+
+/* Error message: This theme shares the same name as a provided theme and cannot be imported. */
+"alert.error.theme-duplicate-of-provided" = "This theme shares the same name as a provided theme and cannot be imported.";
+
+/* The account type in invalid. */
+"alert.error.unrecognized-account" = "The account type is not recognized.";
+
+/* Error message: The user must provide a username and password. */
+"alert.error.username-and-password-required" = "A username and password are required.";
+
+/* The user must provide a username, password, and URL. */
+"alert.error.username-password-url-required" = "A username, password, and API URL are required.";
+
+/* Username required. */
+"alert.error.username-required" = "A username is required.";
+
+/* The variable is the author's home page. In English, the alert message is: Author‘s website:\n%@ */
+"alert.message.author-website.%@" = "Author's website:\n%@";
+
+/* The action cannot be undone.
+ You can't undo this action. */
+"alert.message.cannot-undo-action" = "You can't undo this action.";
+
+/* Asks the user for confirmation that they wish to delete a folder. The variable provided is the folder name. In English, the message is: Are you sure you want to delete the “%@” feed? */
+"alert.message.delete-feed.%@" = "Are you sure you want to delete the “%@” feed?";
+
+/* Asks the user for confirmation that they wish to delete a folder. The variable provided is the folder name. In English, the message is: Are you sure you want to delete the “%@” folder? */
+"alert.message.delete-folder.%@" = "Are you sure you want to delete the “%@” folder?";
+
+/* This device cannot send emails. */
+"alert.message.device-cannot-send-email" = "This device cannot send emails.";
+
+/* In English: The theme “%@” already exists. Do you want to overwrite it?
+ This message details that this theme is a duplicate and gives the user the option to overwrite the existing theme. In English, the message is: The theme “%@” already exists. Overwrite it? */
+"alert.message.duplicate-theme.%@" = "The theme “%@” already exists. Overwrite it?";
+
+/* To exit Full Screen mode tap the top of the screen.\n\nYou'll only see this message once. */
+"alert.message.exit-full-screen" = "To exit Full Screen mode tap the top of the screen.\n\nYou'll only see this message once.";
+
+/* Are you sure you want to import “%@” by %@ */
+"alert.message.import-theme.%@.%@" = "Are you sure you want to import “%@” by %@?";
+
+/* The theme “%@” has been imported. */
+"alert.message.imported-theme-successfully.%@" = "The theme “%@” has been imported.";
+
+/* Message that indicates a user can turn off the Mark As Read confirmation in Settings. */
+"alert.message.markasread.turnoffconfirmation" = "You can turn off this confirmation in Settings.";
+
+/* Subscriptions have been imported to your “%@“ account. */
+"alert.message.opml-import-success.%@" = "Subscriptions have been imported to your “%@“ account.";
+
+/* Your subscriptions have been exported successfully. */
+"alert.message.opml.opml-export-success" = "Your subscriptions have been exported successfully.";
+
+/* Error message when theme data is corrupted. The variable is a description provided by Apple. In English, the message is: This theme cannot be used because of data corruption in the Info.plist. %@. */
+"alert.message.theme-data-corrupted.%@" = "This theme cannot be used because of data corruption in the Info.plist. %@.";
+
+/* This alert message provides confirmation that the theme has been installed. In English, the message is: The theme “%@” has been installed. */
+"alert.message.theme-installed.%@" = "The theme “%@” has been installed.";
+
+/* Error message when a key is missing. In English, the message is: This theme cannot be used because the the key—“%@”—is not found in the Info.plist. */
+"alert.message.theme-key-missing.%@" = "This theme cannot be used because the the key—“%@”—is not found in the Info.plist.";
+
+/* Error message when a type is mismatched. In English, the message is: This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist. */
+"alert.message.theme-type-mismatch.%@" = "This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist.";
+
+/* Error message when a value is missing. In English, the message is: This theme cannot be used because the the value—“%@”—is not found in the Info.plist. */
+"alert.message.theme-value-missing.%@" = "This theme cannot be used because the the value—“%@”—is not found in the Info.plist.";
+
+/* Twitter Deprecation Message */
+"alert.message.twitter-deprecation-message" = "On February 1, 2023, Twitter announced the end of free access to the Twitter API, effective February 9.\n\nSince Twitter does not provide RSS feeds, we’ve had to use the Twitter API. Without free access to that API, we can’t read feeds from Twitter.\n\nWe’ve left your Twitter feeds intact. If you have any starred items from those feeds, they will remain as long as you don’t delete those feeds.\n\nYou can still read whatever you have already downloaded. However, those feeds will no longer update.";
+
+/* Are you sure you want to deactivate “%@“? */
+"alert.title.deactivate-extension.%@" = "Are you sure you want to deactivate “%@“?";
+
+/* Are you sure you want to deactivate the %@ extension “%@“? Note: the ordering of the variables is extension type, extension name. */
+"alert.title.deactive-extension.%@.%@" = "Are you sure you want to deactivate the %@ extension “%@“? ";
+
+/* Delete folder */
+"alert.title.delete-folder" = "Delete Folder";
+
+/* In English: Are you sure you want to delete “%@”? */
+"alert.title.delete-theme.%@" = "Are you sure you want to delete “%@”?";
+
+/* Duplicate Theme */
+"alert.title.duplicate-theme" = "Duplicate Theme";
+
+/* Alert Error
+ Error */
+"alert.title.error" = "Error";
+
+/* Full Screen */
+"alert.title.exit-full-screen" = "Exit Full Screen";
+
+/* Import Theme */
+"alert.title.import-theme" = "Import Theme";
+
+/* Imported Successfully */
+"alert.title.imported-theme-succesfully" = "Imported Successfully";
+
+/* Install Theme */
+"alert.title.install-theme" = "Install Theme";
+
+/* Variable ordering is theme name; author name. In English, the alert title is: Install theme “%@” by %@? */
+"alert.title.install-theme.%@.%@" = "Install theme “%@” by %@?";
+
+/* Mark As Read */
+"alert.title.markasread" = "Mark As Read";
+
+/* Alert title: Exported Successfully */
+"alert.title.opml.opml-export-success" = "Exported Successfully";
+
+/* Alert title: Imported Successfully */
+"alert.title.opml.opml-import-success" = "Imported Successfully";
+
+/* Are you sure you want to remove “%@“? */
+"alert.title.remove-account.%@" = "Are you sure you want to remove “%@“?";
+
+/* Rename feed. The variable provided is the feed name. In English: Rename “%@” */
+"alert.title.rename-feed.%@" = "Rename “%@”";
+
+/* Theme installed */
+"alert.title.theme-installed" = "Theme Installed";
+
+/* Twitter Integration Removed */
+"alert.title.twitter-integration-removed" = "Twitter Integration Removed";
+
+/* Automatic */
+"appdefaults.colorpalette.automatic" = "Automatic";
+
+/* Dark */
+"appdefaults.colorpalette.dark" = "Dark";
+
+/* Light */
+"appdefaults.colorpalette.light" = "Light";
+
+/* Filter Read Feeds */
+"button.accessibility.title.filter-read-feeds" = "Filter Read Feeds";
+
+/* Selected - Filter Read Feeds */
+"button.accessibility.title.selected-filter-read-feeds" = "Selected - Filter Read Feeds";
+
+/* Error - Reader View */
+"button.accessibility.title.error-readerview" = "Error - Reader View";
+
+/* Processing - Reader View */
+"button.accessibility.title.processing-readerview" = "Processing - Reader View";
+
+/* Reader View */
+"button.accessibility.title.readerview" = "Reader View";
+
+/* Selected - Reader View */
+"button.accessibility.title.selected-readerview" = "Selected - Reader View";
+
+/* Every 2 Hours */
+"button.title.2-hours" = "Every 2 Hours";
+
+/* Every 4 Hours */
+"button.title.4-hours" = "Every 4 Hours";
+
+/* Every 8 Hours */
+"button.title.8-hours" = "Every 8 Hours";
+
+/* Every 10 Minutes */
+"button.title.10-minutes" = "Every 10 Minutes";
+
+/* Every 30 Minutes */
+"button.title.30-minutes" = "Every 30 Minutes";
+
+/* About */
+"button.title.about" = "About";
+
+/* Activate Account */
+"button.title.activate-account" = "Activate Account";
+
+/* Add Account */
+"button.title.add-account" = "Add Account";
+
+/* Add Folder */
+"button.title.add-folder" = "Add Folder";
+
+/* Add Item */
+"button.title.add-item" = "Add Item";
+
+/* Add Reddit Feed */
+"button.title.add-reddit-feed" = "Add Reddit Feed";
+
+/* Add Web Feed */
+"button.title.add-web-feed" = "Add Web Feed";
+
+/* Add Twitter Feed */
+"button.title.addtwitterfeed" = "Add Twitter Feed";
+
+/* Button title: Always Use Reader View */
+"button.title.always-use-reader-view" = "Always Use Reader View";
+
+/* Article Themes */
+"button.title.artice-themes" = "Article Themes";
+
+/* Button title
+ Cancel */
+"button.title.cancel" = "Cancel";
+
+/* Close */
+"button.title.close" = "Close";
+
+/* Copy Article URL */
+"button.title.copy-article-url" = "Copy Article URL";
+
+/* Copy External URL */
+"button.title.copy-external-url" = "Copy External URL";
+
+/* Copy Feed URL */
+"button.title.copy-feed-url" = "Copy Feed URL";
+
+/* Copy Home Page URL */
+"button.title.copy-home-pageurl" = "Copy Home Page URL";
+
+/* Credentials */
+"button.title.credentials" = "Credentials";
+
+/* Deactivate */
+"button.title.deactivate" = "Deactivate";
+
+/* Deactivate Account */
+"button.title.deactivate-account" = "Deactivate Account";
+
+/* Deactivate Extension */
+"button.title.deactivate-extension" = "Deactivate Extension";
+
+/* Button title: Default */
+"button.title.default" = "Default";
+
+/* Delete
+ Delete Feed */
+"button.title.delete" = "Delete";
+
+/* Delete feed */
+"button.title.delete-feed" = "Delete Feed";
+
+/* Delete Feeds */
+"button.title.delete-feeds" = "Delete Feeds";
+
+/* Delete Feeds and Folders */
+"button.title.delete-feeds-and-folders" = "Delete Feeds and Folders";
+
+/* Delete Folder */
+"button.title.delete-folder" = "Delete Folder";
+
+/* Delete Folders */
+"button.title.delete-folders" = "Delete Folders";
+
+/* Delete Theme */
+"button.title.delete-theme" = "Delete Theme";
+
+/* Dismiss */
+"button.title.dismiss" = "Dismiss";
+
+/* Display & Behaviors */
+"button.title.display-and-behaviors" = "Display & Behaviors";
+
+/* Done */
+"button.title.done" = "Done";
+
+/* Enable Extension */
+"button.title.enable-extension" = "Enable Extension";
+
+/* Every Hour */
+"button.title.every-hour" = "Every Hour";
+
+/* Export Subscriptions */
+"button.title.export-subscriptions" = "Export Subscriptions";
+
+/* Full Screen */
+"button.title.full-screen" = "Full Screen";
+
+/* Get Info */
+"button.title.get-info" = "Get Info";
+
+/* Go To Feed. Use to navigate to the user to the article list for a feed. */
+"button.title.go-to-feed" = "Go To Feed";
+
+/* Go To Feed. Use to navigate to the user to the article list for a feed. */
+"button.title.go-to-feed.titlecase" = "Go to Feed";
+
+/* Import Subscriptions */
+"button.title.import-subscriptions" = "Import Subscriptions";
+
+/* Import Theme */
+"button.title.import-theme" = "Import Theme";
+
+/* Manage Accounts */
+"button.title.manage-accounts" = "Manage Accounts";
+
+/* Manage Extensions */
+"button.title.manage-extensions" = "Manage Extensions";
+
+/* Manually */
+"button.title.manually" = "Manually";
+
+/* Mark Above as Read. Used to mark articles above the current article as read. */
+"button.title.mark-above-as-read.titlecase" = "Mark Above as Read";
+
+/* Mark All as Read */
+"button.title.mark-all-as-read" = "Mark All as Read";
+
+/* Mark All as Read in ”feed”. The variable name is the feed name. */
+"button.title.mark-all-as-read.%@" = "Mark All as Read in “%@“";
+
+/* Mark All as Read */
+"button.title.mark-all-as-read.titlecase" = "Mark All as Read";
+
+/* Mark Article Unread */
+"button.title.mark-article-unread" = "Mark Article Unread";
+
+/* Mark as Read */
+"button.title.mark-as-read" = "Mark as Read";
+
+/* Mark as Read
+ Mark as Read. Used to mark an article Read */
+"button.title.mark-as-read.titlecase" = "Mark as Read";
+
+/* Mark as Starred */
+"button.title.mark-as-starred" = "Mark as Starred";
+
+/* Mark as Starred
+ Mark as Starred. Used to mark an article as starred */
+"button.title.mark-as-starred.titlecase" = "Mark as Starred";
+
+/* Mark as Unread
+ Mark as Unread. Used to mark an article unread */
+"button.title.mark-as-unread.titlecase" = "Mark as Unread";
+
+/* Mark as Unstarred
+ Mark as Unstarred. Used to mark an article as unstarred */
+"button.title.mark-as-unstarred.titlecase" = "Mark as Unstarred";
+
+/* Mark Below as Read. Used to mark articles below the current article as read. */
+"button.title.mark-below-as-read.titlecase" = "Mark Below as Read";
+
+/* Mark Read */
+"button.title.mark-read" = "Mark Read";
+
+/* Mark Starred */
+"button.title.mark-starred" = "Mark Starred";
+
+/* Mark Unread */
+"button.title.mark-unread" = "Mark Unread";
+
+/* Mark Unstarred */
+"button.title.mark-unstarred" = "Mark Unstarred";
+
+/* More */
+"button.title.more" = "More";
+
+/* NetNewsWire Help */
+"button.title.netnewswire-help" = "NetNewsWire Help";
+
+/* NetNewsWire Website */
+"button.title.netnewswire-website" = "NetNewsWire Website";
+
+/* New Article Notifications */
+"button.title.new-article-notifications" = "New Article Notifications";
+
+/* Next */
+"button.title.next" = "Next";
+
+/* Next Article */
+"button.title.next-article" = "Next Article";
+
+/* Next Result */
+"button.title.next-result" = "Next Result";
+
+/* Next Unread Article */
+"button.title.next-unread-article" = "Next Unread Article";
+
+/* Open */
+"button.title.open" = "Open";
+
+/* Open Home Page */
+"button.title.open-home-page" = "Open Home Page";
+
+/* Open In Browser */
+"button.title.open-in-browser" = "Open In Browser";
+
+/* Open Settings */
+"button.title.open-settings" = "Open Settngs";
+
+/* Open System Settings */
+"button.title.open-system-settings" = "Open System Settings";
+
+/* Overwrite */
+"button.title.overwrite" = "Overwrite";
+
+/* Overwrite Theme */
+"button.title.overwrite-theme" = "Overwrite Theme";
+
+/* Previous Article */
+"button.title.previous-article" = "Previous Article";
+
+/* Previous Result */
+"button.title.previous-result" = "Previous Result";
+
+/* Remove Account */
+"button.title.remove-account" = "Remove Account";
+
+/* Rename */
+"button.title.rename" = "Rename";
+
+/* Selected - Mark Article Unread */
+"button.title.selected-mark-article-unread" = "Selected - Mark Article Unread";
+
+/* Selected - Star Article */
+"button.title.selected-star-article" = "Selected - Star Article";
+
+/* Share */
+"button.title.share" = "Share";
+
+/* Show Feed Article */
+"button.title.show-feed-article" = "Show Feed Article";
+
+/* Show Reader View */
+"button.title.show-reader-view" = "Show Read View";
+
+/* Show Website */
+"button.title.show-website" = "Show Website";
+
+/* Star. Used when marking an article as starred. */
+"button.title.star" = "Star";
+
+/* Star Article */
+"button.title.star-article" = "Star Article";
+
+/* Timeline Layout */
+"button.title.timeline-layout" = "Timeline Layout";
+
+/* Unstar. Used when removing the starred status from an article */
+"button.title.unstar" = "Unstar";
+
+/* Update Credentials */
+"button.title.update-credentials" = "Update Credentials";
+
+/* Use iCloud */
+"button.title.use-cloudkit" = "Use iCloud";
+
+/* Unable to create extension. */
+"error.message.unable-to-create-extension" = "Unable to create extension.";
+
+/* Unable to communicate with NetNewsWire. */
+"errordescription.localized.communication-failure" = "Unable to communicate with NetNewsWire.";
+
+/* Add Feed */
+"homescreen.action.add-feed" = "Add Feed";
+
+/* First Unread */
+"homescreen.action.first-unread" = "First Unread";
+
+/* Search */
+"homescreen.action.search" = "Search";
+
+/* Article Search */
+"keyboard.command.article-search" = "Article Search";
+
+/* Clean Up */
+"keyboard.command.clean-up" = "Clean Up";
+
+/* Find in Article */
+"keyboard.command.find-in-article" = "Find in Article";
+
+/* Get Feed Info */
+"keyboard.command.get-feed-info" = "Get Feed Info";
+
+/* Go To All Unread */
+"keyboard.command.go-to-all-unread" = "Go To All Unread";
+
+/* Go To Settings */
+"keyboard.command.go-to-settings" = "Go To Settings";
+
+/* Go To Starred */
+"keyboard.command.go-to-starred" = "Go To Starred";
+
+/* Go To Today */
+"keyboard.command.go-to-today" = "Go To Today";
+
+/* Mark Above as Read */
+"keyboard.command.mark-above-as-read" = "Mark Above as Read";
+
+/* Mark All as Read */
+"keyboard.command.mark-all-as-read" = "Mark All as Read";
+
+/* Mark Below as Read */
+"keyboard.command.mark-below-as-read" = "Mark Below as Read";
+
+/* New Folder */
+"keyboard.command.new-folder" = "New Folder";
+
+/* New Web Feed */
+"keyboard.command.new-web-feed" = "New Web Feed";
+
+/* Next Unread */
+"keyboard.command.next-unread" = "Next Unread";
+
+/* Open In Browser */
+"keyboard.command.open-in-browser" = "Open In Browser";
+
+/* Refresh */
+"keyboard.command.refresh" = "Refresh";
+
+/* Select Next Down */
+"keyboard.command.select-next-down" = "Select Next Down";
+
+/* Select Next Up */
+"keyboard.command.select-next-up" = "Select Next Up";
+
+/* Toggle Read Articles Filter */
+"keyboard.command.toggle-read-articles-filter" = "Toggle Read Articles Filter";
+
+/* Toggle Read Feeds Filter */
+"keyboard.command.toggle-read-feeds-filter" = "Toggle Read Feeds Filter";
+
+/* Toggle Read Status */
+"keyboard.command.toggle-read-status" = "Toggle Read Status";
+
+/* Toggle Reader View */
+"keyboard.command.toggle-reader-view" = "Toggle Reader View";
+
+/* Toggle Sidebar */
+"keyboard.command.toggle-sidebar" = "Toggle Sidebar";
+
+/* Toggle Starred Status */
+"keyboard.command.toggle-starred-status" = "Toggle Starred Status";
+
+/* Collapse Folder */
+"label.accessibility.collapse-folder" = "Collapse Folder";
+
+/* Disclosure button collapsed state for accessibility */
+"label.accessibility.collapsed" = "Collapsed";
+
+/* Expand Folder */
+"label.accessibility.expand-folder" = "Expand Folder";
+
+/* Disclosure button expanded state for accessibility */
+"label.accessibility.expanded" = "Expanded";
+
+/* Filter Read Articles */
+"label.accessibility.filter-read-artciles" = "Filter Read Articles";
+
+/* Selected - Filter Read Articles */
+"label.accessibility.selected-filter-read-artciles" = "Selected - Filter Read Articles";
+
+/* Unread label for accessiblity */
+"label.accessibility.unread" = "Unread";
+
+/* Markdown formatted link to netnewswire.com */
+"label.markdown.netnewswire-website" = "[netnewswire.com](https://netnewswire.com)";
+
+/* Feed name */
+"label.placeholder.feed-name" = "Feed name";
+
+/* In English, this shows the current search result selection out of the total number of search results. Example: 3 of 5. The variables are ordered as current selection, total search results count. */
+"label.search-results.%i.%i" = "%1$i of %2$i";
+
+/* Add, delete, enable, or disable accounts and extensions. */
+"label.text.account-and-extensions-explainer" = "Add, delete, enable, or disable accounts and extensions.";
+
+/* Settings: Accounts & Extensions section header. */
+"label.text.accounts-and-extensions" = "Account & Extensions";
+
+/* Active Accounts */
+"label.text.active-accounts" = "Active Account";
+
+/* Active Extensions */
+"label.text.active-extensions" = "Active Extensions";
+
+/* Additional Contributors */
+"label.text.additional-contributors" = "Additional Contributors";
+
+/* Settings: Appearance section header. */
+"label.text.appearance" = "Appearance";
+
+/* Manage the look, feel, and behavior of NetNewsWire. */
+"label.text.appearance-explainer" = "Manage the look, feel, and behavior of NetNewsWire.";
+
+/* Application */
+"label.text.application" = "Application";
+
+/* Articles */
+"label.text.articles" = "Articles";
+
+/* Sign in to your BazQux account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a BazQux account? [Sign Up Here](https://bazqux.com) */
+"label.text.bazqux-explainer" = "Sign in to your BazQux account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a BazQux account? \n\n[Sign Up Here](https://bazqux.com)";
+
+/* iCloud */
+"label.text.cloudkit-account" = "iCloud";
+
+/* Your iCloud account syncs your feeds across your Mac and iOS devices */
+"label.text.cloudkit-account-footer" = "Your iCloud account syncs your feeds across your Mac and iOS devices";
+
+/* NetNewsWire will use your iCloud account to sync your subscriptions across your Mac and iOS devices. */
+"label.text.cloudkit-explainer" = "NetNewsWire will use your iCloud account to sync your subscriptions across your Mac and iOS devices.";
+
+/* Default Themes */
+"label.text.default-themes" = "Default Themes";
+
+/* These themes cannot be deleted. */
+"label.text.default-themes-explainer" = "These themes cannot be deleted.";
+
+/* Device Permissions */
+"label.text.device-permissions" = "Device Permissions";
+
+/* Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more. */
+"label.text.device-permissions-explainer" = "Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.";
+
+/* XX-Large */
+"label.text.extra-extra-large" = "XX-Large";
+
+/* X-Large */
+"label.text.extra-large" = "X-Large";
+
+/* Feed Providers */
+"label.text.feed-providers" = "Feed Providers";
+
+/* Feed Providers allow you to subscribe to some pages as if they were RSS feeds */
+"label.text.feed-providers-explainer" = "Feed Providers allow you to subscribe to some pages as if they were RSS feeds.";
+
+/* Feed URL */
+"label.text.feed-url" = "Feed URL";
+
+/* Sign in to your Feedbin account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a Feedbin account? [Sign Up Here](https://feedbin.com/signup) */
+"label.text.feedbin-explainer" = "Sign in to your Feedbin account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a Feedbin account? \n\n[Sign Up Here](https://feedbin.com/signup)";
+
+/* Sign in to your FreshRSS instance and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an FreshRSS instance? [Sign Up Here](https://freshrss.org) */
+"label.text.freshrss-explainer" = "Sign in to your FreshRSS instance and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an FreshRSS instance? \n\n[Sign Up Here](https://freshrss.org)";
+
+/* Home Page */
+"label.text.home-page" = "Home Page";
+
+/* Icon Size */
+"label.text.icon-size" = "Icon Size";
+
+/* Inactive Accounts */
+"label.text.inactive-accounts" = "Inactive Account";
+
+/* Sign in to your InoReader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an InoReader account? [Sign Up Here](https://www.inoreader.com) */
+"label.text.inoreader-explainer" = "Sign in to your InoReader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have an InoReader account? \n\n[Sign Up Here](https://www.inoreader.com)";
+
+/* Large */
+"label.text.large" = "Large";
+
+/* Link: */
+"label.text.link" = "Link:";
+
+/* Local Account */
+"label.text.local-account" = "Local Account";
+
+/* Local accounts do not sync your feeds across devices */
+"label.text.local-account-explainer" = "Local accounts do not sync your feeds across devices";
+
+/* Medium */
+"label.text.medium" = "Medium";
+
+/* By Brent Simmons and the Ranchero Software team. */
+"label.text.netnewswire-byline" = "By Brent Simmons and the Ranchero Software team.";
+
+/* Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a NewsBlur account? [Sign Up Here](https://newsblur.com) */
+"label.text.newsblur-explainer" = "Sign in to your NewsBlur account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a NewsBlur account?\n\n[Sign Up Here](https://newsblur.com)";
+
+/* No results */
+"label.text.no-results" = "No results";
+
+/* (No Text) */
+"label.text.no-text" = "(No Text)";
+
+/* Number of Lines */
+"label.text.number-of-lines" = "Number of Lines";
+
+/* Primary Contributors */
+"label.text.primary-contributors" = "Primary Contributors";
+
+/* Self-Hosted Accounts */
+"label.text.self-hosted-accounts" = "Self-Hosted Accounts";
+
+/* Self-hosted accounts sync your feeds across all your devices */
+"label.text.self-hosted-accounts-explainer" = "Self-hosted accounts sync your feeds across all your devices";
+
+/* Small */
+"label.text.small" = "Small";
+
+/* Starred */
+"label.text.starred" = "Starred";
+
+/* Thanks */
+"label.text.thanks" = "Thanks";
+
+/* Created by %@ */
+"label.text.theme-created-byline.%@" = "Created by %@";
+
+/* Sign in to your The Old Reader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a The Old Reader account? [Sign Up Here](https://theoldreader.com) */
+"label.text.theoldreader-explainer" = "Sign in to your The Old Reader account and sync your feeds across your devices. Your username and password will be encrypted and stored in Keychain.\n\nDon’t have a The Old Reader account? \n\n[Sign Up Here](https://theoldreader.com)";
+
+/* Third Party Themes */
+"label.text.third-party-themes" = "Third Party Themes";
+
+/* Timeline */
+"label.text.timeline" = "Timeline";
+
+/* Unread */
+"label.text.unread" = "Unread";
+
+/* Relative time that the account was last refreshed. The variable is a named relative time. Example: Updated 8 minutes ago */
+"label.text.updatedat.%@" = "Updated %@";
+
+/* Text indicating that feeds have just been updated. Example: Updated Just Now */
+"label.text.updatednow" = "Updated Just Now";
+
+/* Always Dark */
+"label.text.use-dark-appearance" = "Always Dark";
+
+/* Use Device */
+"label.text.use-device-appearance" = "Use Device";
+
+/* Always Light */
+"label.text.use-light-appearance" = "Always Light";
+
+/* Web Account */
+"label.text.web-account" = "Web Account";
+
+/* Web accounts sync your feeds across all your devices */
+"label.text.web-account-explainer" = "Web accounts sync your feeds across all your devices";
+
+/* Link to the NetNewsWire iCloud syncing limitations and soltutions website.
+ Link which opens webpage describing iCloud syncing limitations. */
+"link.markdown.icloud-limitations" = "[iCloud Syncing Limitations & Solutions](https://netnewswire.com/help/iCloud)";
+
+/* Article Appearance menu title */
+"menu.title.articleappearance" = "Article Appearance";
+
+/* About */
+"navigation.title.about" = "About";
+
+/* Add Account */
+"navigation.title.add-account" = "Add Account";
+
+/* Add Extensions */
+"navigation.title.add-extensions" = "Add Extensions";
+
+/* Add Reddit Feed */
+"navigation.title.add-reddit-feed" = "Add Reddit Feed";
+
+/* Add Twitter Feed */
+"navigation.title.add-twitter-feed" = "Add Twitter Feed";
+
+/* Article Themes */
+"navigation.title.article-themes" = "Article Themes";
+
+/* Display & Behaviors */
+"navigation.title.display-and-behaviors" = "Display & Behaviors";
+
+/* Enter Name */
+"navigation.title.enter-name" = "Enter Name";
+
+/* Enter Search */
+"navigation.title.enter-search" = "Enter Search";
+
+/* Manage Accounts */
+"navigation.title.manage-accounts" = "Manage Accounts";
+
+/* Manage Extensions */
+"navigation.title.manage-extensions" = "Manage Extensions";
+
+/* New Article Notifications */
+"navigation.title.new-article-notifications" = "New Article Notifications";
+
+/* Settings */
+"navigation.title.settings" = "Settings";
+
+/* Timeline Layout */
+"navigation.title.timeline-layout" = "Timeline Layout";
+
+/* Search Articles */
+"searchbar.placeholder.searcharticles" = "Search Articles";
+
+/* Title used when desribing the search when scoped to all articles. */
+"searchbar.scope.allarticles" = "All Articles";
+
+/* Title used when describing the search when scoped to the current timeline. */
+"searchbar.scope.here" = "Here";
+
+/* All Unread pseudo-feed title */
+"smartfeed.title.allunread" = "All Unread";
+
+/* Starred pseudo-feed title */
+"smartfeed.title.starred" = "Starred";
+
+/* Today pseudo-feed title */
+"smartfeed.title.today" = "Today";
+
+/* Smart Feeds group title */
+"smartfeeds.title" = "Smart Feeds";
+
+/* Email Address */
+"textfield.placeholder.email-address" = "Email Address";
+
+/* Name */
+"textfield.placeholder.name" = "Name";
+
+/* Password */
+"textfield.placeholder.password" = "Password";
+
+/* Screen Name */
+"textfield.placeholder.screen-name" = "Screen Name";
+
+/* Search: */
+"textfield.placeholder.search" = "Search:";
+
+/* Search Term or #hashtag */
+"textfield.placeholder.search-term-hashtag" = "Search Term or #hashtag";
+
+/* Username */
+"textfield.placeholder.username" = "Username";
+
+/* Username or Email */
+"textfield.placeholder.username-or-email" = "Username or Email";
+
+/* Active */
+"toggle.account.active" = "Active";
+
+/* Always Show Reader View */
+"toggle.title.always-show-reader-view" = "Always Show Reader View";
+
+/* Confirm Mark All as Read */
+"toggle.title.confirm-mark-all-as-read" = "Confirm Mark All as Read";
+
+/* Group by Feed */
+"toggle.title.group-by-feed" = "Group by Feed";
+
+/* New Article Notifications */
+"toggle.title.notify-about-new-articles" = "New Article Notifications";
+
+/* Open Links in NetNewsWire */
+"toggle.title.open-links-in-netnewswire" = "Open Links in NetNewsWire";
+
+/* Refresh to Clear Articles */
+"toggle.title.refresh-to-clear-articles" = "Refresh to Clear Articles";
+
+/* Sort Oldest to Newest */
+"toggle.title.sort-oldest-to-newest" = "Sort Oldest to Newest";
+
diff --git a/iOS/Settings/Account and Extensions/Accounts/AccountsManagementView.swift b/iOS/Settings/Account and Extensions/Accounts/AccountsManagementView.swift
index 6dc43826d..5bf780cf7 100644
--- a/iOS/Settings/Account and Extensions/Accounts/AccountsManagementView.swift
+++ b/iOS/Settings/Account and Extensions/Accounts/AccountsManagementView.swift
@@ -61,19 +61,19 @@ struct AccountsManagementView: View {
var body: some View {
List {
- Section(header: Text("Active Accounts", comment: "Active accounts section header")) {
+ Section(header: Text("label.text.active-accounts", comment: "Active Accounts")) {
ForEach(viewModel.sortedActiveAccounts, id: \.self) { account in
accountRow(account)
}
}
- Section(header: Text("Inactive Accounts", comment: "Inactive accounts section header")) {
+ Section(header: Text("label.text.inactive-accounts", comment: "Inactive Accounts")) {
ForEach(viewModel.sortedInactiveAccounts, id: \.self) { account in
accountRow(account)
}
}
}
- .navigationTitle(Text("Manage Accounts", comment: "Navigation title: Manage Accounts"))
+ .navigationTitle(Text("navigation.title.manage-accounts", comment: "Manage Accounts"))
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
@@ -86,21 +86,21 @@ struct AccountsManagementView: View {
.sheet(isPresented: $viewModel.showAddAccountSheet) {
AddAccountListView()
}
- .alert(Text("Are you sure you want to remove “\(viewModel.accountToDelete?.nameForDisplay ?? "")”?", comment: "Alert title: confirm account removal"),
+ .alert(Text("alert.title.remove-account.\(viewModel.accountToDelete?.nameForDisplay ?? "")", comment: "Are you sure you want to remove “%@“?"),
isPresented: $viewModel.showAccountDeletionAlert) {
Button(role: .destructive) {
AccountManager.shared.deleteAccount(viewModel.accountToDelete!)
} label: {
- Text("Remove Account", comment: "Button title")
+ Text("button.title.remove-account", comment: "Remove Account")
}
Button(role: .cancel) {
viewModel.restoreAccount(viewModel.accountToDelete!)
} label: {
- Text("Cancel", comment: "Button title")
+ Text("button.title.cancel", comment: "Cancel")
}
} message: {
- Text("This action cannot be undone.", comment: "Alert message: remove account confirmation")
+ Text("alert.message.cannot-undo-action", comment: "The action cannot be undone.")
}
}
@@ -112,7 +112,7 @@ struct AccountsManagementView: View {
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25)
- Text(account.nameForDisplay)
+ Text(verbatim: account.nameForDisplay)
}
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
if account != AccountManager.shared.defaultAccount {
@@ -120,21 +120,27 @@ struct AccountsManagementView: View {
viewModel.temporarilyDeleteAccount(account)
} label: {
Label {
- Text("Remove Account", comment: "Button title")
+ Text("button.title.remove-account", comment: "Remove Account")
} icon: {
Image(systemName: "trash")
}
}
}
Button {
- withAnimation {
- account.isActive.toggle()
- }
+ account.isActive.toggle()
} label: {
- if account.isActive {
- Image(systemName: "minus.circle")
- } else {
- Image(systemName: "togglepower")
+ Label {
+ if account.isActive {
+ Text("button.title.deactivate-account", comment: "Deactivate Account")
+ } else {
+ Text("button.title.activate-account", comment: "Activate Account")
+ }
+ } icon: {
+ if account.isActive {
+ Image(systemName: "minus.circle")
+ } else {
+ Image(systemName: "togglepower")
+ }
}
}.tint(account.isActive ? .yellow : Color(uiColor: AppAssets.primaryAccentColor))
}
diff --git a/iOS/Settings/Account and Extensions/Accounts/AddAccountListView.swift b/iOS/Settings/Account and Extensions/Accounts/AddAccountListView.swift
index b56e2b3a4..a4a6138ea 100644
--- a/iOS/Settings/Account and Extensions/Accounts/AddAccountListView.swift
+++ b/iOS/Settings/Account and Extensions/Accounts/AddAccountListView.swift
@@ -74,7 +74,7 @@ struct AddAccountListView: View {
webAccountSection
selfHostedSection
}
- .navigationTitle(Text("Add Account", comment: "Navigation title: Add Account"))
+ .navigationTitle(Text("navigation.title.add-account", comment: "Add Account"))
.navigationBarTitleDisplayMode(.inline)
.listItemTint(.primary)
.toolbar {
@@ -82,7 +82,7 @@ struct AddAccountListView: View {
Button(role: .cancel) {
dismiss()
} label: {
- Text("Cancel", comment: "Button title")
+ Text("button.title.cancel", comment: "Button title")
}
}
}
@@ -100,11 +100,11 @@ struct AddAccountListView: View {
Text(viewModel.showAddAccountSheet.accountType.localizedAccountName())
}
}
- .alert(Text("Error", comment: "Alert title: Error"),
+ .alert(Text("alert.title.error", comment: "Error"),
isPresented: $viewModel.showAddAccountError.1,
actions: { },
message: {
- Text("\(viewModel.showAddAccountError.0?.localizedDescription ?? "Unknown Error")")
+ Text(verbatim: "\(viewModel.showAddAccountError.0?.localizedDescription ?? "Unknown Error")")
})
.dismissOnAccountAdd()
}
@@ -116,7 +116,7 @@ struct AddAccountListView: View {
viewModel.showAddAccountSheet = (true, .onMyMac)
} label: {
Label {
- Text(AccountType.onMyMac.localizedAccountName())
+ Text(verbatim: AccountType.onMyMac.localizedAccountName())
.foregroundColor(.primary)
} icon: {
Image(uiImage: AppAssets.image(for: .onMyMac)!)
@@ -125,9 +125,9 @@ struct AddAccountListView: View {
}
}
} header: {
- Text("Local", comment: "Add Account: Local account section header")
+ Text("label.text.local-account", comment: "Local Account")
} footer: {
- Text("Local accounts do not sync your feeds across devices", comment: "Local account section footer")
+ Text("label.text.local-account-explainer", comment: "Local accounts do not sync your feeds across devices")
}
}
@@ -148,9 +148,9 @@ struct AddAccountListView: View {
}
.disabled(interactionDisabled(for: .cloudKit))
} header: {
- Text("iCloud", comment: "Add Account: iCloud section header")
+ Text("label.text.cloudkit-account", comment: "iCloud")
} footer: {
- Text("Your iCloud account syncs your feeds across your Mac and iOS devices", comment: "Add Account: iCloud section footer")
+ Text("label.text.cloudkit-account-footer", comment: "Your iCloud account syncs your feeds across your Mac and iOS devices")
}
}
@@ -180,9 +180,9 @@ struct AddAccountListView: View {
}
}
} header: {
- Text("Web Account", comment: "Add Account: Web Account section header")
+ Text("label.text.web-account", comment: "Web Account")
} footer: {
- Text("Web accounts sync your feeds across all your devices", comment: "Add Account: Web Account section footer")
+ Text("label.text.web-account-explainer", comment: "Web accounts sync your feeds across all your devices")
}
}
@@ -202,9 +202,9 @@ struct AddAccountListView: View {
}
}
} header: {
- Text("Self-Hosted", comment: "Add Accont: Self-hosted section header")
+ Text("label.text.self-hosted-accounts", comment: "Self-Hosted Accounts")
} footer: {
- Text("Self-hosted accounts sync your feeds across all your devices", comment: "Add Account: Self-hosted section footer")
+ Text("label.text.self-hosted-accounts-explainer", comment: "Self-hosted accounts sync your feeds across all your devices")
}
}
diff --git a/iOS/Settings/Account and Extensions/Extensions/AddExtensionListView.swift b/iOS/Settings/Account and Extensions/Extensions/AddExtensionListView.swift
index b7e5178ea..c7e81a742 100644
--- a/iOS/Settings/Account and Extensions/Extensions/AddExtensionListView.swift
+++ b/iOS/Settings/Account and Extensions/Extensions/AddExtensionListView.swift
@@ -18,8 +18,8 @@ struct AddExtensionListView: View {
var body: some View {
NavigationView {
List {
- Section(header: Text("Feed Providers", comment: "Feed Providers section header"),
- footer: Text("Feed Providers allow you to subscribe to some pages as if they were RSS feeds.", comment: "Feed Providers section footer.")) {
+ Section(header: Text("label.text.feed-providers", comment: "Feed Providers"),
+ footer: Text("label.text.feed-providers-explainer", comment: "Feed Providers allow you to subscribe to some pages as if they were RSS feeds")) {
ForEach(0..(
get: { !appDefaults.useSystemBrowser },
@@ -37,7 +37,7 @@ struct DisplayAndBehaviorsView: View {
// TODO: Add Reader Mode Defaults here. See #3684.
}
}
- .navigationTitle(Text("Display & Behaviors", comment: "Navigation title for Display & Behaviours"))
+ .navigationTitle(Text("navigation.title.display-and-behaviors", comment: "Display & Behaviors"))
.tint(Color(uiColor: AppAssets.primaryAccentColor))
}
diff --git a/iOS/Settings/Appearance/TimelineCustomizerView.swift b/iOS/Settings/Appearance/TimelineCustomizerView.swift
index 07404c7d9..b9252fc0a 100644
--- a/iOS/Settings/Appearance/TimelineCustomizerView.swift
+++ b/iOS/Settings/Appearance/TimelineCustomizerView.swift
@@ -15,7 +15,7 @@ struct TimelineCustomizerView: View {
var body: some View {
List {
- Section(header: Text("Icon Size", comment: "Timline Customiser: Icon size section header")) {
+ Section(header: Text("label.text.icon-size", comment: "Icon Size")) {
ZStack {
TickMarkSliderView(minValue: 1, maxValue: 3, currentValue: Binding(get: {
Float(appDefaults.timelineIconSize.rawValue)
@@ -27,7 +27,7 @@ struct TimelineCustomizerView: View {
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
- Section(header: Text("Number of Lines", comment: "Timeline customiser: Number of lines section header")) {
+ Section(header: Text("label.text.number-of-lines", comment: "Number of Lines")) {
ZStack {
TickMarkSliderView(minValue: 1, maxValue: 5, currentValue: Binding(get: {
Float(appDefaults.timelineNumberOfLines)
@@ -45,7 +45,7 @@ struct TimelineCustomizerView: View {
}
}
.listStyle(.grouped)
- .navigationTitle(Text("Timeline Layout", comment: "Navigation bar title for Timeline Layout"))
+ .navigationTitle(Text("navigation.title.timeline-layout", comment: "Timeline Layout"))
}
@@ -70,11 +70,11 @@ struct TimelineCustomizerView: View {
.bold()
.lineLimit(appDefaults.timelineNumberOfLines)
HStack {
- Text("Feed name", comment: "Feed name placeholder used in timeline preview")
+ Text("label.placeholder.feed-name", comment: "Feed name")
.foregroundColor(.secondary)
.font(.caption)
Spacer()
- Text("08:51", comment: "Sample time used in timeline preview")
+ Text(verbatim: localizedTime())
.foregroundColor(.secondary)
.font(.caption)
}.padding(0)
@@ -84,6 +84,13 @@ struct TimelineCustomizerView: View {
.padding(.vertical, 4)
.padding(.leading, 4)
}
+
+ func localizedTime() -> String {
+ let now = Date.now
+ let formatter = DateFormatter()
+ formatter.setLocalizedDateFormatFromTemplate("hh:mm")
+ return formatter.string(from: now)
+ }
}
struct TimelineCustomizerView_Previews: PreviewProvider {
diff --git a/iOS/Settings/ArticleThemeImporter.swift b/iOS/Settings/ArticleThemeImporter.swift
index 8e184842a..df1337199 100644
--- a/iOS/Settings/ArticleThemeImporter.swift
+++ b/iOS/Settings/ArticleThemeImporter.swift
@@ -20,19 +20,19 @@ struct ArticleThemeImporter: Logging {
return
}
- let localizedTitleText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text")
+ let localizedTitleText = NSLocalizedString("alert.title.install-theme.%@.%@", comment: "Variable ordering is theme name; author name. In English, the alert title is: Install theme “%@” by %@?")
let title = NSString.localizedStringWithFormat(localizedTitleText as NSString, theme.name, theme.creatorName) as String
- let localizedMessageText = NSLocalizedString("Author‘s website:\n%@", comment: "Authors website")
+ let localizedMessageText = NSLocalizedString("alert.message.author-website.%@", comment: "The variable is the author's home page. In English, the alert message is: Author‘s website:\n%@")
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.creatorHomePage) as String
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
- let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
+ let cancelTitle = NSLocalizedString("button.title.cancel", comment: "Cancel")
alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel))
if let url = URL(string: theme.creatorHomePage) {
- let visitSiteTitle = NSLocalizedString("Show Website", comment: "Show Website")
+ let visitSiteTitle = NSLocalizedString("button.title.show-website", comment: "Show Website")
let visitSiteAction = UIAlertAction(title: visitSiteTitle, style: .default) { action in
UIApplication.shared.open(url)
Self.importTheme(controller: controller, filename: filename)
@@ -50,20 +50,20 @@ struct ArticleThemeImporter: Logging {
}
}
- let installThemeTitle = NSLocalizedString("Install Theme", comment: "Install Theme")
+ let installThemeTitle = NSLocalizedString("alert.title.install-theme", comment: "Install Theme")
let installThemeAction = UIAlertAction(title: installThemeTitle, style: .default) { action in
if ArticleThemesManager.shared.themeExists(filename: filename) {
- let title = NSLocalizedString("Duplicate Theme", comment: "Duplicate Theme")
- let localizedMessageText = NSLocalizedString("The theme “%@” already exists. Overwrite it?", comment: "Overwrite theme")
+ let title = NSLocalizedString("alert.title.duplicate-theme", comment: "Duplicate Theme")
+ let localizedMessageText = NSLocalizedString("alert.message.duplicate-theme.%@", comment: "This message details that this theme is a duplicate and gives the user the option to overwrite the existing theme. In English, the message is: The theme “%@” already exists. Overwrite it?")
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name) as String
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
- let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
+ let cancelTitle = NSLocalizedString("button.title.cancel", comment: "Cancel")
alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel))
- let overwriteAction = UIAlertAction(title: NSLocalizedString("Overwrite", comment: "Overwrite"), style: .default) { action in
+ let overwriteAction = UIAlertAction(title: NSLocalizedString("button.title.overwrite", comment: "Overwrite"), style: .default) { action in
importTheme()
}
alertController.addAction(overwriteAction)
@@ -88,14 +88,14 @@ struct ArticleThemeImporter: Logging {
private extension ArticleThemeImporter {
static func confirmImportSuccess(controller: UIViewController, themeName: String) {
- let title = NSLocalizedString("Theme installed", comment: "Theme installed")
+ let title = NSLocalizedString("alert.title.theme-installed", comment: "Theme installed")
- let localizedMessageText = NSLocalizedString("The theme “%@” has been installed.", comment: "Theme installed")
+ let localizedMessageText = NSLocalizedString("alert.message.theme-installed.%@", comment: "This alert message provides confirmation that the theme has been installed. In English, the message is: The theme “%@” has been installed.")
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, themeName) as String
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
- let doneTitle = NSLocalizedString("Done", comment: "Done")
+ let doneTitle = NSLocalizedString("button.title.done", comment: "Done")
alertController.addAction(UIAlertAction(title: doneTitle, style: .default))
controller.present(alertController, animated: true)
diff --git a/iOS/Settings/General/SettingsRows.swift b/iOS/Settings/General/SettingsRows.swift
index 4a6eb4e78..36d39528f 100644
--- a/iOS/Settings/General/SettingsRows.swift
+++ b/iOS/Settings/General/SettingsRows.swift
@@ -18,7 +18,7 @@ struct SettingsRow {
/// This row, when tapped, will open iOS System Settings.
static var openSystemSettings: some View {
Label {
- Text("Open System Settings", comment: "Button: opens device Settings app.")
+ Text("button.title.open-system-settings", comment: "Open System Settings")
} icon: {
Image("system.settings")
.resizable()
@@ -36,7 +36,7 @@ struct SettingsRow {
static var configureNewArticleNotifications: some View {
NavigationLink(destination: NewArticleNotificationsView()) {
Label {
- Text("New Article Notifications", comment: "Button: opens New Article Notifications view")
+ Text("button.title.new-article-notifications", comment: "New Article Notifications")
} icon: {
Image("notifications.sounds")
.resizable()
@@ -51,7 +51,7 @@ struct SettingsRow {
static var addAccount: some View {
NavigationLink(destination: AccountsManagementView()) {
Label {
- Text("Manage Accounts", comment: "Button: opens Accounts Management view")
+ Text("button.title.manage-accounts", comment: "Manage Accounts")
} icon: {
Image("app.account")
.resizable()
@@ -76,7 +76,7 @@ struct SettingsRow {
static var manageExtensions: some View {
NavigationLink(destination: ExtensionsManagementView()) {
Label {
- Text("Manage Extensions", comment: "Button: opens Extensions Management view")
+ Text("button.title.manage-extensions", comment: "Manage Extensions")
} icon: {
Image("app.extension")
.resizable()
@@ -93,7 +93,7 @@ struct SettingsRow {
showImportActionSheet.wrappedValue.toggle()
} label: {
Label {
- Text("Import Subscriptions", comment: "Button: opens import subscriptions view")
+ Text("button.title.import-subscriptions", comment: "Import Subscriptions")
.foregroundColor(.primary)
} icon: {
@@ -112,7 +112,7 @@ struct SettingsRow {
showExportActionSheet.wrappedValue.toggle()
} label: {
Label {
- Text("Export Subscriptions", comment: "Button: opens Export Subscriptions view")
+ Text("button.title.export-subscriptions", comment: "Export Subscriptions")
.foregroundColor(.primary)
} icon: {
@@ -129,7 +129,7 @@ struct SettingsRow {
/// - Returns: `Toggle`
static func sortOldestToNewest(_ preference: Binding) -> some View {
Toggle(isOn: preference) {
- Text("Sort Oldest to Newest", comment: "Toggle: Sort articles from oldest to newest when enabled.")
+ Text("toggle.title.sort-oldest-to-newest", comment: "Sort Oldest to Newest")
}
}
@@ -138,7 +138,7 @@ struct SettingsRow {
/// - Returns: `Toggle`
static func groupByFeed(_ preference: Binding) -> some View {
Toggle(isOn: preference) {
- Text("Group by Feed", comment: "Toggle: groups articles by feed when enabled.")
+ Text("toggle.title.group-by-feed", comment: "Group by Feed")
}
}
@@ -147,7 +147,7 @@ struct SettingsRow {
/// - Returns: `Toggle`
static func refreshToClearReadArticles(_ preference: Binding) -> some View {
Toggle(isOn: preference) {
- Text("Refresh to Clear Read Articles", comment: "Toggle: when enabled, articles will be cleared when the timeline is refreshed")
+ Text("toggle.title.refresh-to-clear-articles", comment: "Refresh to Clear Articles")
}
}
@@ -157,7 +157,7 @@ struct SettingsRow {
NavigationLink {
TimelineCustomizerView()
} label: {
- Text("Timeline Layout", comment: "Button: opens the timeline customiser")
+ Text("button.title.timeline-layout", comment: "Timeline Layout")
}
}
@@ -166,7 +166,7 @@ struct SettingsRow {
static var themeSelection: some View {
NavigationLink(destination: ArticleThemeManagerView()) {
HStack {
- Text("Article Theme", comment: "Button: opens the Article Theme manager view")
+ Text("button.title.artice-themes", comment: "Article Themes")
Spacer()
Text(ArticleThemesManager.shared.currentTheme.name)
.font(.callout)
@@ -180,7 +180,7 @@ struct SettingsRow {
/// - Returns: `Toggle`
static func confirmMarkAllAsRead(_ preference: Binding) -> some View {
Toggle(isOn: preference) {
- Text("Confirm Mark All as Read", comment: "Toggle: when enabled, the app will confirm whether to mark all items as read")
+ Text("toggle.title.confirm-mark-all-as-read", comment: "Confirm Mark All as Read")
}
}
@@ -189,7 +189,7 @@ struct SettingsRow {
/// - Returns: `Toggle`
static func openLinksInNetNewsWire(_ preference: Binding) -> some View {
Toggle(isOn: preference) {
- Text("Open Links in NetNewsWire", comment: "Toggle: when enabled, links will open in NetNewsWire")
+ Text("toggle.title.open-links-in-netnewswire", comment: "Open Links in NetNewsWire")
}
}
@@ -200,7 +200,7 @@ struct SettingsRow {
static func configureAppearance(_ isShown: Binding) -> some View {
NavigationLink(destination: DisplayAndBehaviorsView(), isActive: isShown) {
Label {
- Text("Display & Behaviours", comment: "Button: opens the Display and Appearance view.")
+ Text("button.title.display-and-behaviors", comment: "Display & Behaviors")
} icon: {
Image("app.appearance")
.resizable()
@@ -239,7 +239,7 @@ struct SettingsRow {
AboutView()
} label: {
Label {
- Text("About", comment: "Button: opens the NetNewsWire about view.")
+ Text("button.title.about", comment: "About")
} icon: {
Image(systemName: "info.circle.fill")
.resizable()
diff --git a/iOS/Settings/General/SettingsView.swift b/iOS/Settings/General/SettingsView.swift
index e0fc89d62..022f1c94b 100644
--- a/iOS/Settings/General/SettingsView.swift
+++ b/iOS/Settings/General/SettingsView.swift
@@ -24,18 +24,18 @@ struct SettingsView: View {
NavigationView {
List {
// Device Permissions
- Section(header: Text("Device Permissions", comment: "Settings: Device Permissions section header."),
- footer: Text("Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.", comment: "Settings: Device Permissions section footer.")) {
+ Section(header: Text("label.text.device-permissions", comment: "Device Permissions"),
+ footer: Text("label.text.device-permissions-explainer", comment: "Configure NetNewsWire's access to Siri, background app refresh, mobile data, and more.")) {
SettingsRow.openSystemSettings
}
// Account/Extensions/OPML Management
- Section(header: Text("Accounts & Extensions", comment: "Settings: Accounts and Extensions section header."),
- footer: Text("Add, delete, enable, or disable accounts and extensions.", comment: "Settings: Accounts and Extensions section footer.")) {
+ Section(header: Text("label.text.accounts-and-extensions", comment: "Settings: Accounts & Extensions section header."),
+ footer: Text("label.text.account-and-extensions-explainer", comment: "Add, delete, enable, or disable accounts and extensions.")) {
SettingsRow.addAccount
SettingsRow.manageExtensions
SettingsRow.importOPML(showImportActionSheet: $viewModel.showImportActionSheet)
- .confirmationDialog(Text("Choose an account to receive the imported feeds and folders", comment: "Import OPML confirmation title."),
+ .confirmationDialog(Text("actionsheet.title.choose-opml-destination", comment: "Choose an account to receive the imported feeds and folders"),
isPresented: $viewModel.showImportActionSheet,
titleVisibility: .visible) {
ForEach(AccountManager.shared.sortedActiveAccounts, id: \.self) { account in
@@ -47,7 +47,7 @@ struct SettingsView: View {
}
SettingsRow.exportOPML(showExportActionSheet: $viewModel.showExportActionSheet)
- .confirmationDialog(Text("Choose an account with the subscriptions to export", comment: "Export OPML confirmation title."),
+ .confirmationDialog(Text("actionsheet.title.choose-opml-export-account", comment: "Choose an account with the subscriptions to export"),
isPresented: $viewModel.showExportActionSheet,
titleVisibility: .visible) {
ForEach(AccountManager.shared.sortedAccounts, id: \.self) { account in
@@ -66,8 +66,8 @@ struct SettingsView: View {
}
// Appearance
- Section(header: Text("Appearance", comment: "Settings: Appearance section header."),
- footer: Text("Manage the look, feel, and behavior of NetNewsWire.", comment: "Settings: Appearance section footer.")) {
+ Section(header: Text("label.text.appearance", comment: "Settings: Appearance section header."),
+ footer: Text("label.text.appearance-explainer", comment: "Manage the look, feel, and behavior of NetNewsWire.")) {
SettingsRow.configureAppearance($isConfigureAppearanceShown)
if viewModel.notificationPermissions == .authorized {
SettingsRow.configureNewArticleNotifications
@@ -84,11 +84,11 @@ struct SettingsView: View {
}
.tint(Color(uiColor: AppAssets.primaryAccentColor))
.listStyle(.insetGrouped)
- .navigationTitle(Text("Settings", comment: "Navigation bar title for Settings."))
+ .navigationTitle(Text("navigation.title.settings", comment: "Settings"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading, content: {
- Button(action: { dismiss() }, label: { Text("Done", comment: "Button title") })
+ Button(action: { dismiss() }, label: { Text("button.title.done", comment: "Done") })
})
}
.sheet(isPresented: $viewModel.showAddAccountView) {
@@ -143,18 +143,18 @@ struct SettingsView: View {
viewModel.showImportExportError = true
}
})
- .alert(Text("Imported Successfully", comment: "Alert title: imported OPML file successfully."),
+ .alert(Text("alert.title.opml.opml-import-success", comment: "Alert title: Imported Successfully"),
isPresented: $viewModel.showImportSuccess,
actions: {},
- message: { Text("Subscriptions have been imported to your \(viewModel.importAccount?.nameForDisplay ?? "") account.", comment: "Alert message: imported OPML file successfully.") })
- .alert(Text("Exported Successfully", comment: "Alert title: exported OPML file successfully."),
+ message: { Text("alert.message.opml-import-success.%@", comment: "Subscriptions have been imported to your “%@“ account.") })
+ .alert(Text("alert.title.opml.opml-export-success", comment: "Alert title: Exported Successfully"),
isPresented: $viewModel.showExportSuccess,
actions: {},
- message: { Text("Your OPML file has been successfully exported.", comment: "Alert message: exported OPML file successfully.") })
- .alert(Text("Error", comment: "Alert title: Error"),
+ message: { Text("alert.message.opml.opml-export-success", comment: "Your subscriptions have been exported successfully.") })
+ .alert(Text("alert.title.error", comment: "Error"),
isPresented: $viewModel.showImportExportError,
actions: {},
- message: { Text(viewModel.importExportError?.localizedDescription ?? "Import/Export Error") } )
+ message: { Text(verbatim: viewModel.importExportError?.localizedDescription ?? "") } )
}.navigationViewStyle(.stack)
}
}
diff --git a/iOS/Settings/Help/AboutView.swift b/iOS/Settings/Help/AboutView.swift
index 099b72792..fdd7308ae 100644
--- a/iOS/Settings/Help/AboutView.swift
+++ b/iOS/Settings/Help/AboutView.swift
@@ -14,21 +14,21 @@ struct AboutView: View, LoadableAboutData {
var body: some View {
List {
Section(header: aboutHeaderView) {}
- Section(header: Text("Primary Contributors", comment: "About: Primary Contributors section header")) {
+ Section(header: Text("label.text.primary-contributors", comment: "Primary Contributors")) {
ForEach(0.. Void)? = nil) {
if let decodingError = error as? DecodingError {
- let errorTitle = NSLocalizedString("Error", comment: "Error")
+ let errorTitle = NSLocalizedString("alert.title.error", comment: "Error")
var informativeText: String = ""
switch decodingError {
case .typeMismatch(let type, _):
- let localizedError = NSLocalizedString("This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist", comment: "Type mismatch")
- informativeText = NSString.localizedStringWithFormat(localizedError as NSString, type as! CVarArg) as String
+ let localizedError = NSLocalizedString("alert.message.theme-type-mismatch.%@", comment: "Error message when a type is mismatched. In English, the message is: This theme cannot be used because the the type—“%@”—is mismatched in the Info.plist.")
+ informativeText = String.localizedStringWithFormat(localizedError, type as! CVarArg)
presentError(title: errorTitle, message: informativeText, dismiss: dismiss)
case .valueNotFound(let value, _):
- let localizedError = NSLocalizedString("This theme cannot be used because the the value—“%@”—is not found in the Info.plist.", comment: "Decoding value missing")
- informativeText = NSString.localizedStringWithFormat(localizedError as NSString, value as! CVarArg) as String
+ let localizedError = NSLocalizedString("alert.message.theme-value-missing.%@", comment: "Error message when a value is missing. In English, the message is: This theme cannot be used because the the value—“%@”—is not found in the Info.plist.")
+ informativeText = String.localizedStringWithFormat(localizedError, value as! CVarArg)
presentError(title: errorTitle, message: informativeText, dismiss: dismiss)
case .keyNotFound(let codingKey, _):
- let localizedError = NSLocalizedString("This theme cannot be used because the the key—“%@”—is not found in the Info.plist.", comment: "Decoding key missing")
- informativeText = NSString.localizedStringWithFormat(localizedError as NSString, codingKey.stringValue) as String
+ let localizedError = NSLocalizedString("alert.message.theme-key-missing.%@", comment: "Error message when a key is missing. In English, the message is: This theme cannot be used because the the key—“%@”—is not found in the Info.plist.")
+ informativeText = String.localizedStringWithFormat(localizedError, codingKey.stringValue)
presentError(title: errorTitle, message: informativeText, dismiss: dismiss)
case .dataCorrupted(let context):
guard let error = context.underlyingError as NSError?,
@@ -36,7 +36,7 @@ extension UIViewController {
presentError(title: errorTitle, message: informativeText, dismiss: dismiss)
return
}
- let localizedError = NSLocalizedString("This theme cannot be used because of data corruption in the Info.plist. %@.", comment: "Decoding key missing")
+ let localizedError = NSLocalizedString("alert.message.theme-data-corrupted.%@", comment: "Error message when theme data is corrupted. The variable is a description provided by Apple. In English, the message is: This theme cannot be used because of data corruption in the Info.plist. %@.")
informativeText = NSString.localizedStringWithFormat(localizedError as NSString, debugDescription) as String
presentError(title: errorTitle, message: informativeText, dismiss: dismiss)
@@ -44,8 +44,10 @@ extension UIViewController {
informativeText = error.localizedDescription
presentError(title: errorTitle, message: informativeText, dismiss: dismiss)
}
+ } else if let accountError = error as? AccountError {
+ presentError(title: accountError.errorTitle, message: accountError.localizedDescription)
} else {
- let errorTitle = NSLocalizedString("Error", comment: "Error")
+ let errorTitle = NSLocalizedString("alert.title.error", comment: "Error")
presentError(title: errorTitle, message: error.localizedDescription, dismiss: dismiss)
}
}
diff --git a/iOS/en-GB.lproj/LaunchScreenPad.strings b/iOS/en-GB.lproj/LaunchScreenPad.strings
new file mode 100644
index 000000000..e3c590d0c
--- /dev/null
+++ b/iOS/en-GB.lproj/LaunchScreenPad.strings
@@ -0,0 +1,6 @@
+/* Class = "UIBarButtonItem"; title = "Item"; ObjectID = "AK3-N5-4ke"; Note = "Settings bar button item"; */
+"AK3-N5-4ke.title" = "Item";
+
+/* Class = "UINavigationItem"; title = "Feeds"; ObjectID = "lE1-xw-gjH"; Note = "Navigation bar title: Feeds"; */
+"lE1-xw-gjH.title" = "Feeds";
+
diff --git a/iOS/en-GB.lproj/LaunchScreenPhone.strings b/iOS/en-GB.lproj/LaunchScreenPhone.strings
new file mode 100644
index 000000000..18b547e05
--- /dev/null
+++ b/iOS/en-GB.lproj/LaunchScreenPhone.strings
@@ -0,0 +1,6 @@
+/* Class = "UIBarButtonItem"; title = "Item"; ObjectID = "AK3-N5-4ke"; Note = "Bar button item: Settings"; */
+"AK3-N5-4ke.title" = "Item";
+
+/* Class = "UINavigationItem"; title = "Feeds"; ObjectID = "lE1-xw-gjH"; Note = "Navigation bar title: Feeds"; */
+"lE1-xw-gjH.title" = "Feeds";
+
diff --git a/iOS/en-GB.lproj/Main.strings b/iOS/en-GB.lproj/Main.strings
new file mode 100644
index 000000000..f8a53b174
--- /dev/null
+++ b/iOS/en-GB.lproj/Main.strings
@@ -0,0 +1,42 @@
+/* Class = "UILabel"; text = "Label"; ObjectID = "0Hz-Dv-MhU"; Note = "This does not need to be localized as it is provided by feed data."; */
+"0Hz-Dv-MhU.text" = "Label";
+
+/* Class = "UIBarButtonItem"; title = "Next Article"; ObjectID = "2qz-M5-Yhk"; Note = "Button title: Next Article"; */
+"2qz-M5-Yhk.title" = "Next Article";
+
+/* Class = "UIBarButtonItem"; title = "Next Unread"; ObjectID = "2w5-e9-C2V"; Note = "Button title: Next Unread"; */
+"2w5-e9-C2V.title" = "Next Unread";
+
+/* Class = "UILabel"; text = "Blog Author"; ObjectID = "7GV-PV-YVq"; Note = "This does not need to be localized as it is provided by feed data."; */
+"7GV-PV-YVq.text" = "Blog Author";
+
+/* Class = "UIBarButtonItem"; title = "Item"; ObjectID = "fTv-eX-72r"; Note = "Mark All as Read button title."; */
+"fTv-eX-72r.title" = "Item";
+
+/* Class = "UIBarButtonItem"; title = "Toggle Read"; ObjectID = "hy0-LS-MzE"; Note = "Button title: Toggle Read"; */
+"hy0-LS-MzE.title" = "Toggle Read";
+
+/* Class = "UILabel"; text = "Article Title"; ObjectID = "iFp-rn-HhQ"; Note = "This does not need to be localized as it is provided by feed data."; */
+"iFp-rn-HhQ.text" = "Article Title";
+
+/* Class = "UIViewController"; title = "Detail"; ObjectID = "JEX-9P-axG"; */
+"JEX-9P-axG.title" = "Detail";
+
+/* Class = "UIBarButtonItem"; title = "Settings"; ObjectID = "TlU-Pg-ATe"; Note = "Settings bar button item."; */
+"TlU-Pg-ATe.title" = "Settings";
+
+/* Class = "UIBarButtonItem"; title = "Previous Article"; ObjectID = "v4j-fq-23N"; Note = "Button title: Previous Article"; */
+"v4j-fq-23N.title" = "Previous Article";
+
+/* Class = "UINavigationItem"; title = "Timeline"; ObjectID = "wcC-1L-ug4"; Note = "Title used in the navigation bar for the Timeline."; */
+"wcC-1L-ug4.title" = "Timeline";
+
+/* Class = "UIBarButtonItem"; title = "Toggle Starred"; ObjectID = "wU4-eH-wC9"; Note = "Button title: Toggle Starred"; */
+"wU4-eH-wC9.title" = "Toggle Starred";
+
+/* Class = "UILabel"; text = "Blog Name"; ObjectID = "YsT-Lt-Zry"; Note = "This does not need to be localized as it is provided by feed data."; */
+"YsT-Lt-Zry.text" = "Blog Name";
+
+/* Class = "UINavigationItem"; title = "Feeds"; ObjectID = "Zdf-7t-Un8"; Note = "Title used in the navigation bar for the Feeds list."; */
+"Zdf-7t-Un8.title" = "Feeds";
+
diff --git a/iOS/en.lproj/LaunchScreenPad.strings b/iOS/en.lproj/LaunchScreenPad.strings
new file mode 100644
index 000000000..43cbd2c8d
--- /dev/null
+++ b/iOS/en.lproj/LaunchScreenPad.strings
@@ -0,0 +1,6 @@
+
+/* Class = "UIBarButtonItem"; title = "Item"; ObjectID = "AK3-N5-4ke"; Note = "Settings bar button item"; */
+"AK3-N5-4ke.title" = "Item";
+
+/* Class = "UINavigationItem"; title = "Feeds"; ObjectID = "lE1-xw-gjH"; Note = "Navigation bar title: Feeds"; */
+"lE1-xw-gjH.title" = "Feeds";
diff --git a/iOS/en.lproj/LaunchScreenPhone.strings b/iOS/en.lproj/LaunchScreenPhone.strings
new file mode 100644
index 000000000..f72cda50e
--- /dev/null
+++ b/iOS/en.lproj/LaunchScreenPhone.strings
@@ -0,0 +1,6 @@
+
+/* Class = "UIBarButtonItem"; title = "Item"; ObjectID = "AK3-N5-4ke"; Note = "Bar button item: Settings"; */
+"AK3-N5-4ke.title" = "Item";
+
+/* Class = "UINavigationItem"; title = "Feeds"; ObjectID = "lE1-xw-gjH"; Note = "Navigation bar title: Feeds"; */
+"lE1-xw-gjH.title" = "Feeds";
diff --git a/iOS/en.lproj/Main.strings b/iOS/en.lproj/Main.strings
new file mode 100644
index 000000000..1e26ef220
--- /dev/null
+++ b/iOS/en.lproj/Main.strings
@@ -0,0 +1,42 @@
+
+/* Class = "UILabel"; text = "Label"; ObjectID = "0Hz-Dv-MhU"; Note = "This does not need to be localized as it is provided by feed data."; */
+"0Hz-Dv-MhU.text" = "Label";
+
+/* Class = "UIBarButtonItem"; title = "Next Article"; ObjectID = "2qz-M5-Yhk"; Note = "Button title: Next Article"; */
+"2qz-M5-Yhk.title" = "Next Article";
+
+/* Class = "UIBarButtonItem"; title = "Next Unread"; ObjectID = "2w5-e9-C2V"; Note = "Button title: Next Unread"; */
+"2w5-e9-C2V.title" = "Next Unread";
+
+/* Class = "UILabel"; text = "Blog Author"; ObjectID = "7GV-PV-YVq"; Note = "This does not need to be localized as it is provided by feed data."; */
+"7GV-PV-YVq.text" = "Blog Author";
+
+/* Class = "UIViewController"; title = "Detail"; ObjectID = "JEX-9P-axG"; */
+"JEX-9P-axG.title" = "Detail";
+
+/* Class = "UIBarButtonItem"; title = "Settings"; ObjectID = "TlU-Pg-ATe"; Note = "Settings bar button item."; */
+"TlU-Pg-ATe.title" = "Settings";
+
+/* Class = "UILabel"; text = "Blog Name"; ObjectID = "YsT-Lt-Zry"; Note = "This does not need to be localized as it is provided by feed data."; */
+"YsT-Lt-Zry.text" = "Blog Name";
+
+/* Class = "UINavigationItem"; title = "Feeds"; ObjectID = "Zdf-7t-Un8"; Note = "Title used in the navigation bar for the Feeds list."; */
+"Zdf-7t-Un8.title" = "Feeds";
+
+/* Class = "UIBarButtonItem"; title = "Item"; ObjectID = "fTv-eX-72r"; Note = "Mark All as Read button title."; */
+"fTv-eX-72r.title" = "Item";
+
+/* Class = "UIBarButtonItem"; title = "Toggle Read"; ObjectID = "hy0-LS-MzE"; Note = "Button title: Toggle Read"; */
+"hy0-LS-MzE.title" = "Toggle Read";
+
+/* Class = "UILabel"; text = "Article Title"; ObjectID = "iFp-rn-HhQ"; Note = "This does not need to be localized as it is provided by feed data."; */
+"iFp-rn-HhQ.text" = "Article Title";
+
+/* Class = "UIBarButtonItem"; title = "Previous Article"; ObjectID = "v4j-fq-23N"; Note = "Button title: Previous Article"; */
+"v4j-fq-23N.title" = "Previous Article";
+
+/* Class = "UIBarButtonItem"; title = "Toggle Starred"; ObjectID = "wU4-eH-wC9"; Note = "Button title: Toggle Starred"; */
+"wU4-eH-wC9.title" = "Toggle Starred";
+
+/* Class = "UINavigationItem"; title = "Timeline"; ObjectID = "wcC-1L-ug4"; Note = "Title used in the navigation bar for the Timeline."; */
+"wcC-1L-ug4.title" = "Timeline";
diff --git a/xcconfig/NetNewsWire_project.xcconfig b/xcconfig/NetNewsWire_project.xcconfig
index 67951fe33..40588ba58 100644
--- a/xcconfig/NetNewsWire_project.xcconfig
+++ b/xcconfig/NetNewsWire_project.xcconfig
@@ -1,6 +1,7 @@
#include? "../../SharedXcodeSettings/ProjectSettings.xcconfig"
ALWAYS_SEARCH_USER_PATHS = NO
+CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES
CLANG_ANALYZER_NONNULL = YES
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = gnu++0x
diff --git a/xcconfig/NetNewsWire_project_debug.xcconfig b/xcconfig/NetNewsWire_project_debug.xcconfig
index bc5e7dc11..4e1acc843 100644
--- a/xcconfig/NetNewsWire_project_debug.xcconfig
+++ b/xcconfig/NetNewsWire_project_debug.xcconfig
@@ -10,4 +10,4 @@ ONLY_ACTIVE_ARCH = YES
OTHER_SWIFT_FLAGS = -DDEBUG
SWIFT_OPTIMIZATION_LEVEL = -Onone
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG
-
+CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES
diff --git a/xcconfig/NetNewsWire_project_release.xcconfig b/xcconfig/NetNewsWire_project_release.xcconfig
index f4d9e6289..2748dbb53 100644
--- a/xcconfig/NetNewsWire_project_release.xcconfig
+++ b/xcconfig/NetNewsWire_project_release.xcconfig
@@ -8,3 +8,4 @@ MTL_ENABLE_DEBUG_INFO = NO
OTHER_SWIFT_FLAGS = -DRELEASE
SWIFT_OPTIMIZATION_LEVEL = -Owholemodule
VALIDATE_PRODUCT = YES
+CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES