Merge pull request #1713 from omnivore-app/chore/strings-refs

String updates + unit test for syntax
This commit is contained in:
Satindar Dhillon 2023-01-25 19:48:57 -08:00 committed by GitHub
commit 4c71332aed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 234 additions and 158 deletions

View file

@ -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

View file

@ -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)

View file

@ -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) }
)
}
}

View file

@ -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,

View file

@ -18,7 +18,7 @@ enum PrimaryContentCategory: Identifiable, Hashable, Equatable {
case .feed:
return "Home"
case .profile:
return "Profile"
return LocalText.genericProfile
}
}

View file

@ -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))
}

View file

@ -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),

View file

@ -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)
}
}

View file

@ -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 }
}
}
}

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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)

View file

@ -204,7 +204,7 @@ struct LinkItemDetailView: View {
} else {
HStack(alignment: .center) {
Spacer()
Text("Loading...")
Text(LocalText.genericLoading)
Spacer()
}
}

View file

@ -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(

View file

@ -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") }
)
}
}

View file

@ -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)
}

View file

@ -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: {

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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))

View file

@ -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)
}
}

View file

@ -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
}
)

View file

@ -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
})

View file

@ -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 Omnivores\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()

View file

@ -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
})

View file

@ -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))

View file

@ -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

View file

@ -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) })

View file

@ -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")
}

View file

@ -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))
}

View file

@ -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.";

View file

@ -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.";

View file

@ -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.";

View file

@ -87,7 +87,7 @@ private struct SnoozeIconButtonView: View {
}
}
private struct Snooze {
struct Snooze {
let until: Date
let icon: Image
let title: String

View file

@ -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"))

View 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: "")
}
}