mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1713 from omnivore-app/chore/strings-refs
String updates + unit test for syntax
This commit is contained in:
commit
4c71332aed
45 changed files with 234 additions and 158 deletions
|
|
@ -34,6 +34,41 @@
|
|||
"identifier" : "042F48D826DFD10E00BF98FC",
|
||||
"name" : "UITests-iOS"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target" : {
|
||||
"containerPath" : "container:OmnivoreKit",
|
||||
"identifier" : "AppTests",
|
||||
"name" : "AppTests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target" : {
|
||||
"containerPath" : "container:OmnivoreKit",
|
||||
"identifier" : "ModelsTests",
|
||||
"name" : "ModelsTests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target" : {
|
||||
"containerPath" : "container:OmnivoreKit",
|
||||
"identifier" : "ServicesTests",
|
||||
"name" : "ServicesTests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target" : {
|
||||
"containerPath" : "container:OmnivoreKit",
|
||||
"identifier" : "UtilsTests",
|
||||
"name" : "UtilsTests"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target" : {
|
||||
"containerPath" : "container:OmnivoreKit",
|
||||
"identifier" : "ViewsTests",
|
||||
"name" : "ViewsTests"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 1
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ public struct ShareExtensionView: View {
|
|||
.padding(.trailing, 8)
|
||||
|
||||
VStack {
|
||||
Text("Highlight")
|
||||
Text(LocalText.genericHighlight)
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(Color.appGrayTextContrast)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
|
@ -355,7 +355,7 @@ public struct ShareExtensionView: View {
|
|||
case .editingLabels:
|
||||
return LocalText.labelsGeneric
|
||||
case .viewingHighlight:
|
||||
return "Highlight"
|
||||
return LocalText.genericHighlight
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
@ -479,7 +479,7 @@ public struct ShareExtensionView: View {
|
|||
}
|
||||
.alert("Before saving an article select text in Safari to create a highlight on save.",
|
||||
isPresented: $showHighlightInstructionAlert) {
|
||||
Button("Ok", role: .cancel) { showHighlightInstructionAlert = false }
|
||||
Button(LocalText.genericOk, role: .cancel) { showHighlightInstructionAlert = false }
|
||||
}
|
||||
.task {
|
||||
await labelsViewModel.loadLabelsFromStore(dataService: viewModel.services.dataService)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import Views
|
|||
storedFontSize = max(storedFontSize - 2, 10)
|
||||
NSNotification.readerSettingsChanged()
|
||||
},
|
||||
label: { Text("Decrease Font Size")
|
||||
label: { Text(LocalText.keyboardCommandDecreaseFont)
|
||||
}
|
||||
)
|
||||
.keyboardShortcut("-")
|
||||
|
|
@ -30,7 +30,7 @@ import Views
|
|||
storedFontSize = min(storedFontSize + 2, 28)
|
||||
NSNotification.readerSettingsChanged()
|
||||
},
|
||||
label: { Text("Increase Font Size") }
|
||||
label: { Text(LocalText.keyboardCommandIncreaseFont) }
|
||||
)
|
||||
.keyboardShortcut("+")
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ import Views
|
|||
storedMaxWidthPercentage = min(storedMaxWidthPercentage + 10, 100)
|
||||
NSNotification.readerSettingsChanged()
|
||||
},
|
||||
label: { Text("Decrease Margin")
|
||||
label: { Text(LocalText.keyboardCommandDecreaseMargin)
|
||||
}
|
||||
)
|
||||
.keyboardShortcut("[")
|
||||
|
|
@ -53,7 +53,7 @@ import Views
|
|||
storedMaxWidthPercentage = max(storedMaxWidthPercentage - 10, 40)
|
||||
NSNotification.readerSettingsChanged()
|
||||
},
|
||||
label: { Text("Increase Margin")
|
||||
label: { Text(LocalText.keyboardCommandIncreaseMargin)
|
||||
}
|
||||
)
|
||||
.keyboardShortcut("]")
|
||||
|
|
@ -67,7 +67,7 @@ import Views
|
|||
storedLineSpacing = max(storedLineSpacing - 25, 100)
|
||||
NSNotification.readerSettingsChanged()
|
||||
},
|
||||
label: { Text("Decrease Line Spacing") }
|
||||
label: { Text(LocalText.keyboardCommandDecreaseLineSpacing) }
|
||||
)
|
||||
.keyboardShortcut("k")
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ import Views
|
|||
storedLineSpacing = min(storedLineSpacing + 25, 300)
|
||||
NSNotification.readerSettingsChanged()
|
||||
},
|
||||
label: { Text("Increase Line Spacing") }
|
||||
label: { Text(LocalText.keyboardCommandIncreaseLineSpacing) }
|
||||
)
|
||||
.keyboardShortcut("l")
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ import Views
|
|||
|
||||
Divider()
|
||||
|
||||
Picker(selection: $preferredFont, label: Text("Font Family")) {
|
||||
Picker(selection: $preferredFont, label: Text(LocalText.genericFontFamily)) {
|
||||
ForEach(WebFont.allCases, id: \.self) { font in
|
||||
Text(font.displayValue).tag(font.rawValue)
|
||||
}
|
||||
|
|
@ -112,7 +112,7 @@ import Views
|
|||
|
||||
Toggle(
|
||||
isOn: $prefersHighContrastText,
|
||||
label: { Text("High Contrast Text") }
|
||||
label: { Text(LocalText.genericHighContrastText) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Utils
|
|||
import PSPDFKit
|
||||
import PSPDFKitUI
|
||||
import Services
|
||||
import Views
|
||||
|
||||
struct PDFViewer: View {
|
||||
enum SettingsKeys: String {
|
||||
|
|
@ -134,7 +135,7 @@ import Utils
|
|||
.onShouldShowMenuItemsForSelectedText(perform: { pageView, menuItems, selectedText in
|
||||
let copy = menuItems.first(where: { $0.identifier == "Copy" })
|
||||
let define = menuItems.first(where: { $0.identifier == "Define" })
|
||||
let highlight = MenuItem(title: "Highlight", block: {
|
||||
let highlight = MenuItem(title: LocalText.genericHighlight, block: {
|
||||
_ = coordinator.highlightSelection(
|
||||
pageView: pageView,
|
||||
selectedText: selectedText,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
|
|||
case .feed:
|
||||
return "Home"
|
||||
case .profile:
|
||||
return "Profile"
|
||||
return LocalText.genericProfile
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ struct DebugMenuView: View {
|
|||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Debug Menu")
|
||||
Text(LocalText.menuDebugTitle)
|
||||
.font(.appTitle)
|
||||
Form {
|
||||
Text("API Environment:")
|
||||
Picker(selection: $selectedEnvironment, label: Text("API Environment:")) {
|
||||
Text(LocalText.menuDebugApiEnv)
|
||||
Picker(selection: $selectedEnvironment, label: Text(LocalText.menuDebugApiEnv)) {
|
||||
ForEach(appEnvironments, id: \.self) {
|
||||
Text($0.rawValue)
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ struct DebugMenuView: View {
|
|||
authenticator.logout(dataService: dataService)
|
||||
dataService.switchAppEnvironment(appEnvironment: selectedEnvironment)
|
||||
},
|
||||
label: { Text("Apply Changes") }
|
||||
label: { Text(LocalText.genericChangeApply) }
|
||||
)
|
||||
.buttonStyle(SolidCapsuleButtonStyle(width: 220))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ struct HighlightListItemParams: Identifiable {
|
|||
highlightItems = highlights.map {
|
||||
HighlightListItemParams(
|
||||
highlightID: $0.unwrappedID,
|
||||
title: "Highlight",
|
||||
title: LocalText.genericHighlight,
|
||||
annotation: $0.annotation ?? "",
|
||||
quote: $0.quote ?? "",
|
||||
labels: $0.labels.asArray(of: LinkedItemLabel.self),
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ import Views
|
|||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
Label { Text(LocalText.genericSnooze) } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
if let author = item.author {
|
||||
|
|
@ -401,7 +401,7 @@ import Views
|
|||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
Label { Text(LocalText.genericSnooze) } icon: { Image.moon }
|
||||
}.tint(.appYellow48)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ import Views
|
|||
viewModel.itemToSnoozeID = item.id
|
||||
viewModel.snoozePresented = true
|
||||
} label: {
|
||||
Label { Text("Snooze") } icon: { Image.moon }
|
||||
Label { Text(LocalText.genericSnooze) } icon: { Image.moon }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@
|
|||
}
|
||||
listBody
|
||||
.navigationTitle("Search")
|
||||
.navigationBarItems(trailing: Button(action: { dismiss() }, label: { Text("Close") }))
|
||||
.navigationBarItems(trailing: Button(action: { dismiss() }, label: { Text(LocalText.genericClose) }))
|
||||
.navigationBarTitleDisplayMode(NavigationBarItem.TitleDisplayMode.inline)
|
||||
.searchable(text: $viewModel.searchTerm, placement: .navigationBarDrawer(displayMode: .always)) {
|
||||
ForEach(viewModel.items) { item in
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ struct ApplyLabelsView: View {
|
|||
var confirmButtonText: String {
|
||||
switch self {
|
||||
case .item, .highlight:
|
||||
return "Save"
|
||||
return LocalText.genericSave
|
||||
case .list:
|
||||
return "Done"
|
||||
return LocalText.doneGeneric
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ struct CreateLabelView: View {
|
|||
description: nil
|
||||
)
|
||||
},
|
||||
label: { Text("Create").foregroundColor(.appGrayTextContrast) }
|
||||
label: { Text(LocalText.genericCreate).foregroundColor(.appGrayTextContrast) }
|
||||
)
|
||||
.opacity(shouldDisableCreateButton ? 0.2 : 1)
|
||||
.disabled(shouldDisableCreateButton)
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ struct LinkItemDetailView: View {
|
|||
} else {
|
||||
HStack(alignment: .center) {
|
||||
Spacer()
|
||||
Text("Loading...")
|
||||
Text(LocalText.genericLoading)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ struct LinkedItemMetadataEditView: View {
|
|||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .center, spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Title")
|
||||
Text(LocalText.genericTitle)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
TextField("", text: $viewModel.title)
|
||||
|
|
@ -44,7 +44,7 @@ struct LinkedItemMetadataEditView: View {
|
|||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Author")
|
||||
Text(LocalText.genericAuthor)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
TextField("", text: $viewModel.author)
|
||||
|
|
@ -52,7 +52,7 @@ struct LinkedItemMetadataEditView: View {
|
|||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Description")
|
||||
Text(LocalText.genericDescription)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
TextEditor(text: $viewModel.description)
|
||||
|
|
@ -98,7 +98,7 @@ struct LinkedItemMetadataEditView: View {
|
|||
viewModel.submit(dataService: dataService, item: item)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
},
|
||||
label: { Text("Save").foregroundColor(.appGrayTextContrast) }
|
||||
label: { Text(LocalText.genericSave).foregroundColor(.appGrayTextContrast) }
|
||||
)
|
||||
}
|
||||
ToolbarItem(placement: .barLeading) {
|
||||
|
|
@ -120,7 +120,7 @@ struct LinkedItemMetadataEditView: View {
|
|||
viewModel.submit(dataService: dataService, item: item)
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
},
|
||||
label: { Text("Save").foregroundColor(.appGrayTextContrast) }
|
||||
label: { Text(LocalText.genericSave).foregroundColor(.appGrayTextContrast) }
|
||||
)
|
||||
|
||||
Button(
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public struct PrimaryContentView: View {
|
|||
PrimaryContentCategory.feed.destinationView
|
||||
|
||||
// Third column is the detail view
|
||||
Text("Select a link from the feed")
|
||||
Text(LocalText.navigationSelectLink)
|
||||
}
|
||||
.accentColor(.appGrayTextContrast)
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ struct PrimaryContentSidebar: View {
|
|||
#selector(NSSplitViewController.toggleSidebar(_:)), with: nil
|
||||
)
|
||||
},
|
||||
label: { Label("Toggle sidebar", systemImage: "sidebar.left") }
|
||||
label: { Label(LocalText.navigationSelectSidebarToggle, systemImage: "sidebar.left") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,18 +35,18 @@ struct ManageAccountView: View {
|
|||
action: {
|
||||
showDeleteAccountConfirmation = true
|
||||
},
|
||||
label: { Text("Delete Account") }
|
||||
label: { Text(LocalText.manageAccountDelete) }
|
||||
)
|
||||
Button(
|
||||
action: {
|
||||
dataService.resetCoreData()
|
||||
},
|
||||
label: { Text("Reset Data Cache") }
|
||||
label: { Text(LocalText.manageAccountResetCache) }
|
||||
)
|
||||
.alert(isPresented: $showDeleteAccountConfirmation) {
|
||||
Alert(
|
||||
title: Text("Are you sure you want to delete your account? This action can't be undone."),
|
||||
primaryButton: .destructive(Text("Delete Account")) {
|
||||
title: Text(LocalText.manageAccountConfirmDeleteMessage),
|
||||
primaryButton: .destructive(Text(LocalText.manageAccountDelete)) {
|
||||
Task {
|
||||
await viewModel.deleteAccount(dataService: dataService, authenticator: authenticator)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ struct NewsletterEmailsView: View {
|
|||
}
|
||||
|
||||
if !viewModel.emails.isEmpty {
|
||||
Section(header: Text("Existing Emails (Tap to copy)")) {
|
||||
Section(header: Text(LocalText.newsletterEmailsExisting)) {
|
||||
ForEach(viewModel.emails) { newsletterEmail in
|
||||
Button(
|
||||
action: {
|
||||
|
|
|
|||
|
|
@ -152,8 +152,8 @@ struct ProfileView: View {
|
|||
}
|
||||
.alert(isPresented: $showLogoutConfirmation) {
|
||||
Alert(
|
||||
title: Text("Are you sure you want to logout?"),
|
||||
primaryButton: .destructive(Text("Confirm")) {
|
||||
title: Text(LocalText.profileConfirmLogoutMessage),
|
||||
primaryButton: .destructive(Text(LocalText.genericConfirm)) {
|
||||
authenticator.logout(dataService: dataService)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
|
|
@ -161,7 +161,7 @@ struct ProfileView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Profile")
|
||||
.navigationTitle(LocalText.genericProfile)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ struct PushNotificationDevicesView: View {
|
|||
|
||||
private var innerBody: some View {
|
||||
List {
|
||||
Section(header: Text("Registered device tokens (swipe to remove)")) {
|
||||
Section(header: Text(LocalText.devicesTokensTitle)) {
|
||||
ForEach(viewModel.devices) { device in
|
||||
Text("Created: \(createdStr(device))")
|
||||
Text("\(LocalText.devicesCreated)\(createdStr(device))")
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button(
|
||||
role: .destructive,
|
||||
|
|
|
|||
|
|
@ -69,19 +69,13 @@ struct PushNotificationSettingsView: View {
|
|||
private var innerBody: some View {
|
||||
Group {
|
||||
Section {
|
||||
Toggle(isOn: $viewModel.desiredNotificationsEnabled, label: { Text("Notifications Enabled") })
|
||||
Toggle(isOn: $viewModel.desiredNotificationsEnabled, label: { Text(LocalText.notificationsEnabled) })
|
||||
}.onChange(of: viewModel.desiredNotificationsEnabled) { _ in
|
||||
viewModel.tryUpdateToDesired(dataService: dataService)
|
||||
}
|
||||
|
||||
Section {
|
||||
Text("""
|
||||
Enabling push notifications gives Omnivore device permission to send notifications, \
|
||||
but you are in charge of which notifications are sent.
|
||||
|
||||
Push notifications are triggered using your \
|
||||
[account rules](https://omnivore.app/settings/rules) which you can edit online.
|
||||
""")
|
||||
Section { // TODO: double check this text
|
||||
Text("\(LocalText.notificationsExplainer)\n\(LocalText.notificationsTriggerExplainer)")
|
||||
.accentColor(.blue)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ struct RecommendationGroupView: View {
|
|||
if let shareLink = URL(string: viewModel.recommendationGroup.inviteUrl) {
|
||||
return AnyView(ShareSheet(activityItems: [shareLink]))
|
||||
} else {
|
||||
return AnyView(Text("Error copying invite URL"))
|
||||
return AnyView(Text(LocalText.clubsErrorCopying))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,9 +127,9 @@ struct RecommendationGroupView: View {
|
|||
|
||||
private var membersSection: some View {
|
||||
Section("Members") {
|
||||
if !viewModel.recommendationGroup.canSeeMembers {
|
||||
if !viewModel.recommendationGroup.canSeeMembers { // TODO: might need to fix text here
|
||||
Text("""
|
||||
The admin of this club does not allow viewing all members.
|
||||
\(LocalText.clubsAdminDenyViewing)
|
||||
|
||||
[Learn more about clubs](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc)
|
||||
""")
|
||||
|
|
@ -142,10 +142,9 @@ struct RecommendationGroupView: View {
|
|||
imageURL: member.profileImageURL != nil ? URL(string: member.profileImageURL!) : nil
|
||||
))
|
||||
}
|
||||
} else {
|
||||
} else { // TODO: fix link text to use translation
|
||||
Text("""
|
||||
This club does not have any members. Add users to your club by sending
|
||||
them the invite link.
|
||||
\(LocalText.clubsNoMembers)
|
||||
|
||||
[Learn more about clubs](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc)
|
||||
""")
|
||||
|
|
@ -160,13 +159,13 @@ struct RecommendationGroupView: View {
|
|||
}
|
||||
return AnyView(Button(action: {
|
||||
viewModel.showLeaveGroup = true
|
||||
}, label: { Text("Leave Club") })
|
||||
}, label: { Text(LocalText.clubsLeave) })
|
||||
.accentColor(.red))
|
||||
}
|
||||
|
||||
private var innerBody: some View {
|
||||
Group {
|
||||
Section("Name") {
|
||||
Section(LocalText.genericName) {
|
||||
Text(viewModel.recommendationGroup.name)
|
||||
}
|
||||
|
||||
|
|
@ -196,8 +195,8 @@ struct RecommendationGroupView: View {
|
|||
}
|
||||
.alert(isPresented: $viewModel.showLeaveGroup) {
|
||||
Alert(
|
||||
title: Text("Are you sure you want to leave this club? No data will be deleted, but you will stop receiving recommendations from the club."),
|
||||
primaryButton: .destructive(Text("Leave Club")) {
|
||||
title: Text(LocalText.clubsLeaveConfirm),
|
||||
primaryButton: .destructive(Text(LocalText.clubsLeave)) {
|
||||
Task {
|
||||
let success = await viewModel.leaveGroup(dataService: dataService)
|
||||
if success {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ struct CreateRecommendationGroupView: View {
|
|||
await viewModel.createGroup(dataService: dataService, name: self.name)
|
||||
}
|
||||
}, label: {
|
||||
Text("Next")
|
||||
Text(LocalText.genericNext)
|
||||
})
|
||||
.disabled(name.isEmpty)
|
||||
)
|
||||
|
|
@ -72,7 +72,7 @@ struct CreateRecommendationGroupView: View {
|
|||
var body: some View {
|
||||
NavigationView {
|
||||
Form {
|
||||
TextField("Name", text: $name, prompt: Text("Club Name"))
|
||||
TextField(LocalText.genericName, text: $name, prompt: Text(LocalText.clubsName))
|
||||
|
||||
Section("Club Rules") {
|
||||
Toggle("Only admins can post", isOn: $viewModel.onlyAdminCanPost)
|
||||
|
|
@ -91,7 +91,7 @@ struct CreateRecommendationGroupView: View {
|
|||
.alert(isPresented: $viewModel.showCreateError) {
|
||||
Alert(
|
||||
title: Text(viewModel.createGroupError ?? "Error creating group"),
|
||||
dismissButton: .cancel(Text("Ok")) {
|
||||
dismissButton: .cancel(Text(LocalText.genericOk)) {
|
||||
viewModel.createGroupError = nil
|
||||
viewModel.showCreateError = false
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ struct GroupsView: View {
|
|||
label: {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill").foregroundColor(.green)
|
||||
Text("Create a new club")
|
||||
Text(LocalText.clubsCreate)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
|
@ -152,7 +152,7 @@ struct GroupsView: View {
|
|||
|
||||
if !viewModel.isLoading {
|
||||
if viewModel.recommendationGroups.count > 0 {
|
||||
Section(header: Text("Your clubs")) {
|
||||
Section(header: Text(LocalText.clubsYours)) {
|
||||
ForEach(viewModel.recommendationGroups) { recommendationGroup in
|
||||
let vm = RecommendationsGroupViewModel(recommendationGroup: recommendationGroup)
|
||||
NavigationLink(
|
||||
|
|
@ -164,15 +164,7 @@ struct GroupsView: View {
|
|||
}
|
||||
} else {
|
||||
Section {
|
||||
Text("""
|
||||
You are not a member of any clubs.
|
||||
Create a new club and send the invite link to your friends get started.
|
||||
|
||||
During the beta you are limited to creating three clubs, and each club
|
||||
can have a maximum of twelve users.
|
||||
|
||||
[Learn more about clubs](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc)
|
||||
""")
|
||||
Text(LocalText.clubsNotAMemberMessage)
|
||||
.accentColor(.blue)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,17 +57,17 @@ struct SubscriptionsView: View {
|
|||
.task { await viewModel.loadSubscriptions(dataService: dataService) }
|
||||
} else if viewModel.hasNetworkError {
|
||||
VStack {
|
||||
Text("Sorry, we were unable to retrieve your subscriptions.").multilineTextAlignment(.center)
|
||||
Text(LocalText.subscriptionsErrorRetrieving).multilineTextAlignment(.center)
|
||||
Button(
|
||||
action: { Task { await viewModel.loadSubscriptions(dataService: dataService) } },
|
||||
label: { Text("Retry") }
|
||||
label: { Text(LocalText.genericRetry) }
|
||||
)
|
||||
.buttonStyle(RoundedRectButtonStyle())
|
||||
}
|
||||
} else if viewModel.subscriptions.isEmpty {
|
||||
VStack(alignment: .center) {
|
||||
Spacer()
|
||||
Text("You have no current subscriptions.")
|
||||
Text(LocalText.subscriptionsNone)
|
||||
Spacer()
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@
|
|||
var body: some View {
|
||||
Group {
|
||||
Form {
|
||||
Section("Audio Settings") {
|
||||
Toggle("Enable audio prefetch", isOn: $audioController.preloadEnabled)
|
||||
Section(LocalText.texttospeechSettingsAudio) {
|
||||
Toggle(LocalText.texttospeechSettingsEnablePrefetch, isOn: $audioController.preloadEnabled)
|
||||
}
|
||||
NavigationLink(destination: TextToSpeechLanguageView().navigationTitle("Default Language")) {
|
||||
Text("Default Language")
|
||||
NavigationLink(destination: TextToSpeechLanguageView().navigationTitle(LocalText.texttospeechLanguageDefault)) {
|
||||
Text(LocalText.texttospeechLanguageDefault)
|
||||
}
|
||||
innerBody
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
if FeatureFlag.enableUltraRealisticVoices, language.key == "en" {
|
||||
if viewModel.waitingForRealisticVoices {
|
||||
HStack {
|
||||
Text("Signing up for beta")
|
||||
Text(LocalText.texttospeechBetaSignupInProcess)
|
||||
Spacer()
|
||||
ProgressView()
|
||||
}
|
||||
|
|
@ -35,13 +35,13 @@
|
|||
}
|
||||
|
||||
if !viewModel.waitingForRealisticVoices, !audioController.ultraRealisticFeatureKey.isEmpty {
|
||||
Text("You are in the ultra realistic voices beta. During the beta you can listen to 10,000 words of audio per day.")
|
||||
Text(LocalText.texttospeechBetaRealisticVoiceLimit)
|
||||
.multilineTextAlignment(.leading)
|
||||
} else if audioController.ultraRealisticFeatureRequested {
|
||||
Text("Your request to join the ultra realistic voices demo has been received. You will be informed by email when a spot is available.")
|
||||
Text(LocalText.texttospeechBetaRequestReceived)
|
||||
.multilineTextAlignment(.leading)
|
||||
} else {
|
||||
Text("Ultra realistic voices are currently in limited beta. Enabling the feature will add you to the beta queue.")
|
||||
Text(LocalText.texttospeechBetaWaitlist)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import Views
|
|||
}
|
||||
|
||||
var submitButtonText: String {
|
||||
hasSuggestedProfile ? "Confirm" : "Submit"
|
||||
hasSuggestedProfile ? LocalText.genericConfirm : LocalText.genericSubmit
|
||||
}
|
||||
|
||||
@Published var loginError: LoginError?
|
||||
|
|
@ -134,7 +134,7 @@ struct CreateProfileView: View {
|
|||
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Name")
|
||||
Text(LocalText.genericName)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
#if os(iOS)
|
||||
|
|
@ -149,7 +149,7 @@ struct CreateProfileView: View {
|
|||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Username")
|
||||
Text(LocalText.registrationUsername)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
TextField("", text: $viewModel.potentialUsername)
|
||||
|
|
@ -173,7 +173,7 @@ struct CreateProfileView: View {
|
|||
.animation(.default, value: 0.35)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Bio (optional)")
|
||||
Text(LocalText.registrationBio)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
TextEditor(text: $bio)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ struct EmailLoginFormView: View {
|
|||
}
|
||||
VStack {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Email")
|
||||
Text(LocalText.genericEmail)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
TextField("", text: $email)
|
||||
|
|
@ -64,7 +64,7 @@ struct EmailLoginFormView: View {
|
|||
.padding(.bottom, 8)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Password")
|
||||
Text(LocalText.genericPassword)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
SecureField("", text: $password)
|
||||
|
|
@ -88,7 +88,7 @@ struct EmailLoginFormView: View {
|
|||
)
|
||||
}
|
||||
},
|
||||
label: { Text("Submit") }
|
||||
label: { Text(LocalText.genericSubmit) }
|
||||
)
|
||||
.buttonStyle(SolidCapsuleButtonStyle(color: .appCtaYellow, width: 300))
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ struct EmailLoginFormView: View {
|
|||
Button(
|
||||
action: { viewModel.emailAuthState = .signUp },
|
||||
label: {
|
||||
Text("Don't have an account?")
|
||||
Text(LocalText.registrationNoAccount)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.underline()
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ struct EmailLoginFormView: View {
|
|||
openURL(url)
|
||||
},
|
||||
label: {
|
||||
Text("Forgot your password?")
|
||||
Text(LocalText.registrationForgotPassword)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.underline()
|
||||
}
|
||||
|
|
@ -175,7 +175,7 @@ struct EmailPendingVerificationView: View {
|
|||
)
|
||||
}
|
||||
},
|
||||
label: { Text("Check Status") }
|
||||
label: { Text(LocalText.registrationStatusCheck) }
|
||||
)
|
||||
.buttonStyle(SolidCapsuleButtonStyle(color: .appDeepBackground, width: 300))
|
||||
|
||||
|
|
@ -183,7 +183,7 @@ struct EmailPendingVerificationView: View {
|
|||
Button(
|
||||
action: { viewModel.emailAuthState = .signUp },
|
||||
label: {
|
||||
Text("Use a different email?")
|
||||
Text(LocalText.registrationUseDifferentEmail)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.underline()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ struct EmailSignupFormView: View {
|
|||
VStack {
|
||||
// Email
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Email")
|
||||
Text(LocalText.genericEmail)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
TextField("", text: $email)
|
||||
|
|
@ -108,7 +108,7 @@ struct EmailSignupFormView: View {
|
|||
|
||||
// Password
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Password")
|
||||
Text(LocalText.genericPassword)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
SecureField("", text: $password)
|
||||
|
|
@ -124,7 +124,7 @@ struct EmailSignupFormView: View {
|
|||
|
||||
// Full Name
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Full Name")
|
||||
Text(LocalText.registrationFullName)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
TextField("", text: $name)
|
||||
|
|
@ -142,7 +142,7 @@ struct EmailSignupFormView: View {
|
|||
VStack(alignment: .leading, spacing: 6) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("Username")
|
||||
Text(LocalText.registrationUsername)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
TextField("", text: $viewModel.potentialUsername)
|
||||
|
|
@ -182,7 +182,7 @@ struct EmailSignupFormView: View {
|
|||
)
|
||||
}
|
||||
},
|
||||
label: { Text("Submit") }
|
||||
label: { Text(LocalText.genericSubmit) }
|
||||
)
|
||||
.buttonStyle(SolidCapsuleButtonStyle(color: .appCtaYellow, width: 300))
|
||||
|
||||
|
|
@ -194,7 +194,7 @@ struct EmailSignupFormView: View {
|
|||
Button(
|
||||
action: { viewModel.emailAuthState = .signIn },
|
||||
label: {
|
||||
Text("Already have an account?")
|
||||
Text(LocalText.registrationAlreadyHaveAccount)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.underline()
|
||||
}
|
||||
|
|
@ -221,7 +221,7 @@ struct EmailSignupFormView: View {
|
|||
Spacer()
|
||||
}
|
||||
}
|
||||
.navigationTitle(focusedField == nil ? "Sign Up" : "")
|
||||
.navigationTitle(focusedField == nil ? LocalText.registrationViewSignUpHeadline : "")
|
||||
.task {
|
||||
viewModel.configureUsernameValidation(dataService: dataService)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ struct NewAppleSignupView: View {
|
|||
|
||||
var body: some View {
|
||||
VStack(spacing: 28) {
|
||||
Text("Welcome to Omnivore!")
|
||||
Text(LocalText.registrationWelcome)
|
||||
.font(.appTitle)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
VStack(alignment: .center, spacing: 12) {
|
||||
Text("Your username is:")
|
||||
Text(LocalText.registrationUsernameAssignedPrefix)
|
||||
.font(.appBody)
|
||||
.foregroundColor(.appGrayText)
|
||||
Text("@\(userProfile.username)")
|
||||
|
|
@ -48,13 +48,13 @@ struct NewAppleSignupView: View {
|
|||
await viewModel.submitProfile(userProfile: userProfile, authenticator: authenticator)
|
||||
}
|
||||
},
|
||||
label: { Text("Continue") }
|
||||
label: { Text(LocalText.genericContinue) }
|
||||
)
|
||||
.buttonStyle(SolidCapsuleButtonStyle(color: .appDeepBackground, width: 300))
|
||||
|
||||
Button(
|
||||
action: showProfileEditView,
|
||||
label: { Text("Change Username") }
|
||||
label: { Text(LocalText.registrationChangeUsername) }
|
||||
)
|
||||
.buttonStyle(SolidCapsuleButtonStyle(color: .appDeepBackground, width: 300))
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ struct SelfHostSettingsView: View {
|
|||
Button(action: {
|
||||
showConfirmAlert = true
|
||||
}, label: {
|
||||
Text("Save")
|
||||
Text(LocalText.genericSave)
|
||||
})
|
||||
.disabled(!allFieldsSet)
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ struct SelfHostSettingsView: View {
|
|||
.alert(isPresented: $showConfirmAlert) {
|
||||
Alert(
|
||||
title: Text("Changing your environment settings will close the app."),
|
||||
dismissButton: .cancel(Text("Ok")) {
|
||||
dismissButton: .cancel(Text(LocalText.genericOk)) {
|
||||
AppEnvironment.setCustom(serverBaseURL: apiServerAddress, webAppBaseURL: webServerAddress, ttsBaseURL: ttsServerAddress)
|
||||
dataService.switchAppEnvironment(appEnvironment: AppEnvironment.custom)
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ struct SelfHostSettingsView: View {
|
|||
.navigationBarItems(leading:
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: { Text("Cancel") }),
|
||||
}, label: { Text(LocalText.cancelGeneric) }),
|
||||
trailing: saveButton)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ struct RecommendToView: View {
|
|||
Button(action: {
|
||||
self.viewModel.showNoteView = true
|
||||
}, label: {
|
||||
Text("Next")
|
||||
Text(LocalText.genericNext)
|
||||
.bold()
|
||||
})
|
||||
.disabled(viewModel.selectedGroups.isEmpty)
|
||||
|
|
@ -99,7 +99,7 @@ struct RecommendToView: View {
|
|||
}
|
||||
}
|
||||
}, label: {
|
||||
Text("Send")
|
||||
Text(LocalText.genericSend)
|
||||
.bold()
|
||||
})
|
||||
.disabled(viewModel.selectedGroups.isEmpty)
|
||||
|
|
@ -110,7 +110,7 @@ struct RecommendToView: View {
|
|||
var noteView: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Text("To:")
|
||||
Text(LocalText.recommendationToPrefix)
|
||||
.font(.appCaption)
|
||||
.foregroundColor(.appGrayText)
|
||||
Text(InternalRecommendationGroup.readable(list: viewModel.selectedGroups))
|
||||
|
|
@ -131,7 +131,7 @@ struct RecommendToView: View {
|
|||
.background(RoundedRectangle(cornerRadius: 8).fill(Color.systemBackground))
|
||||
)
|
||||
.overlay(
|
||||
Text("Add a note (optional)")
|
||||
Text(LocalText.recommendationAddNote)
|
||||
.allowsHitTesting(false)
|
||||
.opacity(viewModel.note.isEmpty ? 0.4 : 0.0)
|
||||
.font(.appBody)
|
||||
|
|
@ -163,9 +163,7 @@ struct RecommendToView: View {
|
|||
List {
|
||||
if !viewModel.isLoading, viewModel.recommendationGroups.count < 1 {
|
||||
Text("""
|
||||
You do not have any clubs you can post to.
|
||||
|
||||
Join a club or create your own to start recommending articles.
|
||||
\(LocalText.clubsNoneJoined)
|
||||
|
||||
[Learn more about clubs](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc)
|
||||
""")
|
||||
|
|
@ -201,8 +199,8 @@ struct RecommendToView: View {
|
|||
}
|
||||
.alert(isPresented: $viewModel.showError) {
|
||||
Alert(
|
||||
title: Text("Error recommending this page"),
|
||||
dismissButton: .cancel(Text("Ok")) {
|
||||
title: Text(LocalText.recommendationError),
|
||||
dismissButton: .cancel(Text(LocalText.genericOk)) {
|
||||
viewModel.showError = false
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ struct WebReaderContainerView: View {
|
|||
.padding(.horizontal)
|
||||
.scaleEffect(navBarVisibilityRatio)
|
||||
#else
|
||||
Text("Options")
|
||||
Text(LocalText.genericOptions)
|
||||
#endif
|
||||
}
|
||||
)
|
||||
|
|
@ -365,24 +365,24 @@ struct WebReaderContainerView: View {
|
|||
if let linkToOpen = linkToOpen {
|
||||
safariWebLink = SafariWebLink(id: UUID(), url: linkToOpen)
|
||||
}
|
||||
}, label: { Text("Open") })
|
||||
}, label: { Text(LocalText.genericOpen) })
|
||||
Button(action: {
|
||||
UIPasteboard.general.string = item.unwrappedPageURLString
|
||||
showInSnackbar("Link Copied")
|
||||
}, label: { Text("Copy Link") })
|
||||
}, label: { Text(LocalText.readerCopyLink) })
|
||||
Button(action: {
|
||||
if let linkToOpen = linkToOpen {
|
||||
viewModel.saveLink(dataService: dataService, url: linkToOpen)
|
||||
}
|
||||
}, label: { Text("Save to Omnivore") })
|
||||
}, label: { Text(LocalText.readerSave) })
|
||||
}
|
||||
#if os(iOS)
|
||||
.fullScreenCover(item: $safariWebLink) {
|
||||
SafariView(url: $0.url)
|
||||
}
|
||||
#endif
|
||||
.alert(errorAlertMessage ?? "An error occurred", isPresented: $showErrorAlertMessage) {
|
||||
Button("Ok", role: .cancel, action: {
|
||||
.alert(errorAlertMessage ?? LocalText.readerError, isPresented: $showErrorAlertMessage) {
|
||||
Button(LocalText.genericOk, role: .cancel, action: {
|
||||
errorAlertMessage = nil
|
||||
showErrorAlertMessage = false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ struct WelcomeView: View {
|
|||
|
||||
var headlineText: some View {
|
||||
Group {
|
||||
Text("Read-it-later for serious readers.")
|
||||
Text(LocalText.welcomeTitle)
|
||||
}
|
||||
.font(.appLargeTitle)
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ struct WelcomeView: View {
|
|||
},
|
||||
label: {
|
||||
HStack(spacing: 4) {
|
||||
Text("Learn more")
|
||||
Text(LocalText.welcomeLearnMore)
|
||||
Image(systemName: "arrow.right")
|
||||
}
|
||||
.font(.appTitleThree)
|
||||
|
|
@ -70,9 +70,9 @@ struct WelcomeView: View {
|
|||
|
||||
var footerView: some View {
|
||||
Group {
|
||||
Text("By signing up, you agree to Omnivore’s\n")
|
||||
+ Text("Terms of Service").underline()
|
||||
+ Text(" and ")
|
||||
Text(LocalText.welcomeSignupAgreement)
|
||||
+ Text(LocalText.welcomeTitleTermsOfService).underline()
|
||||
+ Text(LocalText.welcomeTitleAndJoiner)
|
||||
+ Text(LocalText.privacyPolicyGeneric).underline()
|
||||
}
|
||||
.font(.appSubheadline)
|
||||
|
|
@ -163,7 +163,7 @@ struct WelcomeView: View {
|
|||
let emailButton = Button(
|
||||
action: { showEmailLoginModal = true },
|
||||
label: {
|
||||
Text("Continue with Email")
|
||||
Text(LocalText.welcomeTitleEmailContinue)
|
||||
.font(.appHeadline)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.underline()
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public struct HighlightAnnotationSheet: View {
|
|||
Spacer()
|
||||
Label("Note", systemImage: "note.text")
|
||||
Spacer()
|
||||
Button("Save") {
|
||||
Button(LocalText.genericSave) {
|
||||
onSave()
|
||||
}
|
||||
}
|
||||
|
|
@ -49,8 +49,8 @@ public struct HighlightAnnotationSheet: View {
|
|||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.alert(errorAlertMessage ?? "An error occurred", isPresented: $showErrorAlertMessage) {
|
||||
Button("Ok", role: .cancel, action: {
|
||||
.alert(errorAlertMessage ?? LocalText.readerError, isPresented: $showErrorAlertMessage) {
|
||||
Button(LocalText.genericOk, role: .cancel, action: {
|
||||
errorAlertMessage = nil
|
||||
showErrorAlertMessage = false
|
||||
})
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ public final class OmnivoreWebView: WKWebView {
|
|||
// on iOS16 we use menuBuilder to create these items
|
||||
} else {
|
||||
let annotate = UIMenuItem(title: "Annotate", action: #selector(annotateSelection))
|
||||
let highlight = UIMenuItem(title: "Highlight", action: #selector(highlightSelection))
|
||||
let highlight = UIMenuItem(title: LocalText.genericHighlight, action: #selector(highlightSelection))
|
||||
// let share = UIMenuItem(title: "Share", action: #selector(shareSelection))
|
||||
|
||||
UIMenuController.shared.menuItems = [highlight, /* share, */ annotate]
|
||||
|
|
@ -315,7 +315,7 @@ public final class OmnivoreWebView: WKWebView {
|
|||
override public func buildMenu(with builder: UIMenuBuilder) {
|
||||
if #available(iOS 16.0, *) {
|
||||
let annotate = UICommand(title: "Note", action: #selector(annotateSelection))
|
||||
let highlight = UICommand(title: "Highlight", action: #selector(highlightSelection))
|
||||
let highlight = UICommand(title: LocalText.genericHighlight, action: #selector(highlightSelection))
|
||||
let remove = UICommand(title: "Remove", action: #selector(removeSelection))
|
||||
let setLabels = UICommand(title: LocalText.labelsGeneric, action: #selector(setLabels))
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public struct CommunityModal: View {
|
|||
|
||||
public var header: some View {
|
||||
VStack(spacing: 0) {
|
||||
Text("Help build the Omnivore Community")
|
||||
Text(LocalText.communityHeadline)
|
||||
.font(.textToSpeechRead)
|
||||
.foregroundColor(Color.appGrayTextContrast)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
|
@ -71,10 +71,10 @@ public struct CommunityModal: View {
|
|||
}
|
||||
|
||||
let links = [
|
||||
(title: "Tweet about Omnivore", url: tweetUrl),
|
||||
(title: "Follow us on Twitter", url: "https://twitter.com/omnivoreapp"),
|
||||
(title: "Join us on Discord", url: "https://discord.gg/h2z5rppzz9"),
|
||||
(title: "Star on GitHub", url: "https://github.com/omnivore-app/omnivore")
|
||||
(title: LocalText.communityTweet, url: tweetUrl),
|
||||
(title: LocalText.communityFollowTwitter, url: "https://twitter.com/omnivoreapp"),
|
||||
(title: LocalText.communityJoinDiscord, url: "https://discord.gg/h2z5rppzz9"),
|
||||
(title: LocalText.communityStarGithub, url: "https://github.com/omnivore-app/omnivore")
|
||||
]
|
||||
|
||||
var buttonLinks: some View {
|
||||
|
|
@ -83,7 +83,7 @@ public struct CommunityModal: View {
|
|||
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
|
||||
SKStoreReviewController.requestReview(in: scene)
|
||||
}
|
||||
}, label: { Text("Review on the AppStore") })
|
||||
}, label: { Text(LocalText.communityAppstoreReview) })
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
ForEach(links, id: \.url) { link in
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ public enum WebFont: String, CaseIterable {
|
|||
|
||||
NavigationLink(destination: fontList) {
|
||||
HStack {
|
||||
Text("Font")
|
||||
Text(LocalText.genericFont)
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
// Button(action: {}, label: { Text("Crimson Text").frame(width: 91) })
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Foundation
|
||||
|
||||
public enum LocalText {
|
||||
public static func localText(key: String, comment: String? = nil) -> String {
|
||||
private static func localText(key: String, comment: String? = nil) -> String {
|
||||
NSLocalizedString(key, bundle: .module, comment: comment ?? "no comment provided by developer")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ import SwiftUI
|
|||
.smallOmnivoreLogo
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
Text("Enable Push Notifications?")
|
||||
Text(LocalText.notificationsEnable)
|
||||
.font(.appHeadline)
|
||||
}
|
||||
|
||||
// swiftlint:disable:next line_length
|
||||
Text("Get notified when newsletter links reach your inbox. Or receive reminders that you set from our share extension.")
|
||||
Text(LocalText.notificationsGeneralExplainer)
|
||||
.font(.appBody)
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(nil)
|
||||
|
|
@ -35,8 +35,8 @@ import SwiftUI
|
|||
|
||||
HStack {
|
||||
Spacer()
|
||||
Button(action: denyAction, label: { Text("No Thanks") })
|
||||
Button(action: acceptAction, label: { Text("Yes Please") })
|
||||
Button(action: denyAction, label: { Text(LocalText.notificationsOptionDeny) })
|
||||
Button(action: acceptAction, label: { Text(LocalText.notificationsOptionEnable) })
|
||||
}
|
||||
.buttonStyle(BorderedButtonStyle(color: .appTextDefault))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// Unit test Entry -- Do not remove this or add entries before this one.
|
||||
// This allows us to check for syntax errors in this file with a unit test
|
||||
"unitTestLeadingEntry" = "For testing purposes only.";
|
||||
|
||||
// share extension
|
||||
"saveArticleSavedState" = "Saved to Omnivore";
|
||||
"saveArticleProcessingState" = "Saved to Omnivore";
|
||||
|
|
@ -55,7 +59,7 @@
|
|||
"clubsName" = "Club Name";
|
||||
"clubsCreate" = "Create a new club";
|
||||
"clubsYours" = "Your clubs";
|
||||
"clubsNotAMemberMessage" = "You are not a member of any clubs.\nCreate a new club and send the invite link to your friends get started.\n\nDuring the beta you are limited to creating three clubs, and each club\ncan have a maximum of twelve users."
|
||||
"clubsNotAMemberMessage" = "You are not a member of any clubs.\nCreate a new club and send the invite link to your friends get started.\n\nDuring the beta you are limited to creating three clubs, and each club\ncan have a maximum of twelve users.";
|
||||
"clubsErrorCopying" = "Error copying invite URL";
|
||||
"clubsAdminDenyViewing" = "The admin of this club does not allow viewing all members.";
|
||||
"clubsNoMembers" = "This club does not have any members. Add users to your club by sending\nthem the invite link.";
|
||||
|
|
@ -189,4 +193,8 @@
|
|||
"dismissButton" = "Dismiss";
|
||||
"errorNetwork" = "We are having trouble connecting to the internet.";
|
||||
|
||||
// TODO: search navigationTitle, toggle, section, button, Label
|
||||
// TODO: search navigationTitle, toggle, section, button, Label, title: ", CreateProfileViewModel, TextField, .keyboardShortcut
|
||||
|
||||
// Unit test Entry -- Do not remove this or add entries after this one.
|
||||
// This allows us to check for syntax errors in this file with a unit test
|
||||
"unitTestTrailingEntry" = "For testing purposes only.";
|
||||
|
|
|
|||
|
|
@ -1 +1,9 @@
|
|||
// Unit test Entry -- Do not remove this or add entries before this one.
|
||||
// This allows us to check for syntax errors in this file with a unit test
|
||||
"unitTestLeadingEntry" = "For testing purposes only.";
|
||||
|
||||
"googleSignInButton" = "Continuar con Google";
|
||||
|
||||
// Unit test Entry -- Do not remove this or add entries after this one.
|
||||
// This allows us to check for syntax errors in this file with a unit test
|
||||
"unitTestTrailingEntry" = "For testing purposes only.";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// Unit test Entry -- Do not remove this or add entries before this one.
|
||||
// This allows us to check for syntax errors in this file with a unit test
|
||||
"unitTestLeadingEntry" = "For testing purposes only.";
|
||||
|
||||
// share extension
|
||||
"saveArticleSavedState" = "正在保存到 Omnivore 中";
|
||||
"saveArticleProcessingState" = "已经保存到 Omnivore 中";
|
||||
|
|
@ -55,7 +59,7 @@
|
|||
"clubsName" = "读书俱乐部名称";
|
||||
"clubsCreate" = "创建新读书俱乐部";
|
||||
"clubsYours" = "您的读书俱乐部";
|
||||
"clubsNotAMemberMessage" = "您不是任何读书俱乐部的成员.\n创建新读书俱乐部并将邀请链接发送给您的朋友开始使用.\n\n在测试期间您只能创建三个读书俱乐部, 且每个读书俱乐部\n最多可以有 12 个用户."
|
||||
"clubsNotAMemberMessage" = "您不是任何读书俱乐部的成员.\n创建新读书俱乐部并将邀请链接发送给您的朋友开始使用.\n\n在测试期间您只能创建三个读书俱乐部, 且每个读书俱乐部\n最多可以有 12 个用户.";
|
||||
"clubsErrorCopying" = "复制邀请 URL 时出错";
|
||||
"clubsAdminDenyViewing" = "此读书俱乐部的管理员不允许查看所有成员.";
|
||||
"clubsNoMembers" = "此读书俱乐部没有任何成员. 通过向用户发送邀请链接\n将用户添加到您的读书俱乐部.";
|
||||
|
|
@ -86,8 +90,7 @@
|
|||
"registrationUsername" = "用户名";
|
||||
"registrationAlreadyHaveAccount" = "已有帐户?";
|
||||
"registrationBio" = "个人简介 (可选)";
|
||||
// 读万卷书,行万里路. From Chinese verse, Similar to the old saying: study, every day.
|
||||
"registrationWelcome" = "读万卷书,行万里路.";
|
||||
"registrationWelcome" = "读万卷书,行万里路."; // From Chinese verse, Similar to the old saying: study, every day.
|
||||
"registrationUsernameAssignedPrefix" = "您的用户名是:";
|
||||
"registrationChangeUsername" = "更改用户名";
|
||||
"registrationEdit" = "编辑";
|
||||
|
|
@ -199,3 +202,7 @@
|
|||
// "username.validation.error.tooshort" = "用户名应至少包含 3 个英文字符.";
|
||||
// "username.validation.error.toolong" = "用户名不能超过 15 个英文字符.";
|
||||
// "saveArticleSavingState" = "保存中...";
|
||||
|
||||
// Unit test Entry -- Do not remove this or add entries after this one.
|
||||
// This allows us to check for syntax errors in this file with a unit test
|
||||
"unitTestTrailingEntry" = "For testing purposes only.";
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ private struct SnoozeIconButtonView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private struct Snooze {
|
||||
struct Snooze {
|
||||
let until: Date
|
||||
let icon: Image
|
||||
let title: String
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ final class HomeFeedViewTests: XCTestCase {
|
|||
|
||||
func test_weekdayBefore8PM() {
|
||||
let now = parse("2022-01-31T10:11:12-08:00")
|
||||
let snoozes = HomeFeedView.snoozeValuesForDate(now: now)
|
||||
let snoozes = Snooze.snoozeValuesForDate(now: now)
|
||||
|
||||
XCTAssertEqual(snoozes[0].until, parse("2022-01-31T20:00:00-08:00"))
|
||||
XCTAssertEqual(snoozes[1].until, parse("2022-02-01T08:00:00-08:00"))
|
||||
|
|
@ -21,7 +21,7 @@ final class HomeFeedViewTests: XCTestCase {
|
|||
|
||||
func test_weekdayAfter8PM() {
|
||||
let now = parse("2022-01-31T20:11:12-08:00")
|
||||
let snoozes = HomeFeedView.snoozeValuesForDate(now: now)
|
||||
let snoozes = Snooze.snoozeValuesForDate(now: now)
|
||||
|
||||
XCTAssertEqual(snoozes[0].until, parse("2022-02-01T08:00:00-08:00"))
|
||||
XCTAssertEqual(snoozes[1].until, parse("2022-02-01T20:00:00-08:00"))
|
||||
|
|
|
|||
34
apple/OmnivoreKit/Tests/ViewsTests/LocalTextTests.swift
Normal file
34
apple/OmnivoreKit/Tests/ViewsTests/LocalTextTests.swift
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
@testable import Views
|
||||
import XCTest
|
||||
|
||||
final class LocalTextTests: XCTestCase {
|
||||
func testThatLocalTextFindsStrings() {
|
||||
// Make sure that the same key is not returned when looking up a localized string by key
|
||||
// Testing the first and last entry in teh strings file is adequate for finding syntax errors.
|
||||
// If any entry is not proper than the key will be returned and the test will fail.
|
||||
|
||||
for languageCode in LanguageCode.allCases {
|
||||
XCTAssertNotEqual(languageCode.translation(key: "unitTestLeadingEntry"), "unitTestLeadingEntry")
|
||||
XCTAssertNotEqual(languageCode.translation(key: "unitTestTrailingEntry"), "unitTestTrailingEntry")
|
||||
}
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testThatLocalTextFindsStrings", testThatLocalTextFindsStrings)
|
||||
]
|
||||
}
|
||||
|
||||
private enum LanguageCode: String, CaseIterable {
|
||||
case english = "en"
|
||||
case simpleChinese = "zh-Hans"
|
||||
case spanish = "es"
|
||||
|
||||
func translation(key: String) -> String {
|
||||
guard
|
||||
let bundlePath = Bundle.module.path(forResource: rawValue, ofType: "lproj"),
|
||||
let bundle = Bundle(path: bundlePath)
|
||||
else { return key }
|
||||
|
||||
return NSLocalizedString(key, bundle: bundle, comment: "")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue