Refactor String identifiers (#920)

- Group all views under "views.*"
- Split global strings into actions and nouns
- Use underscores
- Clean up unused

Fixes #835
This commit is contained in:
Davide 2024-11-23 20:31:22 +01:00 committed by GitHub
parent 3e2823c2e0
commit f13a292b4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
71 changed files with 731 additions and 772 deletions

View File

@ -96,7 +96,7 @@ extension AboutCoordinator {
LinksView()
default:
Text(Strings.Global.noSelection)
Text(Strings.Global.Nouns.noSelection)
.themeEmptyMessage()
}
}
@ -124,7 +124,7 @@ extension AboutCoordinator {
}
default:
Text(Strings.Global.noSelection)
Text(Strings.Global.Nouns.noSelection)
.themeEmptyMessage()
}
}

View File

@ -74,11 +74,11 @@ private extension AboutContentView {
.themeSection(header: Strings.Views.About.Sections.resources)
Section {
linkContent(.diagnostics)
Text(Strings.Global.version)
Text(Strings.Global.Nouns.version)
.themeTrailingValue(BundleConfiguration.mainVersionString)
}
}
.navigationTitle(Strings.Global.settings)
.navigationTitle(Strings.Global.Nouns.settings)
}
}

View File

@ -60,7 +60,7 @@ struct AboutContentView<LinkContent, AboutDestination, LogDestination>: View whe
.themeNavigationStack(closable: false, path: $path)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(Strings.Global.ok) {
Button(Strings.Global.Nouns.ok) {
dismiss()
}
}

View File

@ -52,10 +52,10 @@ struct AddProfileMenu: View {
private extension AddProfileMenu {
var newProfileButton: some View {
Button {
let profile = profileManager.new(withName: Strings.Entities.Profile.Name.new)
let profile = profileManager.new(withName: Strings.Placeholders.Profile.name)
onNewProfile(profile)
} label: {
ThemeImageLabel(Strings.Views.Profiles.Toolbar.newProfile, .profileEdit)
ThemeImageLabel(Strings.Views.App.Toolbar.newProfile, .profileEdit)
}
}
@ -63,13 +63,13 @@ private extension AddProfileMenu {
Button {
isImporting = true
} label: {
ThemeImageLabel(Strings.Views.Profiles.Toolbar.importProfile.withTrailingDots, .profileImport)
ThemeImageLabel(Strings.Views.App.Toolbar.importProfile.withTrailingDots, .profileImport)
}
}
var migrateProfilesButton: some View {
Button(action: onMigrateProfiles) {
ThemeImageLabel(Strings.Views.Profiles.Toolbar.migrateProfiles.withTrailingDots, .profileMigrate)
ThemeImageLabel(Strings.Views.App.Toolbar.migrateProfiles.withTrailingDots, .profileMigrate)
}
}
}

View File

@ -90,7 +90,7 @@ private extension InstalledProfileView {
}
var nameView: some View {
Text(profile?.name ?? Strings.Views.Profiles.Rows.notInstalled)
Text(profile?.name ?? Strings.Views.App.Rows.notInstalled)
.font(.title2)
.fontWeight(theme.relevantWeight)
.themeTruncating(.tail)

View File

@ -70,7 +70,7 @@ struct ProfileAttributesView: View {
case .tv:
return (
isRemoteImportingEnabled ? .tvOn : .tvOff,
Strings.Modules.General.Rows.appleTv(Strings.Unlocalized.appleTV)
Strings.Modules.General.Rows.appletv(Strings.Unlocalized.appleTV)
)
}
}

View File

@ -50,7 +50,7 @@ struct ProfileCardView: View {
.font(.headline)
.themeTruncating()
Text(preview.subtitle ?? Strings.Views.Profiles.Rows.noModules)
Text(preview.subtitle ?? Strings.Views.App.Rows.noModules)
.multilineTextAlignment(.leading)
.font(.subheadline)
.foregroundStyle(.secondary)

View File

@ -96,8 +96,8 @@ private extension ProfileContainerView {
InteractiveCoordinator(style: .modal, manager: interactiveManager) {
errorHandler.handle(
$0,
title: Strings.Global.connection,
message: Strings.Views.Profiles.Errors.tunnel
title: Strings.Global.Nouns.connection,
message: Strings.Views.App.Errors.tunnel
)
}
.presentationDetents([.medium])
@ -122,10 +122,10 @@ private struct ContainerModifier: ViewModifier {
isEmpty: !profileManager.hasProfiles,
emptyContent: {
VStack(spacing: 16) {
Text(Strings.Views.Profiles.Folders.noProfiles)
Text(Strings.Views.App.Folders.noProfiles)
.themeEmptyMessage(fullScreen: false)
Button(Strings.Views.Profiles.Folders.NoProfiles.migrate) {
Button(Strings.Views.App.Folders.NoProfiles.migrate) {
flow?.onMigrateProfiles()
}
}

View File

@ -78,7 +78,7 @@ private extension ProfileContextMenu {
},
label: {
ThemeImageLabel(
$0 ? Strings.Global.enable : Strings.Global.disable,
$0 ? Strings.Global.Actions.enable : Strings.Global.Actions.disable,
$0 ? .tunnelEnable : .tunnelDisable
)
}
@ -89,7 +89,7 @@ private extension ProfileContextMenu {
profile?
.selectedProvider
.map { _ in
Button(Strings.Ui.ProfileContext.connectTo) {
Button(Strings.Views.App.ProfileContext.connectTo) {
flow?.onEditProviderEntity(profile!)
}
}
@ -104,7 +104,7 @@ private extension ProfileContextMenu {
flow?.onPurchaseRequired($0)
},
label: {
ThemeImageLabel(Strings.Global.restart, .tunnelRestart)
ThemeImageLabel(Strings.Global.Actions.restart, .tunnelRestart)
}
)
}
@ -113,7 +113,7 @@ private extension ProfileContextMenu {
Button {
flow?.onEditProfile(preview)
} label: {
ThemeImageLabel(Strings.Global.edit.withTrailingDots, .profileEdit)
ThemeImageLabel(Strings.Global.Actions.edit.withTrailingDots, .profileEdit)
}
}
@ -123,7 +123,7 @@ private extension ProfileContextMenu {
preview: preview,
errorHandler: errorHandler
) {
ThemeImageLabel(Strings.Global.duplicate, .contextDuplicate)
ThemeImageLabel(Strings.Global.Actions.duplicate, .contextDuplicate)
}
}
@ -132,7 +132,7 @@ private extension ProfileContextMenu {
profileManager: profileManager,
preview: preview
) {
ThemeImageLabel(Strings.Global.remove, .contextRemove)
ThemeImageLabel(Strings.Global.Actions.remove, .contextRemove)
}
}
}

View File

@ -44,8 +44,8 @@ struct ProfileDuplicateButton<Label>: View where Label: View {
} catch {
errorHandler.handle(
error,
title: Strings.Global.duplicate,
message: Strings.Views.Profiles.Errors.duplicate(preview.name)
title: Strings.Global.Actions.duplicate,
message: Strings.Views.App.Errors.duplicate(preview.name)
)
}
}

View File

@ -71,7 +71,7 @@ struct ProfileGridView: View, Routable, TunnelInstallationProviding {
}
}
}
.themeGridHeader(title: Strings.Views.Profiles.Folders.default)
.themeGridHeader(title: Strings.Views.App.Folders.default)
}
.padding(.horizontal)
}

View File

@ -53,7 +53,7 @@ struct ProfileImporterModifier: ViewModifier {
handleResult(.success($0))
}
.alert(
Strings.Views.Profiles.Toolbar.importProfile,
Strings.Views.App.Toolbar.importProfile,
isPresented: $importer.isPresentingPassphrase,
presenting: importer.nextURL,
actions: actions,
@ -79,7 +79,7 @@ private extension ProfileImporterModifier {
)
}
}
Button(Strings.Global.cancel, role: .cancel) {
Button(Strings.Global.Actions.cancel, role: .cancel) {
importer.cancelImport()
}
}
@ -100,8 +100,8 @@ private extension ProfileImporterModifier {
} catch {
await errorHandler.handle(
error,
title: Strings.Views.Profiles.Toolbar.importProfile,
message: Strings.Views.Profiles.Errors.import
title: Strings.Views.App.Toolbar.importProfile,
message: Strings.Views.App.Errors.import
)
}
}

View File

@ -70,7 +70,7 @@ struct ProfileListView: View, Routable, TunnelInstallationProviding {
}
}
}
.themeSection(header: Strings.Views.Profiles.Folders.default)
.themeSection(header: Strings.Views.App.Folders.default)
}
.themeForm()
}

View File

@ -59,8 +59,8 @@ struct TunnelRestartButton<Label>: View where Label: View {
} catch {
errorHandler.handle(
error,
title: Strings.Global.connection,
message: Strings.Views.Profiles.Errors.tunnel
title: Strings.Global.Nouns.connection,
message: Strings.Views.App.Errors.tunnel
)
}
}

View File

@ -66,24 +66,24 @@ private extension AppMenu {
}
var showToggle: some View {
Button(Strings.Global.show) {
Button(Strings.Global.Actions.show) {
settings.isVisible = true
}
}
var loginToggle: some View {
Toggle(Strings.Views.Settings.launchesOnLogin, isOn: $settings.launchesOnLogin)
Toggle(Strings.Views.Preferences.launchesOnLogin, isOn: $settings.launchesOnLogin)
}
var keepToggle: some View {
Toggle(Strings.Views.Settings.keepsInMenu, isOn: $settings.keepsInMenu)
Toggle(Strings.Views.Preferences.keepsInMenu, isOn: $settings.keepsInMenu)
}
var profilesList: some View {
Group {
ForEach(profileManager.previews, id: \.id, content: profileToggle)
}
.themeSection(header: Strings.Views.Profiles.Folders.default)
.themeSection(header: Strings.Views.App.Folders.default)
}
func profileToggle(for preview: ProfilePreview) -> some View {
@ -112,14 +112,14 @@ private extension AppMenu {
}
var aboutButton: some View {
Button(Strings.Global.about.withTrailingDots) {
Button(Strings.Global.Nouns.about.withTrailingDots) {
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(self)
}
}
var quitButton: some View {
Button(Strings.AppMenu.Items.quit(BundleConfiguration.mainDisplayName)) {
Button(Strings.Views.AppMenu.Items.quit(BundleConfiguration.mainDisplayName)) {
NSApp.terminate(self)
}
}

View File

@ -90,7 +90,7 @@ struct DiagnosticsView: View {
.themeForm()
.navigationTitle(Strings.Views.Diagnostics.title)
.alert(Strings.Views.Diagnostics.ReportIssue.title, isPresented: $isPresentingUnableToEmail) {
Button(Strings.Global.ok, role: .cancel) {
Button(Strings.Global.Nouns.ok, role: .cancel) {
isPresentingUnableToEmail = false
}
} message: {

View File

@ -40,10 +40,10 @@ private extension MigrateButton {
var title: String {
switch step {
case .initial, .fetching, .fetched:
return Strings.Views.Migrate.Items.migrate
return Strings.Views.Migration.Items.migrate
case .migrating, .migrated:
return Strings.Global.done
return Strings.Global.Nouns.done
}
}

View File

@ -67,7 +67,7 @@ private extension MigrateContentView.ListView {
}
var messageView: some View {
Text(Strings.Views.Migrate.Sections.Main.header(Strings.Unlocalized.appName))
Text(Strings.Views.Migration.Sections.Main.header(Strings.Unlocalized.appName))
.padding([.top, .leading, .trailing])
}
@ -98,7 +98,7 @@ private extension MigrateContentView.ListView {
}
.listStyle(.plain)
.disabled(!step.canSelect)
.themeEmpty(if: isEmpty, message: Strings.Views.Migrate.noProfiles)
.themeEmpty(if: isEmpty, message: Strings.Views.Migration.noProfiles)
}
func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {
@ -142,7 +142,7 @@ private extension MigrateContentView.ListView {
var body: some View {
HStack {
if isEditing {
Button(Strings.Global.cancel) {
Button(Strings.Global.Actions.cancel) {
isEditing = false
}
}
@ -165,7 +165,7 @@ private extension MigrateContentView.ListView {
}
var title: String {
isEditing ? Strings.Views.Migrate.Items.discard : Strings.Global.edit
isEditing ? Strings.Views.Migration.Items.discard : Strings.Global.Actions.edit
}
var role: ButtonRole? {

View File

@ -61,18 +61,18 @@ private extension MigrateContentView.TableView {
}
var messageView: some View {
Text(Strings.Views.Migrate.Sections.Main.header(Strings.Unlocalized.appName))
Text(Strings.Views.Migration.Sections.Main.header(Strings.Unlocalized.appName))
.padding([.top, .leading, .trailing])
}
var profilesForm: some View {
Form {
Table(profiles) {
TableColumn(Strings.Global.name) {
TableColumn(Strings.Global.Nouns.name) {
Text($0.name)
.foregroundStyle(statuses.style(for: $0.id))
}
TableColumn(Strings.Global.lastUpdate) {
TableColumn(Strings.Global.Nouns.lastUpdate) {
Text($0.timestamp)
.foregroundStyle(statuses.style(for: $0.id))
}
@ -98,7 +98,7 @@ private extension MigrateContentView.TableView {
}
.disabled(!step.canSelect)
.themeForm()
.themeEmpty(if: isEmpty, message: Strings.Views.Migrate.noProfiles)
.themeEmpty(if: isEmpty, message: Strings.Views.Migration.noProfiles)
}
func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {

View File

@ -81,7 +81,7 @@ struct MigrateView: View {
.themeAnimation(on: model, category: .profiles)
.themeConfirmation(
isPresented: $isDeleting,
title: Strings.Views.Migrate.Items.discard,
title: Strings.Views.Migration.Items.discard,
message: messageForDeletion,
isDestructive: true,
action: confirmPendingDeletion
@ -96,7 +96,7 @@ struct MigrateView: View {
private extension MigrateView {
var title: String {
Strings.Views.Migrate.title
Strings.Views.Migration.title
}
var messageForDeletion: String? {
@ -105,7 +105,7 @@ private extension MigrateView {
.map(\.name)
.joined(separator: "\n")
return Strings.Views.Migrate.Alerts.Delete.message(nameList)
return Strings.Views.Migration.Alerts.Delete.message(nameList)
}
}

View File

@ -63,7 +63,7 @@ private extension DNSView {
var protocolSection: some View {
Section {
Picker(Strings.Global.protocol, selection: draft.protocolType) {
Picker(Strings.Global.Nouns.protocol, selection: draft.protocolType) {
ForEach(Self.allProtocols, id: \.self) {
Text($0.localizedDescription)
}
@ -77,7 +77,7 @@ private extension DNSView {
.labelsHidden()
case .tls:
ThemeTextField(Strings.Global.hostname, text: draft.dotHostname, placeholder: Strings.Unlocalized.Placeholders.dotHostname)
ThemeTextField(Strings.Global.Nouns.hostname, text: draft.dotHostname, placeholder: Strings.Unlocalized.Placeholders.dotHostname)
.labelsHidden()
}
}
@ -85,9 +85,9 @@ private extension DNSView {
var domainSection: some View {
Group {
ThemeTextField(Strings.Global.domain, text: draft.domainName ?? "", placeholder: Strings.Unlocalized.Placeholders.hostname)
ThemeTextField(Strings.Global.Nouns.domain, text: draft.domainName ?? "", placeholder: Strings.Unlocalized.Placeholders.hostname)
}
.themeSection(header: Strings.Global.domain)
.themeSection(header: Strings.Global.Nouns.domain)
}
var serversSection: some View {

View File

@ -53,16 +53,16 @@ struct HTTPProxyView: View, ModuleDraftEditing {
private extension HTTPProxyView {
var httpSection: some View {
Group {
ThemeTextField(Strings.Global.address, text: draft.address, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
ThemeTextField(Strings.Global.port, text: draft.port.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
ThemeTextField(Strings.Global.Nouns.address, text: draft.address, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
ThemeTextField(Strings.Global.Nouns.port, text: draft.port.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
}
.themeSection(header: Strings.Unlocalized.http)
}
var httpsSection: some View {
Group {
ThemeTextField(Strings.Global.address, text: draft.secureAddress, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
ThemeTextField(Strings.Global.port, text: draft.securePort.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
ThemeTextField(Strings.Global.Nouns.address, text: draft.secureAddress, placeholder: Strings.Unlocalized.Placeholders.proxyIPv4Address)
ThemeTextField(Strings.Global.Nouns.port, text: draft.securePort.toString(omittingZero: true), placeholder: Strings.Unlocalized.Placeholders.proxyPort)
}
.themeSection(header: Strings.Unlocalized.https)
}

View File

@ -48,24 +48,24 @@ extension IPView {
var body: some View {
Form {
Section {
Toggle(Strings.Global.default, isOn: $isDefault.animation(theme.animation(for: .modules)))
Toggle(Strings.Global.Nouns.default, isOn: $isDefault.animation(theme.animation(for: .modules)))
}
if !isDefault {
Section {
ThemeTextField(Strings.Global.destination, text: $destinationString, placeholder: Strings.Unlocalized.Placeholders.ipDestination(forFamily: family))
ThemeTextField(Strings.Global.gateway, text: $gatewayString, placeholder: Strings.Unlocalized.Placeholders.ipGateway(forFamily: family))
ThemeTextField(Strings.Global.Nouns.destination, text: $destinationString, placeholder: Strings.Unlocalized.Placeholders.ipDestination(forFamily: family))
ThemeTextField(Strings.Global.Nouns.gateway, text: $gatewayString, placeholder: Strings.Unlocalized.Placeholders.ipGateway(forFamily: family))
}
}
}
.themeForm()
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(Strings.Global.cancel, role: .cancel) {
Button(Strings.Global.Actions.cancel, role: .cancel) {
onSubmit(nil)
}
}
ToolbarItem(placement: .confirmationAction) {
Button(Strings.Global.ok, action: parseAndSubmit)
Button(Strings.Global.Nouns.ok, action: parseAndSubmit)
}
}
.themeNavigationDetail()

View File

@ -138,7 +138,7 @@ private extension IPView {
placeholder: Strings.Unlocalized.Placeholders.mtu
)
}
.themeSection(header: Strings.Global.interface)
.themeSection(header: Strings.Global.Nouns.interface)
}
}

View File

@ -72,7 +72,7 @@ private extension OnDemandView {
var enabledSection: some View {
Section {
Toggle(Strings.Global.enabled, isOn: draft.isEnabled)
Toggle(Strings.Global.Nouns.enabled, isOn: draft.isEnabled)
}
}
@ -128,7 +128,7 @@ private extension OnDemandView {
Toggle(Strings.Modules.OnDemand.ethernet, isOn: draft.withEthernetNetwork)
}
}
.themeSection(header: Strings.Global.networks)
.themeSection(header: Strings.Global.Nouns.networks)
}
var wifiSection: some View {

View File

@ -35,7 +35,7 @@ extension OpenVPNView {
let credentialsRoute: any Hashable
var body: some View {
moduleSection(for: accountRows, header: Strings.Global.account)
moduleSection(for: accountRows, header: Strings.Global.Nouns.account)
moduleSection(for: remotesRows, header: Strings.Modules.Openvpn.remotes)
if !isServerPushed {
moduleSection(for: pullRows, header: Strings.Modules.Openvpn.pull)
@ -56,7 +56,7 @@ extension OpenVPNView {
if !isServerPushed {
moduleSection(for: tlsRows, header: Strings.Unlocalized.tls)
}
moduleSection(for: otherRows, header: Strings.Global.other)
moduleSection(for: otherRows, header: Strings.Global.Nouns.other)
}
}
}
@ -92,10 +92,10 @@ private extension OpenVPNView.ConfigurationView {
var rows: [ModuleRow] = []
if let ip {
ip.localizedDescription(optionalStyle: .address).map {
rows.append(.copiableText(caption: Strings.Global.address, value: $0))
rows.append(.copiableText(caption: Strings.Global.Nouns.address, value: $0))
}
ip.localizedDescription(optionalStyle: .defaultGateway).map {
rows.append(.copiableText(caption: Strings.Global.gateway, value: $0))
rows.append(.copiableText(caption: Strings.Global.Nouns.gateway, value: $0))
}
ip.includedRoutes
@ -118,7 +118,7 @@ private extension OpenVPNView.ConfigurationView {
}
}
routes?.forEach {
rows.append(.longContent(caption: Strings.Global.route, value: $0.localizedDescription))
rows.append(.longContent(caption: Strings.Global.Nouns.route, value: $0.localizedDescription))
}
return rows.nilIfEmpty
}
@ -147,14 +147,14 @@ private extension OpenVPNView.ConfigurationView {
.nilIfEmpty
.map {
rows.append(.textList(
caption: Strings.Global.servers,
caption: Strings.Global.Nouns.servers,
values: $0
))
}
configuration.dnsDomain.map {
rows.append(.copiableText(
caption: Strings.Global.domain,
caption: Strings.Global.Nouns.domain,
value: $0
))
}
@ -237,10 +237,10 @@ private extension OpenVPNView.ConfigurationView {
rows.append(.longContentPreview(caption: Strings.Unlocalized.ca, value: $0.pem, preview: nil))
}
configuration.clientCertificate.map {
rows.append(.longContentPreview(caption: Strings.Global.certificate, value: $0.pem, preview: nil))
rows.append(.longContentPreview(caption: Strings.Global.Nouns.certificate, value: $0.pem, preview: nil))
}
configuration.clientKey.map {
rows.append(.longContentPreview(caption: Strings.Global.key, value: $0.pem, preview: nil))
rows.append(.longContentPreview(caption: Strings.Global.Nouns.key, value: $0.pem, preview: nil))
}
configuration.tlsWrap.map {
rows.append(.longContentPreview(
@ -256,7 +256,7 @@ private extension OpenVPNView.ConfigurationView {
var otherRows: [ModuleRow]? {
var rows: [ModuleRow] = []
configuration.localizedDescription(optionalStyle: .keepAlive).map {
rows.append(.text(caption: Strings.Global.keepAlive, value: $0))
rows.append(.text(caption: Strings.Global.Nouns.keepAlive, value: $0))
}
configuration.localizedDescription(optionalStyle: .renegotiatesAfter).map {
rows.append(.text(caption: Strings.Modules.Openvpn.renegotiation, value: $0))

View File

@ -129,7 +129,7 @@ private extension OpenVPNView {
Button(Strings.Alerts.Import.Passphrase.ok) {
importConfiguration(from: .success(url))
}
Button(Strings.Global.cancel, role: .cancel) {
Button(Strings.Global.Actions.cancel, role: .cancel) {
isImporting = false
}
},

View File

@ -43,12 +43,12 @@ extension WireGuardView {
private extension WireGuardView.ConfigurationView {
var interfaceRows: [ModuleRow]? {
var rows: [ModuleRow] = []
rows.append(.longContent(caption: Strings.Global.privateKey, value: configuration.interface.privateKey))
rows.append(.longContent(caption: Strings.Global.Nouns.privateKey, value: configuration.interface.privateKey))
configuration.interface.addresses
.nilIfEmpty
.map {
rows.append(.textList(
caption: Strings.Global.addresses,
caption: Strings.Global.Nouns.addresses,
values: $0
))
}
@ -65,14 +65,14 @@ private extension WireGuardView.ConfigurationView {
.nilIfEmpty
.map {
rows.append(.textList(
caption: Strings.Global.servers,
caption: Strings.Global.Nouns.servers,
values: $0
))
}
configuration.interface.dns.domainName.map {
rows.append(.text(
caption: Strings.Global.domain,
caption: Strings.Global.Nouns.domain,
value: $0
))
}
@ -91,12 +91,12 @@ private extension WireGuardView.ConfigurationView {
func peersRows(for peer: WireGuard.RemoteInterface.Builder) -> [ModuleRow]? {
var rows: [ModuleRow] = []
rows.append(.longContent(caption: Strings.Global.publicKey, value: peer.publicKey))
rows.append(.longContent(caption: Strings.Global.Nouns.publicKey, value: peer.publicKey))
peer.preSharedKey.map {
rows.append(.longContent(caption: Strings.Modules.Wireguard.presharedKey, value: $0))
}
peer.endpoint.map {
rows.append(.copiableText(caption: Strings.Global.endpoint, value: $0))
rows.append(.copiableText(caption: Strings.Global.Nouns.endpoint, value: $0))
}
peer.allowedIPs
.nilIfEmpty
@ -107,7 +107,7 @@ private extension WireGuardView.ConfigurationView {
))
}
peer.keepAlive.map {
rows.append(.text(caption: Strings.Global.keepAlive, value: TimeInterval($0).localizedDescription(style: .timeString)))
rows.append(.text(caption: Strings.Global.Nouns.keepAlive, value: TimeInterval($0).localizedDescription(style: .timeString)))
}
return rows.nilIfEmpty
}

View File

@ -53,7 +53,7 @@ private extension ModuleDetailView {
}
var emptyView: some View {
Text(Strings.Global.noSelection)
Text(Strings.Global.Nouns.noSelection)
.themeEmptyMessage()
}
}

View File

@ -109,7 +109,7 @@ private extension View {
}
} else {
Text(caption)
.themeTrailingValue(Strings.Global.empty)
.themeTrailingValue(Strings.Global.Nouns.empty)
}
case .copiableText(let caption, let value, let multiline):

View File

@ -35,11 +35,11 @@ struct NameSection: View {
var body: some View {
debugChanges()
return Group {
ThemeTextField(Strings.Global.name, text: $name, placeholder: placeholder)
ThemeTextField(Strings.Global.Nouns.name, text: $name, placeholder: placeholder)
.labelsHidden()
.themeManualInput()
}
.themeSection(header: Strings.Global.name)
.themeSection(header: Strings.Global.Nouns.name)
}
}

View File

@ -74,11 +74,11 @@ struct ProfileCoordinator: View {
contentView
.modifier(PaywallModifier(reason: $paywallReason))
.alert(Strings.Views.Profile.Alerts.Purchase.title, isPresented: $requiresPurchase) {
Button(Strings.Global.purchase) {
Button(Strings.Global.Actions.purchase) {
paywallReason = .purchase(requiredFeatures, nil)
}
Button(Strings.Views.Profile.Alerts.Purchase.Buttons.ok, action: onDismiss)
Button(Strings.Global.cancel, role: .cancel, action: {})
Button(Strings.Global.Actions.cancel, role: .cancel, action: {})
} message: {
Text(purchaseMessage)
}
@ -142,7 +142,7 @@ private extension ProfileCoordinator {
try await onCommitEditingRestricted()
}
} catch {
errorHandler.handle(error, title: Strings.Global.save)
errorHandler.handle(error, title: Strings.Global.Actions.save)
throw error
}
}

View File

@ -60,7 +60,7 @@ private extension StorageSection {
}
var tvToggle: some View {
Toggle(Strings.Modules.General.Rows.appleTv(Strings.Unlocalized.appleTV), isOn: $profileEditor.isAvailableForTV)
Toggle(Strings.Modules.General.Rows.appletv(Strings.Unlocalized.appleTV), isOn: $profileEditor.isAvailableForTV)
.disabled(!iapManager.isEligible(for: .appleTV) || !profileEditor.isShared)
}
@ -87,7 +87,7 @@ private extension StorageSection {
var purchaseTVButton: some View {
EmptyView()
.modifier(PurchaseButtonModifier(
Strings.Modules.General.Rows.AppleTv.purchase,
Strings.Modules.General.Rows.Appletv.purchase,
feature: .appleTV,
suggesting: .Features.appleTV,
showsIfRestricted: false,

View File

@ -63,7 +63,7 @@ struct ProfileEditView: View, Routable {
UUIDSection(uuid: profileEditor.profile.id)
}
.toolbar(content: toolbarContent)
.navigationTitle(Strings.Global.profile)
.navigationTitle(Strings.Global.Nouns.profile)
.navigationBarBackButtonHidden(true)
.navigationDestination(for: NavigationRoute.self, destination: pushDestination)
}
@ -77,14 +77,14 @@ private extension ProfileEditView {
func toolbarContent() -> some ToolbarContent {
ToolbarItem(placement: .confirmationAction) {
ProfileSaveButton(
title: Strings.Global.save,
title: Strings.Global.Actions.save,
errorModuleIds: $errorModuleIds
) {
try await flow?.onCommitEditing()
}
}
ToolbarItem(placement: .cancellationAction) {
Button(Strings.Global.cancel, role: .cancel) {
Button(Strings.Global.Actions.cancel, role: .cancel) {
flow?.onCancelEditing()
}
}
@ -99,7 +99,7 @@ private extension ProfileEditView {
addModuleButton
}
.themeSection(
header: Strings.Global.modules,
header: Strings.Global.Nouns.modules,
footer: Strings.Views.Profile.ModuleList.Section.footer
)
}

View File

@ -50,7 +50,7 @@ struct ModuleListView: View, Routable {
var body: some View {
List(selection: $selectedModuleId) {
Section {
NavigationLink(Strings.Global.general, value: ProfileSplitView.Detail.general)
NavigationLink(Strings.Global.Nouns.general, value: ProfileSplitView.Detail.general)
.tag(Self.generalModuleId)
}
Group {
@ -61,7 +61,7 @@ struct ModuleListView: View, Routable {
}
.onMove(perform: moveModules)
}
.themeSection(header: !profileEditor.modules.isEmpty ? Strings.Global.modules : nil)
.themeSection(header: !profileEditor.modules.isEmpty ? Strings.Global.Nouns.modules : nil)
}
.onDeleteCommand(perform: removeSelectedModule)
.toolbar(content: toolbarContent)

View File

@ -84,13 +84,13 @@ extension ProfileSplitView {
@ToolbarContentBuilder
func toolbarContent() -> some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button(Strings.Global.cancel, role: .cancel) {
Button(Strings.Global.Actions.cancel, role: .cancel) {
flow?.onCancelEditing()
}
}
ToolbarItem(placement: .confirmationAction) {
ProfileSaveButton(
title: Strings.Global.save,
title: Strings.Global.Actions.save,
errorModuleIds: $errorModuleIds
) {
try await flow?.onCommitEditing()

View File

@ -82,7 +82,7 @@ private extension ProviderContentModifier {
providerRows
refreshButton {
HStack {
Text(Strings.Providers.refreshInfrastructure)
Text(Strings.Views.Providers.refreshInfrastructure)
if providerManager.isLoading {
Spacer()
ProgressView()
@ -109,7 +109,7 @@ private extension ProviderContentModifier {
}
Spacer()
refreshButton {
Text(Strings.Providers.refreshInfrastructure)
Text(Strings.Views.Providers.refreshInfrastructure)
}
}
}
@ -148,9 +148,9 @@ private extension ProviderContentModifier {
var lastUpdatedString: String? {
guard let lastUpdate else {
return providerManager.isLoading ? Strings.Providers.LastUpdated.loading : nil
return providerManager.isLoading ? Strings.Views.Providers.LastUpdated.loading : nil
}
return Strings.Providers.lastUpdated(lastUpdate.localizedDescription(style: .timestamp))
return Strings.Views.Providers.lastUpdated(lastUpdate.localizedDescription(style: .timestamp))
}
}

View File

@ -43,19 +43,19 @@ struct ProviderPicker: View {
var body: some View {
Picker(selection: $providerId) {
if !providers.isEmpty {
Text(isRequired ? Strings.Providers.selectProvider : Strings.Providers.noProvider)
Text(isRequired ? Strings.Views.Providers.selectProvider : Strings.Views.Providers.noProvider)
.tag(nil as ProviderID?)
ForEach(providers, id: \.id) {
Text($0.description)
.tag($0.id as ProviderID?)
}
} else {
Text(isLoading ? Strings.Global.loading : Strings.Global.none)
Text(isLoading ? Strings.Global.Nouns.loading : Strings.Global.Nouns.none)
.tag(providerId) // tag always exists
}
} label: {
HStack {
Text(Strings.Global.provider)
Text(Strings.Global.Nouns.provider)
PurchaseRequiredButton(for: providerId, paywallReason: $paywallReason)
}
}

View File

@ -66,8 +66,8 @@ private extension VPNFiltersView {
}
var categoryPicker: some View {
Picker(Strings.Global.category, selection: categoryNameBinding) {
Text(Strings.Global.any)
Picker(Strings.Global.Nouns.category, selection: categoryNameBinding) {
Text(Strings.Global.Nouns.any)
.tag(nil as String?)
ForEach(model.categories, id: \.self) {
Text($0.capitalized)
@ -77,8 +77,8 @@ private extension VPNFiltersView {
}
var countryPicker: some View {
Picker(Strings.Global.country, selection: $model.filters.countryCode) {
Text(Strings.Global.any)
Picker(Strings.Global.Nouns.country, selection: $model.filters.countryCode) {
Text(Strings.Global.Nouns.any)
.tag(nil as String?)
ForEach(model.countries, id: \.code) {
ThemeCountryText($0.code, title: $0.description)
@ -88,8 +88,8 @@ private extension VPNFiltersView {
}
var presetPicker: some View {
Picker(Strings.Providers.Vpn.preset, selection: $model.filters.presetId) {
Text(Strings.Global.any)
Picker(Strings.Views.Providers.Vpn.preset, selection: $model.filters.presetId) {
Text(Strings.Global.Nouns.any)
.tag(nil as String?)
ForEach(model.presets, id: \.presetId) {
Text($0.description)
@ -99,11 +99,11 @@ private extension VPNFiltersView {
}
var favoritesToggle: some View {
Toggle(Strings.Providers.onlyFavorites, isOn: $model.onlyShowsFavorites)
Toggle(Strings.Views.Providers.onlyFavorites, isOn: $model.onlyShowsFavorites)
}
var clearFiltersButton: some View {
Button(Strings.Providers.clearFilters, role: .destructive) {
Button(Strings.Views.Providers.clearFilters, role: .destructive) {
model.filters = VPNFilters()
}
}

View File

@ -67,7 +67,7 @@ private extension VPNProviderContentModifier {
var providerEntityRow: some View {
NavigationLink(value: entityDestination) {
HStack {
Text(Strings.Global.server)
Text(Strings.Global.Nouns.server)
if let selectedEntity {
Spacer()
Text(selectedEntity.server.hostname ?? selectedEntity.server.serverId)

View File

@ -65,7 +65,7 @@ private extension VPNProviderServerCoordinator {
try await onSelect(entity)
} catch {
pp_log(.app, .fault, "Unable to select server \(server.serverId) for provider \(server.provider.id): \(error)")
errorHandler.handle(error, title: Strings.Providers.selectEntity)
errorHandler.handle(error, title: Strings.Views.Providers.selectEntity)
}
}
}

View File

@ -42,7 +42,7 @@ struct VPNProviderServerView<Configuration>: View where Configuration: ProviderC
let filtersWithSelection: Bool
var selectTitle = Strings.Providers.selectEntity
var selectTitle = Strings.Views.Providers.selectEntity
let onSelect: (VPNServer, VPNPreset<Configuration>) -> Void
@ -163,7 +163,7 @@ extension VPNProviderServerView {
await reloadServers(filters: filtersViewModel.filters)
} catch {
pp_log(.app, .error, "Unable to load VPN repository: \(error)")
errorHandler.handle(error, title: Strings.Global.servers)
errorHandler.handle(error, title: Strings.Global.Nouns.servers)
}
}
.onReceive(filtersViewModel.$filters.dropFirst()) { newValue in
@ -184,7 +184,7 @@ extension VPNProviderServerView {
private extension VPNProviderServerView.ServersView {
var title: String {
providerManager.provider(withId: providerId)?.description ?? Strings.Global.servers
providerManager.provider(withId: providerId)?.description ?? Strings.Global.Nouns.servers
}
var filteredServers: [VPNServer] {

View File

@ -75,7 +75,7 @@ private extension VPNProviderServerView {
func body(content: Content) -> some View {
NavigationStack {
content
.navigationTitle(Strings.Global.filters)
.navigationTitle(Strings.Global.Nouns.filters)
.themeNavigationDetail()
.toolbar {
ToolbarItem(placement: .cancellationAction) {
@ -127,7 +127,7 @@ private extension VPNProviderServerView.ServersSubview {
var listView: some View {
List {
Section {
Toggle(Strings.Providers.onlyFavorites, isOn: $filtersViewModel.onlyShowsFavorites)
Toggle(Strings.Views.Providers.onlyFavorites, isOn: $filtersViewModel.onlyShowsFavorites)
}
Group {
if isFiltering || !servers.isEmpty {
@ -142,7 +142,7 @@ private extension VPNProviderServerView.ServersSubview {
}
}
.themeSection(
header: filtersViewModel.filters.categoryName ?? Strings.Providers.Vpn.Category.any
header: filtersViewModel.filters.categoryName ?? Strings.Views.Providers.Vpn.Category.any
)
.onLoad {
if let selectedServer {
@ -153,7 +153,7 @@ private extension VPNProviderServerView.ServersSubview {
}
var emptyView: some View {
Text(Strings.Providers.Vpn.noServers)
Text(Strings.Views.Providers.Vpn.noServers)
}
}

View File

@ -77,13 +77,13 @@ extension VPNProviderServerView {
}
.width(10.0)
TableColumn(Strings.Global.region) { server in
TableColumn(Strings.Global.Nouns.region) { server in
ThemeCountryText(server.provider.countryCode, title: server.region)
.help(server.region)
.environmentObject(theme) // TODO: #873, Table loses environment
}
TableColumn(Strings.Global.address, value: \.address)
TableColumn(Strings.Global.Nouns.address, value: \.address)
TableColumn("􀋂") { server in
FavoriteToggle(

View File

@ -45,7 +45,7 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
return TabView {
profileView
.tabItem {
Text(Strings.Global.profile)
Text(Strings.Global.Nouns.profile)
}
// searchView

View File

@ -83,7 +83,7 @@ struct ActiveProfileView: View {
private extension ActiveProfileView {
var currentProfileView: some View {
Text(profile?.name ?? Strings.Views.Profiles.Rows.notInstalled)
Text(profile?.name ?? Strings.Views.App.Rows.notInstalled)
.font(.title)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .leading)
@ -100,18 +100,18 @@ private extension ActiveProfileView {
func detailView(for profile: Profile) -> some View {
VStack(spacing: 10) {
if let connectionType = profile.localizedDescription(optionalStyle: .connectionType) {
DetailRowView(title: Strings.Global.protocol) {
DetailRowView(title: Strings.Global.Nouns.protocol) {
Text(connectionType)
}
}
if let pair = profile.selectedProvider {
if let metadata = providerManager.provider(withId: pair.selection.id) {
DetailRowView(title: Strings.Global.provider) {
DetailRowView(title: Strings.Global.Nouns.provider) {
Text(metadata.description)
}
}
if let entity = pair.selection.entity {
DetailRowView(title: Strings.Global.country) {
DetailRowView(title: Strings.Global.Nouns.country) {
ThemeCountryText(entity.header.countryCode)
}
}
@ -135,7 +135,7 @@ private extension ActiveProfileView {
onProviderEntityRequired: onProviderEntityRequired,
onPurchaseRequired: onPurchaseRequired,
label: {
Text($0 ? Strings.Global.connect : Strings.Global.disconnect)
Text($0 ? Strings.Global.Actions.connect : Strings.Global.Actions.disconnect)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
}
@ -159,7 +159,7 @@ private extension ActiveProfileView {
Button {
isSwitching.toggle()
} label: {
Text(Strings.Global.select)
Text(Strings.Global.Actions.select)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
}

View File

@ -55,7 +55,7 @@ struct ProfileListView: View {
.listStyle(.grouped)
.scrollClipDisabled()
.themeProgress(if: false, isEmpty: !profileManager.hasProfiles) {
Text(Strings.Views.Profiles.Folders.noProfiles)
Text(Strings.Views.App.Folders.noProfiles)
.themeEmptyMessage()
}
}
@ -68,7 +68,7 @@ private extension ProfileListView {
}
var headerView: some View {
Text(Strings.Views.Profiles.Tv.header(Strings.Unlocalized.appName, Strings.Unlocalized.appleTV))
Text(Strings.Views.App.Tv.header(Strings.Unlocalized.appName, Strings.Unlocalized.appleTV))
.textCase(.none)
.foregroundStyle(.primary)
.font(.body)

View File

@ -126,8 +126,8 @@ private extension ProfileView {
InteractiveCoordinator(style: .inline(withCancel: false), manager: interactiveManager) {
errorHandler.handle(
$0,
title: Strings.Global.connection,
message: Strings.Views.Profiles.Errors.tunnel
title: Strings.Global.Nouns.connection,
message: Strings.Views.App.Errors.tunnel
)
}
.font(.body)

View File

@ -26,5 +26,5 @@
import Foundation
extension AppProduct {
static let providerPrefix = "providers."
static let providerPrefix = "views.providers."
}

View File

@ -32,7 +32,7 @@ extension AppFeature: LocalizableEntity {
let V = Strings.Features.self
switch self {
case .appleTV:
return V.appleTV(Strings.Unlocalized.appleTV)
return V.appletv(Strings.Unlocalized.appleTV)
case .dns:
return V.dns(Strings.Unlocalized.dns)
@ -50,7 +50,7 @@ extension AppFeature: LocalizableEntity {
return V.providers
case .routing:
return V.routing(Strings.Global.routing)
return V.routing(Strings.Global.Nouns.routing)
case .sharing:
return V.sharing(Strings.Unlocalized.iCloud)

View File

@ -32,7 +32,7 @@ extension ErrorHandler {
public static func `default`() -> ErrorHandler {
ErrorHandler(
defaultTitle: Strings.Unlocalized.appName,
dismissTitle: Strings.Global.ok,
dismissTitle: Strings.Global.Nouns.ok,
errorDescription: {
AppError($0).localizedDescription
},

View File

@ -37,7 +37,7 @@ extension TimeInterval: StyledLocalizableEntity {
if self > 0 {
return asTimeString
} else {
return Strings.Global.disabled
return Strings.Global.Nouns.disabled
}
}
}

View File

@ -44,10 +44,10 @@ extension ModuleType: LocalizableEntity {
return Strings.Unlocalized.httpProxy
case .ip:
return Strings.Global.routing
return Strings.Global.Nouns.routing
case .onDemand:
return Strings.Global.onDemand
return Strings.Global.Nouns.onDemand
default:
assertionFailure("Missing localization for ModuleType: \(rawValue)")

View File

@ -31,7 +31,7 @@ extension OpenVPN.PullMask: LocalizableEntity {
public var localizedDescription: String {
switch self {
case .routes:
return Strings.Global.routes
return Strings.Global.Nouns.routes
case .dns:
return Strings.Unlocalized.dns
@ -58,7 +58,7 @@ extension OpenVPN.CompressionFraming: LocalizableEntity {
public var localizedDescription: String {
switch self {
case .disabled:
return Strings.Global.disabled
return Strings.Global.Nouns.disabled
case .compLZO:
return Strings.Unlocalized.OpenVPN.compLZO
@ -67,7 +67,7 @@ extension OpenVPN.CompressionFraming: LocalizableEntity {
return Strings.Unlocalized.OpenVPN.compress
default:
return Strings.Global.unknown
return Strings.Global.Nouns.unknown
}
}
}
@ -76,7 +76,7 @@ extension OpenVPN.CompressionAlgorithm: LocalizableEntity {
public var localizedDescription: String {
switch self {
case .disabled:
return Strings.Global.disabled
return Strings.Global.Nouns.disabled
case .LZO:
return Strings.Unlocalized.OpenVPN.lzo
@ -85,7 +85,7 @@ extension OpenVPN.CompressionAlgorithm: LocalizableEntity {
return Strings.Entities.Openvpn.CompressionAlgorithm.other
default:
return Strings.Global.unknown
return Strings.Global.Nouns.unknown
}
}
}
@ -189,7 +189,7 @@ extension OpenVPN.Configuration.Builder: StyledOptionalLocalizableEntity {
private extension Optional where Wrapped == OpenVPN.TLSWrap {
var tlsWrapDescription: String {
guard let strategy = self?.strategy else {
return Strings.Global.disabled
return Strings.Global.Nouns.disabled
}
switch strategy {
case .auth:
@ -203,19 +203,19 @@ private extension Optional where Wrapped == OpenVPN.TLSWrap {
private extension Optional where Wrapped == Bool {
var ekuDescription: String {
let V = Strings.Global.self
let V = Strings.Global.Nouns.self
return (self ?? false) ? V.enabled : V.disabled
}
}
private extension Bool {
var randomizeEndpointDescription: String {
let V = Strings.Global.self
let V = Strings.Global.Nouns.self
return self ? V.enabled : V.disabled
}
var randomizeHostnamesDescription: String {
let V = Strings.Global.self
let V = Strings.Global.Nouns.self
return self ? V.enabled : V.disabled
}
}

View File

@ -82,25 +82,6 @@ extension TunnelStatus: LocalizableEntity {
}
}
extension ConnectionStatus: LocalizableEntity {
public var localizedDescription: String {
let V = Strings.Entities.ConnectionStatus.self
switch self {
case .disconnected:
return V.disconnected
case .connecting:
return V.connecting
case .connected:
return V.connected
case .disconnecting:
return V.disconnecting
}
}
}
extension DataCount: LocalizableEntity {
public var localizedDescription: String {
let down = received.descriptionAsDataUnit

View File

@ -11,14 +11,6 @@ import Foundation
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
public enum Strings {
public enum Alerts {
public enum Iap {
public enum Restricted {
/// Some features are unavailable in this build.
public static let message = Strings.tr("Localizable", "alerts.iap.restricted.message", fallback: "Some features are unavailable in this build.")
/// Restricted
public static let title = Strings.tr("Localizable", "alerts.iap.restricted.title", fallback: "Restricted")
}
}
public enum Import {
public enum Passphrase {
/// Enter passphrase for '%@'.
@ -30,25 +22,7 @@ public enum Strings {
}
}
}
public enum AppMenu {
public enum Items {
/// Quit %@
public static func quit(_ p1: Any) -> String {
return Strings.tr("Localizable", "app_menu.items.quit", String(describing: p1), fallback: "Quit %@")
}
}
}
public enum Entities {
public enum ConnectionStatus {
/// Connected
public static let connected = Strings.tr("Localizable", "entities.connection_status.connected", fallback: "Connected")
/// Connecting
public static let connecting = Strings.tr("Localizable", "entities.connection_status.connecting", fallback: "Connecting")
/// Disconnected
public static let disconnected = Strings.tr("Localizable", "entities.connection_status.disconnected", fallback: "Disconnected")
/// Disconnecting
public static let disconnecting = Strings.tr("Localizable", "entities.connection_status.disconnecting", fallback: "Disconnecting")
}
public enum Dns {
/// Search domains
public static let searchDomains = Strings.tr("Localizable", "entities.dns.search_domains", fallback: "Search domains")
@ -91,12 +65,6 @@ public enum Strings {
public static let `none` = Strings.tr("Localizable", "entities.openvpn.otp_method.none", fallback: "None")
}
}
public enum Profile {
public enum Name {
/// New profile
public static let new = Strings.tr("Localizable", "entities.profile.name.new", fallback: "New profile")
}
}
public enum TunnelStatus {
/// Activating
public static let activating = Strings.tr("Localizable", "entities.tunnel_status.activating", fallback: "Activating")
@ -174,8 +142,8 @@ public enum Strings {
}
public enum Features {
/// %@
public static func appleTV(_ p1: Any) -> String {
return Strings.tr("Localizable", "features.appleTV", String(describing: p1), fallback: "%@")
public static func appletv(_ p1: Any) -> String {
return Strings.tr("Localizable", "features.appletv", String(describing: p1), fallback: "%@")
}
/// %@ Settings
public static func dns(_ p1: Any) -> String {
@ -183,12 +151,12 @@ public enum Strings {
}
/// %@ Settings
public static func httpProxy(_ p1: Any) -> String {
return Strings.tr("Localizable", "features.httpProxy", String(describing: p1), fallback: "%@ Settings")
return Strings.tr("Localizable", "features.http_proxy", String(describing: p1), fallback: "%@ Settings")
}
/// Interactive Login
public static let interactiveLogin = Strings.tr("Localizable", "features.interactiveLogin", fallback: "Interactive Login")
/// On-Demand Rules
public static let onDemand = Strings.tr("Localizable", "features.onDemand", fallback: "On-Demand Rules")
public static let onDemand = Strings.tr("Localizable", "features.on_demand", fallback: "On-Demand Rules")
/// All Providers
public static let providers = Strings.tr("Localizable", "features.providers", fallback: "All Providers")
/// Custom %@
@ -201,156 +169,160 @@ public enum Strings {
}
}
public enum Global {
/// About
public static let about = Strings.tr("Localizable", "global.about", fallback: "About")
/// Account
public static let account = Strings.tr("Localizable", "global.account", fallback: "Account")
/// Address
public static let address = Strings.tr("Localizable", "global.address", fallback: "Address")
/// Addresses
public static let addresses = Strings.tr("Localizable", "global.addresses", fallback: "Addresses")
/// Any
public static let any = Strings.tr("Localizable", "global.any", fallback: "Any")
/// Cancel
public static let cancel = Strings.tr("Localizable", "global.cancel", fallback: "Cancel")
/// Category
public static let category = Strings.tr("Localizable", "global.category", fallback: "Category")
/// Certificate
public static let certificate = Strings.tr("Localizable", "global.certificate", fallback: "Certificate")
/// Compression
public static let compression = Strings.tr("Localizable", "global.compression", fallback: "Compression")
/// Connect
public static let connect = Strings.tr("Localizable", "global.connect", fallback: "Connect")
/// Connection
public static let connection = Strings.tr("Localizable", "global.connection", fallback: "Connection")
/// Country
public static let country = Strings.tr("Localizable", "global.country", fallback: "Country")
/// Default
public static let `default` = Strings.tr("Localizable", "global.default", fallback: "Default")
/// Delete
public static let delete = Strings.tr("Localizable", "global.delete", fallback: "Delete")
/// Destination
public static let destination = Strings.tr("Localizable", "global.destination", fallback: "Destination")
/// Disable
public static let disable = Strings.tr("Localizable", "global.disable", fallback: "Disable")
/// Disabled
public static let disabled = Strings.tr("Localizable", "global.disabled", fallback: "Disabled")
/// Disconnect
public static let disconnect = Strings.tr("Localizable", "global.disconnect", fallback: "Disconnect")
/// Don't ask again
public static let doNotAskAgain = Strings.tr("Localizable", "global.do_not_ask_again", fallback: "Don't ask again")
/// Domain
public static let domain = Strings.tr("Localizable", "global.domain", fallback: "Domain")
/// Done
public static let done = Strings.tr("Localizable", "global.done", fallback: "Done")
/// Duplicate
public static let duplicate = Strings.tr("Localizable", "global.duplicate", fallback: "Duplicate")
/// Edit
public static let edit = Strings.tr("Localizable", "global.edit", fallback: "Edit")
/// Empty
public static let empty = Strings.tr("Localizable", "global.empty", fallback: "Empty")
/// Enable
public static let enable = Strings.tr("Localizable", "global.enable", fallback: "Enable")
/// Enabled
public static let enabled = Strings.tr("Localizable", "global.enabled", fallback: "Enabled")
/// Endpoint
public static let endpoint = Strings.tr("Localizable", "global.endpoint", fallback: "Endpoint")
/// Filters
public static let filters = Strings.tr("Localizable", "global.filters", fallback: "Filters")
/// Folder
public static let folder = Strings.tr("Localizable", "global.folder", fallback: "Folder")
/// Gateway
public static let gateway = Strings.tr("Localizable", "global.gateway", fallback: "Gateway")
/// General
public static let general = Strings.tr("Localizable", "global.general", fallback: "General")
/// Hide
public static let hide = Strings.tr("Localizable", "global.hide", fallback: "Hide")
/// Hostname
public static let hostname = Strings.tr("Localizable", "global.hostname", fallback: "Hostname")
/// Interface
public static let interface = Strings.tr("Localizable", "global.interface", fallback: "Interface")
/// Keep-alive
public static let keepAlive = Strings.tr("Localizable", "global.keep_alive", fallback: "Keep-alive")
/// Key
public static let key = Strings.tr("Localizable", "global.key", fallback: "Key")
/// Last update
public static let lastUpdate = Strings.tr("Localizable", "global.last_update", fallback: "Last update")
/// Loading
public static let loading = Strings.tr("Localizable", "global.loading", fallback: "Loading")
/// Method
public static let method = Strings.tr("Localizable", "global.method", fallback: "Method")
/// Modules
public static let modules = Strings.tr("Localizable", "global.modules", fallback: "Modules")
/// %d seconds
public static func nSeconds(_ p1: Int) -> String {
return Strings.tr("Localizable", "global.n_seconds", p1, fallback: "%d seconds")
public enum Actions {
/// Cancel
public static let cancel = Strings.tr("Localizable", "global.actions.cancel", fallback: "Cancel")
/// Connect
public static let connect = Strings.tr("Localizable", "global.actions.connect", fallback: "Connect")
/// Delete
public static let delete = Strings.tr("Localizable", "global.actions.delete", fallback: "Delete")
/// Disable
public static let disable = Strings.tr("Localizable", "global.actions.disable", fallback: "Disable")
/// Disconnect
public static let disconnect = Strings.tr("Localizable", "global.actions.disconnect", fallback: "Disconnect")
/// Duplicate
public static let duplicate = Strings.tr("Localizable", "global.actions.duplicate", fallback: "Duplicate")
/// Edit
public static let edit = Strings.tr("Localizable", "global.actions.edit", fallback: "Edit")
/// Enable
public static let enable = Strings.tr("Localizable", "global.actions.enable", fallback: "Enable")
/// Hide
public static let hide = Strings.tr("Localizable", "global.actions.hide", fallback: "Hide")
/// Purchase
public static let purchase = Strings.tr("Localizable", "global.actions.purchase", fallback: "Purchase")
/// Delete
public static let remove = Strings.tr("Localizable", "global.actions.remove", fallback: "Delete")
/// Restart
public static let restart = Strings.tr("Localizable", "global.actions.restart", fallback: "Restart")
/// Save
public static let save = Strings.tr("Localizable", "global.actions.save", fallback: "Save")
/// Select
public static let select = Strings.tr("Localizable", "global.actions.select", fallback: "Select")
/// Show
public static let show = Strings.tr("Localizable", "global.actions.show", fallback: "Show")
}
public enum Nouns {
/// About
public static let about = Strings.tr("Localizable", "global.nouns.about", fallback: "About")
/// Account
public static let account = Strings.tr("Localizable", "global.nouns.account", fallback: "Account")
/// Address
public static let address = Strings.tr("Localizable", "global.nouns.address", fallback: "Address")
/// Addresses
public static let addresses = Strings.tr("Localizable", "global.nouns.addresses", fallback: "Addresses")
/// Any
public static let any = Strings.tr("Localizable", "global.nouns.any", fallback: "Any")
/// Category
public static let category = Strings.tr("Localizable", "global.nouns.category", fallback: "Category")
/// Certificate
public static let certificate = Strings.tr("Localizable", "global.nouns.certificate", fallback: "Certificate")
/// Compression
public static let compression = Strings.tr("Localizable", "global.nouns.compression", fallback: "Compression")
/// Connection
public static let connection = Strings.tr("Localizable", "global.nouns.connection", fallback: "Connection")
/// Country
public static let country = Strings.tr("Localizable", "global.nouns.country", fallback: "Country")
/// Default
public static let `default` = Strings.tr("Localizable", "global.nouns.default", fallback: "Default")
/// Destination
public static let destination = Strings.tr("Localizable", "global.nouns.destination", fallback: "Destination")
/// Disabled
public static let disabled = Strings.tr("Localizable", "global.nouns.disabled", fallback: "Disabled")
/// Don't ask again
public static let doNotAskAgain = Strings.tr("Localizable", "global.nouns.do_not_ask_again", fallback: "Don't ask again")
/// Domain
public static let domain = Strings.tr("Localizable", "global.nouns.domain", fallback: "Domain")
/// Done
public static let done = Strings.tr("Localizable", "global.nouns.done", fallback: "Done")
/// Empty
public static let empty = Strings.tr("Localizable", "global.nouns.empty", fallback: "Empty")
/// Enabled
public static let enabled = Strings.tr("Localizable", "global.nouns.enabled", fallback: "Enabled")
/// Endpoint
public static let endpoint = Strings.tr("Localizable", "global.nouns.endpoint", fallback: "Endpoint")
/// Filters
public static let filters = Strings.tr("Localizable", "global.nouns.filters", fallback: "Filters")
/// Folder
public static let folder = Strings.tr("Localizable", "global.nouns.folder", fallback: "Folder")
/// Gateway
public static let gateway = Strings.tr("Localizable", "global.nouns.gateway", fallback: "Gateway")
/// General
public static let general = Strings.tr("Localizable", "global.nouns.general", fallback: "General")
/// Hostname
public static let hostname = Strings.tr("Localizable", "global.nouns.hostname", fallback: "Hostname")
/// Interface
public static let interface = Strings.tr("Localizable", "global.nouns.interface", fallback: "Interface")
/// Keep-alive
public static let keepAlive = Strings.tr("Localizable", "global.nouns.keep_alive", fallback: "Keep-alive")
/// Key
public static let key = Strings.tr("Localizable", "global.nouns.key", fallback: "Key")
/// Last update
public static let lastUpdate = Strings.tr("Localizable", "global.nouns.last_update", fallback: "Last update")
/// Loading
public static let loading = Strings.tr("Localizable", "global.nouns.loading", fallback: "Loading")
/// Method
public static let method = Strings.tr("Localizable", "global.nouns.method", fallback: "Method")
/// Modules
public static let modules = Strings.tr("Localizable", "global.nouns.modules", fallback: "Modules")
/// %d seconds
public static func nSeconds(_ p1: Int) -> String {
return Strings.tr("Localizable", "global.nouns.n_seconds", p1, fallback: "%d seconds")
}
/// Name
public static let name = Strings.tr("Localizable", "global.nouns.name", fallback: "Name")
/// Networks
public static let networks = Strings.tr("Localizable", "global.nouns.networks", fallback: "Networks")
/// No content
public static let noContent = Strings.tr("Localizable", "global.nouns.no_content", fallback: "No content")
/// No selection
public static let noSelection = Strings.tr("Localizable", "global.nouns.no_selection", fallback: "No selection")
/// None
public static let `none` = Strings.tr("Localizable", "global.nouns.none", fallback: "None")
/// OK
public static let ok = Strings.tr("Localizable", "global.nouns.ok", fallback: "OK")
/// On-demand
public static let onDemand = Strings.tr("Localizable", "global.nouns.on_demand", fallback: "On-demand")
/// Other
public static let other = Strings.tr("Localizable", "global.nouns.other", fallback: "Other")
/// Password
public static let password = Strings.tr("Localizable", "global.nouns.password", fallback: "Password")
/// Port
public static let port = Strings.tr("Localizable", "global.nouns.port", fallback: "Port")
/// Private key
public static let privateKey = Strings.tr("Localizable", "global.nouns.private_key", fallback: "Private key")
/// Profile
public static let profile = Strings.tr("Localizable", "global.nouns.profile", fallback: "Profile")
/// Protocol
public static let `protocol` = Strings.tr("Localizable", "global.nouns.protocol", fallback: "Protocol")
/// Provider
public static let provider = Strings.tr("Localizable", "global.nouns.provider", fallback: "Provider")
/// Public key
public static let publicKey = Strings.tr("Localizable", "global.nouns.public_key", fallback: "Public key")
/// Region
public static let region = Strings.tr("Localizable", "global.nouns.region", fallback: "Region")
/// Route
public static let route = Strings.tr("Localizable", "global.nouns.route", fallback: "Route")
/// Routes
public static let routes = Strings.tr("Localizable", "global.nouns.routes", fallback: "Routes")
/// Routing
public static let routing = Strings.tr("Localizable", "global.nouns.routing", fallback: "Routing")
/// Server
public static let server = Strings.tr("Localizable", "global.nouns.server", fallback: "Server")
/// Servers
public static let servers = Strings.tr("Localizable", "global.nouns.servers", fallback: "Servers")
/// Settings
public static let settings = Strings.tr("Localizable", "global.nouns.settings", fallback: "Settings")
/// Status
public static let status = Strings.tr("Localizable", "global.nouns.status", fallback: "Status")
/// Subnet
public static let subnet = Strings.tr("Localizable", "global.nouns.subnet", fallback: "Subnet")
/// Unknown
public static let unknown = Strings.tr("Localizable", "global.nouns.unknown", fallback: "Unknown")
/// Username
public static let username = Strings.tr("Localizable", "global.nouns.username", fallback: "Username")
/// Version
public static let version = Strings.tr("Localizable", "global.nouns.version", fallback: "Version")
}
/// Name
public static let name = Strings.tr("Localizable", "global.name", fallback: "Name")
/// Networks
public static let networks = Strings.tr("Localizable", "global.networks", fallback: "Networks")
/// No content
public static let noContent = Strings.tr("Localizable", "global.no_content", fallback: "No content")
/// No selection
public static let noSelection = Strings.tr("Localizable", "global.no_selection", fallback: "No selection")
/// None
public static let `none` = Strings.tr("Localizable", "global.none", fallback: "None")
/// OK
public static let ok = Strings.tr("Localizable", "global.ok", fallback: "OK")
/// On-demand
public static let onDemand = Strings.tr("Localizable", "global.on_demand", fallback: "On-demand")
/// Other
public static let other = Strings.tr("Localizable", "global.other", fallback: "Other")
/// Password
public static let password = Strings.tr("Localizable", "global.password", fallback: "Password")
/// Port
public static let port = Strings.tr("Localizable", "global.port", fallback: "Port")
/// Private key
public static let privateKey = Strings.tr("Localizable", "global.private_key", fallback: "Private key")
/// Profile
public static let profile = Strings.tr("Localizable", "global.profile", fallback: "Profile")
/// Protocol
public static let `protocol` = Strings.tr("Localizable", "global.protocol", fallback: "Protocol")
/// Provider
public static let provider = Strings.tr("Localizable", "global.provider", fallback: "Provider")
/// Public key
public static let publicKey = Strings.tr("Localizable", "global.public_key", fallback: "Public key")
/// Purchase
public static let purchase = Strings.tr("Localizable", "global.purchase", fallback: "Purchase")
/// Region
public static let region = Strings.tr("Localizable", "global.region", fallback: "Region")
/// Delete
public static let remove = Strings.tr("Localizable", "global.remove", fallback: "Delete")
/// Restart
public static let restart = Strings.tr("Localizable", "global.restart", fallback: "Restart")
/// Route
public static let route = Strings.tr("Localizable", "global.route", fallback: "Route")
/// Routes
public static let routes = Strings.tr("Localizable", "global.routes", fallback: "Routes")
/// Routing
public static let routing = Strings.tr("Localizable", "global.routing", fallback: "Routing")
/// Save
public static let save = Strings.tr("Localizable", "global.save", fallback: "Save")
/// Select
public static let select = Strings.tr("Localizable", "global.select", fallback: "Select")
/// Server
public static let server = Strings.tr("Localizable", "global.server", fallback: "Server")
/// Servers
public static let servers = Strings.tr("Localizable", "global.servers", fallback: "Servers")
/// Settings
public static let settings = Strings.tr("Localizable", "global.settings", fallback: "Settings")
/// Show
public static let show = Strings.tr("Localizable", "global.show", fallback: "Show")
/// Status
public static let status = Strings.tr("Localizable", "global.status", fallback: "Status")
/// Subnet
public static let subnet = Strings.tr("Localizable", "global.subnet", fallback: "Subnet")
/// Unknown
public static let unknown = Strings.tr("Localizable", "global.unknown", fallback: "Unknown")
/// Username
public static let username = Strings.tr("Localizable", "global.username", fallback: "Username")
/// Version
public static let version = Strings.tr("Localizable", "global.version", fallback: "Version")
}
public enum Modules {
public enum Dns {
@ -366,16 +338,16 @@ public enum Strings {
public enum General {
public enum Rows {
/// %@
public static func appleTv(_ p1: Any) -> String {
return Strings.tr("Localizable", "modules.general.rows.apple_tv", String(describing: p1), fallback: "%@")
public static func appletv(_ p1: Any) -> String {
return Strings.tr("Localizable", "modules.general.rows.appletv", String(describing: p1), fallback: "%@")
}
/// Import from file...
public static let importFromFile = Strings.tr("Localizable", "modules.general.rows.import_from_file", fallback: "Import from file...")
/// Enabled
public static let shared = Strings.tr("Localizable", "modules.general.rows.shared", fallback: "Enabled")
public enum AppleTv {
public enum Appletv {
/// Drop TV restriction
public static let purchase = Strings.tr("Localizable", "modules.general.rows.apple_tv.purchase", fallback: "Drop TV restriction")
public static let purchase = Strings.tr("Localizable", "modules.general.rows.appletv.purchase", fallback: "Drop TV restriction")
}
public enum Shared {
/// Share on iCloud
@ -514,44 +486,6 @@ public enum Strings {
public static let providerKey = Strings.tr("Localizable", "modules.wireguard.provider_key", fallback: "Private key")
}
}
public enum Paywall {
public enum Alerts {
public enum Pending {
/// The purchase is pending external confirmation. The feature will be credited upon approval.
public static let message = Strings.tr("Localizable", "paywall.alerts.pending.message", fallback: "The purchase is pending external confirmation. The feature will be credited upon approval.")
}
}
public enum Rows {
/// Restore purchases
public static let restorePurchases = Strings.tr("Localizable", "paywall.rows.restore_purchases", fallback: "Restore purchases")
}
public enum Sections {
public enum Features {
public enum Other {
/// Also included
public static let header = Strings.tr("Localizable", "paywall.sections.features.other.header", fallback: "Also included")
}
public enum Required {
/// Required features
public static let header = Strings.tr("Localizable", "paywall.sections.features.required.header", fallback: "Required features")
}
}
public enum OneTime {
/// One-time purchase
public static let header = Strings.tr("Localizable", "paywall.sections.one_time.header", fallback: "One-time purchase")
}
public enum Recurring {
/// All features
public static let header = Strings.tr("Localizable", "paywall.sections.recurring.header", fallback: "All features")
}
public enum Restore {
/// If you bought this app or feature in the past, you can restore your purchases.
public static let footer = Strings.tr("Localizable", "paywall.sections.restore.footer", fallback: "If you bought this app or feature in the past, you can restore your purchases.")
/// Restore
public static let header = Strings.tr("Localizable", "paywall.sections.restore.header", fallback: "Restore")
}
}
}
public enum Placeholders {
/// secret
public static let secret = Strings.tr("Localizable", "placeholders.secret", fallback: "secret")
@ -566,38 +500,6 @@ public enum Strings {
public static let name = Strings.tr("Localizable", "placeholders.profile.name", fallback: "My profile")
}
}
public enum Providers {
/// Clear filters
public static let clearFilters = Strings.tr("Localizable", "providers.clear_filters", fallback: "Clear filters")
/// Last updated on %@
public static func lastUpdated(_ p1: Any) -> String {
return Strings.tr("Localizable", "providers.last_updated", String(describing: p1), fallback: "Last updated on %@")
}
/// None
public static let noProvider = Strings.tr("Localizable", "providers.no_provider", fallback: "None")
/// Only favorites
public static let onlyFavorites = Strings.tr("Localizable", "providers.only_favorites", fallback: "Only favorites")
/// Refresh infrastructure
public static let refreshInfrastructure = Strings.tr("Localizable", "providers.refresh_infrastructure", fallback: "Refresh infrastructure")
/// Select
public static let selectEntity = Strings.tr("Localizable", "providers.select_entity", fallback: "Select")
/// Select a provider
public static let selectProvider = Strings.tr("Localizable", "providers.select_provider", fallback: "Select a provider")
public enum LastUpdated {
/// Loading...
public static let loading = Strings.tr("Localizable", "providers.last_updated.loading", fallback: "Loading...")
}
public enum Vpn {
/// No servers
public static let noServers = Strings.tr("Localizable", "providers.vpn.no_servers", fallback: "No servers")
/// Preset
public static let preset = Strings.tr("Localizable", "providers.vpn.preset", fallback: "Preset")
public enum Category {
/// All categories
public static let any = Strings.tr("Localizable", "providers.vpn.category.any", fallback: "All categories")
}
}
}
public enum Theme {
public enum Confirmation {
/// Cancel
@ -614,26 +516,6 @@ public enum Strings {
}
}
}
public enum Ui {
public enum ConnectionStatus {
/// (on-demand)
public static let onDemandSuffix = Strings.tr("Localizable", "ui.connection_status.on_demand_suffix", fallback: " (on-demand)")
}
public enum ProfileContext {
/// Connect to...
public static let connectTo = Strings.tr("Localizable", "ui.profile_context.connect_to", fallback: "Connect to...")
}
public enum PurchaseRequired {
public enum Purchase {
/// Purchase required
public static let help = Strings.tr("Localizable", "ui.purchase_required.purchase.help", fallback: "Purchase required")
}
public enum Restricted {
/// Feature is restricted
public static let help = Strings.tr("Localizable", "ui.purchase_required.restricted.help", fallback: "Feature is restricted")
}
}
}
public enum Views {
public enum About {
/// About
@ -675,6 +557,64 @@ public enum Strings {
public static let resources = Strings.tr("Localizable", "views.about.sections.resources", fallback: "Resources")
}
}
public enum App {
public enum Errors {
/// Unable to duplicate profile '%@'.
public static func duplicate(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.app.errors.duplicate", String(describing: p1), fallback: "Unable to duplicate profile '%@'.")
}
/// Unable to import profiles.
public static let `import` = Strings.tr("Localizable", "views.app.errors.import", fallback: "Unable to import profiles.")
/// Unable to execute tunnel operation.
public static let tunnel = Strings.tr("Localizable", "views.app.errors.tunnel", fallback: "Unable to execute tunnel operation.")
}
public enum Folders {
/// Installed profile
public static let activeProfile = Strings.tr("Localizable", "views.app.folders.active_profile", fallback: "Installed profile")
/// Add profile
public static let addProfile = Strings.tr("Localizable", "views.app.folders.add_profile", fallback: "Add profile")
/// My profiles
public static let `default` = Strings.tr("Localizable", "views.app.folders.default", fallback: "My profiles")
/// No profiles
public static let noProfiles = Strings.tr("Localizable", "views.app.folders.no_profiles", fallback: "No profiles")
public enum NoProfiles {
/// Migrate old profiles...
public static let migrate = Strings.tr("Localizable", "views.app.folders.no_profiles.migrate", fallback: "Migrate old profiles...")
}
}
public enum ProfileContext {
/// Connect to...
public static let connectTo = Strings.tr("Localizable", "views.app.profile_context.connect_to", fallback: "Connect to...")
}
public enum Rows {
/// No active modules
public static let noModules = Strings.tr("Localizable", "views.app.rows.no_modules", fallback: "No active modules")
/// Select a profile
public static let notInstalled = Strings.tr("Localizable", "views.app.rows.not_installed", fallback: "Select a profile")
}
public enum Toolbar {
/// Import profile
public static let importProfile = Strings.tr("Localizable", "views.app.toolbar.import_profile", fallback: "Import profile")
/// Migrate profiles
public static let migrateProfiles = Strings.tr("Localizable", "views.app.toolbar.migrate_profiles", fallback: "Migrate profiles")
/// New profile
public static let newProfile = Strings.tr("Localizable", "views.app.toolbar.new_profile", fallback: "New profile")
}
public enum Tv {
/// Open %@ on your iOS or macOS device and enable the "%@" toggle of a profile to make it appear here.
public static func header(_ p1: Any, _ p2: Any) -> String {
return Strings.tr("Localizable", "views.app.tv.header", String(describing: p1), String(describing: p2), fallback: "Open %@ on your iOS or macOS device and enable the \"%@\" toggle of a profile to make it appear here.")
}
}
}
public enum AppMenu {
public enum Items {
/// Quit %@
public static func quit(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.app_menu.items.quit", String(describing: p1), fallback: "Quit %@")
}
}
}
public enum Diagnostics {
/// Diagnostics
public static let title = Strings.tr("Localizable", "views.diagnostics.title", fallback: "Diagnostics")
@ -729,36 +669,106 @@ public enum Strings {
}
}
}
public enum Migrate {
public enum Migration {
/// Nothing to migrate
public static let noProfiles = Strings.tr("Localizable", "views.migrate.no_profiles", fallback: "Nothing to migrate")
public static let noProfiles = Strings.tr("Localizable", "views.migration.no_profiles", fallback: "Nothing to migrate")
/// Migrate
public static let title = Strings.tr("Localizable", "views.migrate.title", fallback: "Migrate")
public static let title = Strings.tr("Localizable", "views.migration.title", fallback: "Migrate")
public enum Alerts {
public enum Delete {
/// Do you want to discard these profiles? You will not be able to recover them later.
///
/// %@
public static func message(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.migrate.alerts.delete.message", String(describing: p1), fallback: "Do you want to discard these profiles? You will not be able to recover them later.\n\n%@")
return Strings.tr("Localizable", "views.migration.alerts.delete.message", String(describing: p1), fallback: "Do you want to discard these profiles? You will not be able to recover them later.\n\n%@")
}
}
}
public enum Items {
/// Discard
public static let discard = Strings.tr("Localizable", "views.migrate.items.discard", fallback: "Discard")
public static let discard = Strings.tr("Localizable", "views.migration.items.discard", fallback: "Discard")
/// Proceed
public static let migrate = Strings.tr("Localizable", "views.migrate.items.migrate", fallback: "Proceed")
public static let migrate = Strings.tr("Localizable", "views.migration.items.migrate", fallback: "Proceed")
}
public enum Sections {
public enum Main {
/// Select below the profiles from old versions of %@ that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later.
public static func header(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.migrate.sections.main.header", String(describing: p1), fallback: "Select below the profiles from old versions of %@ that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later.")
return Strings.tr("Localizable", "views.migration.sections.main.header", String(describing: p1), fallback: "Select below the profiles from old versions of %@ that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later.")
}
}
}
}
public enum Paywall {
public enum Alerts {
public enum Pending {
/// The purchase is pending external confirmation. The feature will be credited upon approval.
public static let message = Strings.tr("Localizable", "views.paywall.alerts.pending.message", fallback: "The purchase is pending external confirmation. The feature will be credited upon approval.")
}
public enum Restricted {
/// Some features are unavailable in this build.
public static let message = Strings.tr("Localizable", "views.paywall.alerts.restricted.message", fallback: "Some features are unavailable in this build.")
/// Restricted
public static let title = Strings.tr("Localizable", "views.paywall.alerts.restricted.title", fallback: "Restricted")
}
}
public enum Rows {
/// Restore purchases
public static let restorePurchases = Strings.tr("Localizable", "views.paywall.rows.restore_purchases", fallback: "Restore purchases")
}
public enum Sections {
public enum Features {
public enum Other {
/// Also included
public static let header = Strings.tr("Localizable", "views.paywall.sections.features.other.header", fallback: "Also included")
}
public enum Required {
/// Required features
public static let header = Strings.tr("Localizable", "views.paywall.sections.features.required.header", fallback: "Required features")
}
}
public enum OneTime {
/// One-time purchase
public static let header = Strings.tr("Localizable", "views.paywall.sections.one_time.header", fallback: "One-time purchase")
}
public enum Recurring {
/// All features
public static let header = Strings.tr("Localizable", "views.paywall.sections.recurring.header", fallback: "All features")
}
public enum Restore {
/// If you bought this app or feature in the past, you can restore your purchases.
public static let footer = Strings.tr("Localizable", "views.paywall.sections.restore.footer", fallback: "If you bought this app or feature in the past, you can restore your purchases.")
/// Restore
public static let header = Strings.tr("Localizable", "views.paywall.sections.restore.header", fallback: "Restore")
}
}
}
public enum Preferences {
/// Erase iCloud store
public static let eraseIcloud = Strings.tr("Localizable", "views.preferences.erase_icloud", fallback: "Erase iCloud store")
/// Keep in menu bar
public static let keepsInMenu = Strings.tr("Localizable", "views.preferences.keeps_in_menu", fallback: "Keep in menu bar")
/// Launch on login
public static let launchesOnLogin = Strings.tr("Localizable", "views.preferences.launches_on_login", fallback: "Launch on login")
/// Lock in background
public static let locksInBackground = Strings.tr("Localizable", "views.preferences.locks_in_background", fallback: "Lock in background")
public enum EraseIcloud {
/// To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.
public static let footer = Strings.tr("Localizable", "views.preferences.erase_icloud.footer", fallback: "To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.")
}
public enum KeepsInMenu {
/// Enable this to keep the app in the menu bar after closing it.
public static let footer = Strings.tr("Localizable", "views.preferences.keeps_in_menu.footer", fallback: "Enable this to keep the app in the menu bar after closing it.")
}
public enum LaunchesOnLogin {
/// Open the app in background after login.
public static let footer = Strings.tr("Localizable", "views.preferences.launches_on_login.footer", fallback: "Open the app in background after login.")
}
public enum LocksInBackground {
/// Lock the app with FaceID when sent to the background.
public static let footer = Strings.tr("Localizable", "views.preferences.locks_in_background.footer", fallback: "Lock the app with FaceID when sent to the background.")
}
}
public enum Profile {
public enum Alerts {
public enum Purchase {
@ -783,76 +793,52 @@ public enum Strings {
public static let addModule = Strings.tr("Localizable", "views.profile.rows.add_module", fallback: "Add module")
}
}
public enum Profiles {
public enum Errors {
/// Unable to duplicate profile '%@'.
public static func duplicate(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.profiles.errors.duplicate", String(describing: p1), fallback: "Unable to duplicate profile '%@'.")
}
/// Unable to import profiles.
public static let `import` = Strings.tr("Localizable", "views.profiles.errors.import", fallback: "Unable to import profiles.")
/// Unable to execute tunnel operation.
public static let tunnel = Strings.tr("Localizable", "views.profiles.errors.tunnel", fallback: "Unable to execute tunnel operation.")
public enum Providers {
/// Clear filters
public static let clearFilters = Strings.tr("Localizable", "views.providers.clear_filters", fallback: "Clear filters")
/// Last updated on %@
public static func lastUpdated(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.providers.last_updated", String(describing: p1), fallback: "Last updated on %@")
}
public enum Folders {
/// Installed profile
public static let activeProfile = Strings.tr("Localizable", "views.profiles.folders.active_profile", fallback: "Installed profile")
/// Add profile
public static let addProfile = Strings.tr("Localizable", "views.profiles.folders.add_profile", fallback: "Add profile")
/// My profiles
public static let `default` = Strings.tr("Localizable", "views.profiles.folders.default", fallback: "My profiles")
/// No profiles
public static let noProfiles = Strings.tr("Localizable", "views.profiles.folders.no_profiles", fallback: "No profiles")
public enum NoProfiles {
/// Migrate old profiles...
public static let migrate = Strings.tr("Localizable", "views.profiles.folders.no_profiles.migrate", fallback: "Migrate old profiles...")
}
/// None
public static let noProvider = Strings.tr("Localizable", "views.providers.no_provider", fallback: "None")
/// Only favorites
public static let onlyFavorites = Strings.tr("Localizable", "views.providers.only_favorites", fallback: "Only favorites")
/// Refresh infrastructure
public static let refreshInfrastructure = Strings.tr("Localizable", "views.providers.refresh_infrastructure", fallback: "Refresh infrastructure")
/// Select
public static let selectEntity = Strings.tr("Localizable", "views.providers.select_entity", fallback: "Select")
/// Select a provider
public static let selectProvider = Strings.tr("Localizable", "views.providers.select_provider", fallback: "Select a provider")
public enum LastUpdated {
/// Loading...
public static let loading = Strings.tr("Localizable", "views.providers.last_updated.loading", fallback: "Loading...")
}
public enum Rows {
/// No active modules
public static let noModules = Strings.tr("Localizable", "views.profiles.rows.no_modules", fallback: "No active modules")
/// Select a profile
public static let notInstalled = Strings.tr("Localizable", "views.profiles.rows.not_installed", fallback: "Select a profile")
}
public enum Toolbar {
/// Import profile
public static let importProfile = Strings.tr("Localizable", "views.profiles.toolbar.import_profile", fallback: "Import profile")
/// Migrate profiles
public static let migrateProfiles = Strings.tr("Localizable", "views.profiles.toolbar.migrate_profiles", fallback: "Migrate profiles")
/// New profile
public static let newProfile = Strings.tr("Localizable", "views.profiles.toolbar.new_profile", fallback: "New profile")
}
public enum Tv {
/// Open %@ on your iOS or macOS device and enable the "%@" toggle of a profile to make it appear here.
public static func header(_ p1: Any, _ p2: Any) -> String {
return Strings.tr("Localizable", "views.profiles.tv.header", String(describing: p1), String(describing: p2), fallback: "Open %@ on your iOS or macOS device and enable the \"%@\" toggle of a profile to make it appear here.")
public enum Vpn {
/// No servers
public static let noServers = Strings.tr("Localizable", "views.providers.vpn.no_servers", fallback: "No servers")
/// Preset
public static let preset = Strings.tr("Localizable", "views.providers.vpn.preset", fallback: "Preset")
public enum Category {
/// All categories
public static let any = Strings.tr("Localizable", "views.providers.vpn.category.any", fallback: "All categories")
}
}
}
public enum Settings {
/// Erase iCloud store
public static let eraseIcloud = Strings.tr("Localizable", "views.settings.erase_icloud", fallback: "Erase iCloud store")
/// Keep in menu bar
public static let keepsInMenu = Strings.tr("Localizable", "views.settings.keeps_in_menu", fallback: "Keep in menu bar")
/// Launch on login
public static let launchesOnLogin = Strings.tr("Localizable", "views.settings.launches_on_login", fallback: "Launch on login")
/// Lock in background
public static let locksInBackground = Strings.tr("Localizable", "views.settings.locks_in_background", fallback: "Lock in background")
public enum EraseIcloud {
/// To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.
public static let footer = Strings.tr("Localizable", "views.settings.erase_icloud.footer", fallback: "To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.")
public enum Ui {
public enum ConnectionStatus {
/// (on-demand)
public static let onDemandSuffix = Strings.tr("Localizable", "views.ui.connection_status.on_demand_suffix", fallback: " (on-demand)")
}
public enum KeepsInMenu {
/// Enable this to keep the app in the menu bar after closing it.
public static let footer = Strings.tr("Localizable", "views.settings.keeps_in_menu.footer", fallback: "Enable this to keep the app in the menu bar after closing it.")
}
public enum LaunchesOnLogin {
/// Open the app in background after login.
public static let footer = Strings.tr("Localizable", "views.settings.launches_on_login.footer", fallback: "Open the app in background after login.")
}
public enum LocksInBackground {
/// Lock the app with FaceID when sent to the background.
public static let footer = Strings.tr("Localizable", "views.settings.locks_in_background.footer", fallback: "Lock the app with FaceID when sent to the background.")
public enum PurchaseRequired {
public enum Purchase {
/// Purchase required
public static let help = Strings.tr("Localizable", "views.ui.purchase_required.purchase.help", fallback: "Purchase required")
}
public enum Restricted {
/// Feature is restricted
public static let help = Strings.tr("Localizable", "views.ui.purchase_required.restricted.help", fallback: "Feature is restricted")
}
}
}
}

View File

@ -1,154 +1,7 @@
// MARK: Global
"global.about" = "About";
"global.account" = "Account";
"global.address" = "Address";
"global.addresses" = "Addresses";
"global.any" = "Any";
"global.cancel" = "Cancel";
"global.category" = "Category";
"global.certificate" = "Certificate";
"global.compression" = "Compression";
"global.connect" = "Connect";
"global.connection" = "Connection";
"global.country" = "Country";
"global.default" = "Default";
"global.delete" = "Delete";
"global.destination" = "Destination";
"global.disable" = "Disable";
"global.disabled" = "Disabled";
"global.disconnect" = "Disconnect";
"global.domain" = "Domain";
"global.done" = "Done";
"global.do_not_ask_again" = "Don't ask again";
"global.duplicate" = "Duplicate";
"global.edit" = "Edit";
"global.empty" = "Empty";
"global.enable" = "Enable";
"global.enabled" = "Enabled";
"global.endpoint" = "Endpoint";
"global.filters" = "Filters";
"global.folder" = "Folder";
"global.gateway" = "Gateway";
"global.general" = "General";
"global.hide" = "Hide";
"global.hostname" = "Hostname";
"global.interface" = "Interface";
"global.keep_alive" = "Keep-alive";
"global.key" = "Key";
"global.last_update" = "Last update";
"global.loading" = "Loading";
"global.method" = "Method";
"global.modules" = "Modules";
"global.n_seconds" = "%d seconds";
"global.name" = "Name";
"global.networks" = "Networks";
"global.no_content" = "No content";
"global.no_selection" = "No selection";
"global.none" = "None";
"global.ok" = "OK";
"global.on_demand" = "On-demand";
"global.other" = "Other";
"global.password" = "Password";
"global.port" = "Port";
"global.private_key" = "Private key";
"global.profile" = "Profile";
"global.protocol" = "Protocol";
"global.provider" = "Provider";
"global.public_key" = "Public key";
"global.purchase" = "Purchase";
"global.region" = "Region";
"global.remove" = "Delete";
"global.restart" = "Restart";
"global.route" = "Route";
"global.routes" = "Routes";
"global.routing" = "Routing";
"global.save" = "Save";
"global.select" = "Select";
"global.server" = "Server";
"global.servers" = "Servers";
"global.settings" = "Settings";
"global.show" = "Show";
"global.status" = "Status";
"global.subnet" = "Subnet";
"global.unknown" = "Unknown";
"global.username" = "Username";
"global.version" = "Version";
// MARK: - Entities
"entities.dns.servers" = "Servers";
"entities.dns.search_domains" = "Search domains";
"entities.dns_protocol.cleartext" = "Cleartext";
"entities.dns_protocol.https" = "Over HTTPS";
"entities.dns_protocol.tls" = "Over TLS";
"entities.http_proxy.bypass_domains" = "Bypass domains";
"entities.on_demand.policy.any" = "All networks";
"entities.on_demand.policy.excluding" = "Excluding";
"entities.on_demand.policy.including" = "Including";
"entities.openvpn.compression_algorithm.other" = "Unsupported";
"entities.openvpn.otp_method.none" = "None";
"entities.openvpn.otp_method.append" = "Append";
"entities.openvpn.otp_method.encode" = "Encode";
"entities.profile.name.new" = "New profile";
"entities.tunnel_status.inactive" = "Inactive";
"entities.tunnel_status.activating" = "Activating";
"entities.tunnel_status.active" = "Active";
"entities.tunnel_status.deactivating" = "Deactivating";
"entities.connection_status.disconnected" = "Disconnected";
"entities.connection_status.connecting" = "Connecting";
"entities.connection_status.connected" = "Connected";
"entities.connection_status.disconnecting" = "Disconnecting";
// MARK: - Entity placeholders
"placeholders.profile.name" = "My profile";
"placeholders.on_demand.ssid" = "My SSID";
"placeholders.username" = "username";
"placeholders.secret" = "secret";
// MARK: - Views
"views.profiles.rows.not_installed" = "Select a profile";
"views.profiles.rows.no_modules" = "No active modules";
"views.profiles.folders.active_profile" = "Installed profile";
"views.profiles.folders.default" = "My profiles";
"views.profiles.folders.add_profile" = "Add profile";
"views.profiles.folders.no_profiles" = "No profiles";
"views.profiles.folders.no_profiles.migrate" = "Migrate old profiles...";
"views.profiles.toolbar.new_profile" = "New profile";
"views.profiles.toolbar.import_profile" = "Import profile";
"views.profiles.toolbar.migrate_profiles" = "Migrate profiles";
"views.profiles.tv.header" = "Open %@ on your iOS or macOS device and enable the \"%@\" toggle of a profile to make it appear here.";
"views.profiles.errors.tunnel" = "Unable to execute tunnel operation.";
"views.profiles.errors.duplicate" = "Unable to duplicate profile '%@'.";
"views.profiles.errors.import" = "Unable to import profiles.";
"views.profile.rows.add_module" = "Add module";
"views.profile.module_list.section.footer" = "Drag modules to rearrange them, as their order determines priority.";
"views.profile.alerts.purchase.title" = "Purchase required";
"views.profile.alerts.purchase.buttons.ok" = "Save anyway";
"views.profile.alerts.purchase.message" = "This profile requires paid features to work.";
"views.settings.launches_on_login" = "Launch on login";
"views.settings.launches_on_login.footer" = "Open the app in background after login.";
"views.settings.keeps_in_menu" = "Keep in menu bar";
"views.settings.keeps_in_menu.footer" = "Enable this to keep the app in the menu bar after closing it.";
"views.settings.locks_in_background" = "Lock in background";
"views.settings.locks_in_background.footer" = "Lock the app with FaceID when sent to the background.";
"views.settings.erase_icloud" = "Erase iCloud store";
"views.settings.erase_icloud.footer" = "To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.";
// MARK: Views
"views.about.title" = "About";
"views.about.sections.resources" = "Resources";
"views.about.links.title" = "Links";
"views.about.links.sections.support" = "Support";
"views.about.links.sections.web" = "Web";
@ -157,22 +10,29 @@
"views.about.links.rows.home_page" = "Home page";
"views.about.links.rows.disclaimer" = "Disclaimer";
"views.about.links.rows.privacy_policy" = "Privacy policy";
"views.about.credits.title" = "Credits";
"views.about.credits.licenses" = "Licenses";
"views.about.credits.notices" = "Notices";
"views.about.credits.translations" = "Translations";
"views.migrate.title" = "Migrate";
"views.migrate.no_profiles" = "Nothing to migrate";
"views.migrate.items.discard" = "Discard";
"views.migrate.items.migrate" = "Proceed";
"views.migrate.sections.main.header" = "Select below the profiles from old versions of %@ that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later.";
"views.migrate.alerts.delete.message" = "Do you want to discard these profiles? You will not be able to recover them later.\n\n%@";
"views.app.rows.not_installed" = "Select a profile";
"views.app.rows.no_modules" = "No active modules";
"views.app.folders.active_profile" = "Installed profile";
"views.app.folders.default" = "My profiles";
"views.app.folders.add_profile" = "Add profile";
"views.app.folders.no_profiles" = "No profiles";
"views.app.folders.no_profiles.migrate" = "Migrate old profiles...";
"views.app.toolbar.new_profile" = "New profile";
"views.app.toolbar.import_profile" = "Import profile";
"views.app.toolbar.migrate_profiles" = "Migrate profiles";
"views.app.tv.header" = "Open %@ on your iOS or macOS device and enable the \"%@\" toggle of a profile to make it appear here.";
"views.app.errors.tunnel" = "Unable to execute tunnel operation.";
"views.app.errors.duplicate" = "Unable to duplicate profile '%@'.";
"views.app.errors.import" = "Unable to import profiles.";
"views.donate.title" = "Make a donation";
"views.donate.sections.main.footer" = "If you want to display gratitude for my work, here are a couple amounts you can donate instantly.\n\nYou will only be charged once per donation, and you can donate multiple times.";
"views.donate.alerts.thank_you.message" = "This means a lot to me and I really hope you keep using and promoting this app.";
"views.app.profile_context.connect_to" = "Connect to...";
"views.app_menu.items.quit" = "Quit %@";
"views.diagnostics.title" = "Diagnostics";
"views.diagnostics.sections.live" = "Live log";
@ -181,18 +41,73 @@
"views.diagnostics.rows.tunnel" = "Tunnel";
"views.diagnostics.rows.include_private_data" = "Include private data";
"views.diagnostics.rows.remove_tunnel_logs" = "Delete all logs";
"views.diagnostics.openvpn.rows.server_configuration" = "Server configuration";
"views.diagnostics.report_issue.title" = "Report issue";
"views.diagnostics.alerts.report_issue.email" = "The device is not configured to send e-mails.";
// MARK: - Module views
"views.donate.title" = "Make a donation";
"views.donate.sections.main.footer" = "If you want to display gratitude for my work, here are a couple amounts you can donate instantly.\n\nYou will only be charged once per donation, and you can donate multiple times.";
"views.donate.alerts.thank_you.message" = "This means a lot to me and I really hope you keep using and promoting this app.";
"views.migration.title" = "Migrate";
"views.migration.no_profiles" = "Nothing to migrate";
"views.migration.items.discard" = "Discard";
"views.migration.items.migrate" = "Proceed";
"views.migration.sections.main.header" = "Select below the profiles from old versions of %@ that you want to import. In case your profiles are stored on iCloud, they may take a while to synchronize. If you do not see them now, please come back later.";
"views.migration.alerts.delete.message" = "Do you want to discard these profiles? You will not be able to recover them later.\n\n%@";
"views.paywall.sections.recurring.header" = "All features";
"views.paywall.sections.one_time.header" = "One-time purchase";
"views.paywall.sections.features.required.header" = "Required features";
"views.paywall.sections.features.other.header" = "Also included";
"views.paywall.sections.restore.header" = "Restore";
"views.paywall.sections.restore.footer" = "If you bought this app or feature in the past, you can restore your purchases.";
"views.paywall.rows.restore_purchases" = "Restore purchases";
"views.paywall.alerts.restricted.title" = "Restricted";
"views.paywall.alerts.restricted.message" = "Some features are unavailable in this build.";
"views.paywall.alerts.pending.message" = "The purchase is pending external confirmation. The feature will be credited upon approval.";
"views.preferences.launches_on_login" = "Launch on login";
"views.preferences.launches_on_login.footer" = "Open the app in background after login.";
"views.preferences.keeps_in_menu" = "Keep in menu bar";
"views.preferences.keeps_in_menu.footer" = "Enable this to keep the app in the menu bar after closing it.";
"views.preferences.locks_in_background" = "Lock in background";
"views.preferences.locks_in_background.footer" = "Lock the app with FaceID when sent to the background.";
"views.preferences.erase_icloud" = "Erase iCloud store";
"views.preferences.erase_icloud.footer" = "To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.";
"views.profile.rows.add_module" = "Add module";
"views.profile.module_list.section.footer" = "Drag modules to rearrange them, as their order determines priority.";
"views.profile.alerts.purchase.title" = "Purchase required";
"views.profile.alerts.purchase.buttons.ok" = "Save anyway";
"views.profile.alerts.purchase.message" = "This profile requires paid features to work.";
"views.providers.no_provider" = "None";
"views.providers.select_provider" = "Select a provider";
"views.providers.select_entity" = "Select";
"views.providers.only_favorites" = "Only favorites";
"views.providers.clear_filters" = "Clear filters";
"views.providers.refresh_infrastructure" = "Refresh infrastructure";
"views.providers.last_updated" = "Last updated on %@";
"views.providers.last_updated.loading" = "Loading...";
"views.providers.vpn.category.any" = "All categories";
"views.providers.vpn.preset" = "Preset";
"views.providers.vpn.no_servers" = "No servers";
"views.ui.connection_status.on_demand_suffix" = " (on-demand)";
"views.ui.purchase_required.purchase.help" = "Purchase required";
"views.ui.purchase_required.restricted.help" = "Feature is restricted";
// MARK: Views (Modules)
"modules.general.sections.storage.header" = "%@";
"modules.general.sections.storage.footer" = "Profiles are stored to %@ encrypted.";
"modules.general.sections.storage.footer.purchase.tv_beta" = "TV profiles do not work in beta builds.";
"modules.general.sections.storage.footer.purchase.tv_release" = "TV profiles do not work without a purchase.";
"modules.general.rows.shared" = "Enabled";
"modules.general.rows.apple_tv" = "%@";
"modules.general.rows.shared.purchase" = "Share on iCloud";
"modules.general.rows.appletv" = "%@";
"modules.general.rows.appletv.purchase" = "Drop TV restriction";
"modules.general.rows.import_from_file" = "Import from file...";
"modules.dns.servers.add" = "Add address";
@ -240,78 +155,148 @@
"modules.wireguard.preshared_key" = "Pre-shared key";
"modules.wireguard.allowed_ips" = "Allowed IPs";
// MARK: - Providers
// MARK: - Entities
"providers.no_provider" = "None";
"providers.select_provider" = "Select a provider";
"providers.select_entity" = "Select";
"providers.only_favorites" = "Only favorites";
"providers.clear_filters" = "Clear filters";
"providers.refresh_infrastructure" = "Refresh infrastructure";
"providers.last_updated" = "Last updated on %@";
"providers.last_updated.loading" = "Loading...";
"providers.vpn.category.any" = "All categories";
"providers.vpn.preset" = "Preset";
"providers.vpn.no_servers" = "No servers";
"entities.tunnel_status.inactive" = "Inactive";
"entities.tunnel_status.activating" = "Activating";
"entities.tunnel_status.active" = "Active";
"entities.tunnel_status.deactivating" = "Deactivating";
// MARK: - App menu
// MARK: Entities (Modules)
"app_menu.items.quit" = "Quit %@";
"entities.dns.servers" = "Servers";
"entities.dns.search_domains" = "Search domains";
// MARK: - Theme
"entities.dns_protocol.cleartext" = "Cleartext";
"entities.dns_protocol.https" = "Over HTTPS";
"entities.dns_protocol.tls" = "Over TLS";
"theme.confirmation.ok" = "Confirm";
"theme.confirmation.cancel" = "Cancel";
"theme.confirmation.message" = "Are you sure you want to proceed with this operation?";
"theme.lock_screen.reason" = "%@ is locked";
"entities.http_proxy.bypass_domains" = "Bypass domains";
// MARK: - Components
"entities.on_demand.policy.any" = "All networks";
"entities.on_demand.policy.excluding" = "Excluding";
"entities.on_demand.policy.including" = "Including";
"ui.connection_status.on_demand_suffix" = " (on-demand)";
"ui.profile_context.connect_to" = "Connect to...";
"ui.purchase_required.purchase.help" = "Purchase required";
"ui.purchase_required.restricted.help" = "Feature is restricted";
"entities.openvpn.compression_algorithm.other" = "Unsupported";
"entities.openvpn.otp_method.none" = "None";
"entities.openvpn.otp_method.append" = "Append";
"entities.openvpn.otp_method.encode" = "Encode";
// MARK: - Paywall
// MARK: - Features
"paywall.sections.recurring.header" = "All features";
"paywall.sections.one_time.header" = "One-time purchase";
"paywall.sections.features.required.header" = "Required features";
"paywall.sections.features.other.header" = "Also included";
"paywall.sections.restore.header" = "Restore";
"paywall.sections.restore.footer" = "If you bought this app or feature in the past, you can restore your purchases.";
"paywall.rows.restore_purchases" = "Restore purchases";
"paywall.alerts.pending.message" = "The purchase is pending external confirmation. The feature will be credited upon approval.";
"features.appleTV" = "%@";
"features.appletv" = "%@";
"features.dns" = "%@ Settings";
"features.httpProxy" = "%@ Settings";
"features.http_proxy" = "%@ Settings";
"features.interactiveLogin" = "Interactive Login";
"features.onDemand" = "On-Demand Rules";
"features.on_demand" = "On-Demand Rules";
"features.providers" = "All Providers";
"features.routing" = "Custom %@";
"features.sharing" = "%@";
"modules.general.sections.storage.footer.purchase.tv_beta" = "TV profiles do not work in beta builds.";
"modules.general.sections.storage.footer.purchase.tv_release" = "TV profiles do not work without a purchase.";
"modules.general.rows.shared.purchase" = "Share on iCloud";
"modules.general.rows.apple_tv.purchase" = "Drop TV restriction";
// MARK: - Global
// MARK: - Alerts
// MARK: Global (Actions)
"global.actions.cancel" = "Cancel";
"global.actions.connect" = "Connect";
"global.actions.delete" = "Delete";
"global.actions.disable" = "Disable";
"global.actions.disconnect" = "Disconnect";
"global.actions.duplicate" = "Duplicate";
"global.actions.edit" = "Edit";
"global.actions.enable" = "Enable";
"global.actions.hide" = "Hide";
"global.actions.purchase" = "Purchase";
"global.actions.remove" = "Delete";
"global.actions.restart" = "Restart";
"global.actions.save" = "Save";
"global.actions.select" = "Select";
"global.actions.show" = "Show";
// MARK: Global (Nouns)
"global.nouns.about" = "About";
"global.nouns.account" = "Account";
"global.nouns.address" = "Address";
"global.nouns.addresses" = "Addresses";
"global.nouns.any" = "Any";
"global.nouns.category" = "Category";
"global.nouns.certificate" = "Certificate";
"global.nouns.compression" = "Compression";
"global.nouns.connection" = "Connection";
"global.nouns.country" = "Country";
"global.nouns.default" = "Default";
"global.nouns.destination" = "Destination";
"global.nouns.disabled" = "Disabled";
"global.nouns.domain" = "Domain";
"global.nouns.done" = "Done";
"global.nouns.do_not_ask_again" = "Don't ask again";
"global.nouns.empty" = "Empty";
"global.nouns.enabled" = "Enabled";
"global.nouns.endpoint" = "Endpoint";
"global.nouns.filters" = "Filters";
"global.nouns.folder" = "Folder";
"global.nouns.gateway" = "Gateway";
"global.nouns.general" = "General";
"global.nouns.hostname" = "Hostname";
"global.nouns.interface" = "Interface";
"global.nouns.keep_alive" = "Keep-alive";
"global.nouns.key" = "Key";
"global.nouns.last_update" = "Last update";
"global.nouns.loading" = "Loading";
"global.nouns.method" = "Method";
"global.nouns.modules" = "Modules";
"global.nouns.n_seconds" = "%d seconds";
"global.nouns.name" = "Name";
"global.nouns.networks" = "Networks";
"global.nouns.no_content" = "No content";
"global.nouns.no_selection" = "No selection";
"global.nouns.none" = "None";
"global.nouns.ok" = "OK";
"global.nouns.on_demand" = "On-demand";
"global.nouns.other" = "Other";
"global.nouns.password" = "Password";
"global.nouns.port" = "Port";
"global.nouns.private_key" = "Private key";
"global.nouns.profile" = "Profile";
"global.nouns.protocol" = "Protocol";
"global.nouns.provider" = "Provider";
"global.nouns.public_key" = "Public key";
"global.nouns.region" = "Region";
"global.nouns.route" = "Route";
"global.nouns.routes" = "Routes";
"global.nouns.routing" = "Routing";
"global.nouns.server" = "Server";
"global.nouns.servers" = "Servers";
"global.nouns.settings" = "Settings";
"global.nouns.status" = "Status";
"global.nouns.subnet" = "Subnet";
"global.nouns.unknown" = "Unknown";
"global.nouns.username" = "Username";
"global.nouns.version" = "Version";
// MARK: Global (Placeholders)
"placeholders.profile.name" = "My profile";
"placeholders.on_demand.ssid" = "My SSID";
"placeholders.username" = "username";
"placeholders.secret" = "secret";
// MARK: Global (Alerts)
"alerts.import.passphrase.message" = "Enter passphrase for '%@'.";
"alerts.import.passphrase.ok" = "Decrypt";
"alerts.iap.restricted.title" = "Restricted";
"alerts.iap.restricted.message" = "Some features are unavailable in this build.";
// MARK: - Errors
// MARK: Global (App errors)
"errors.app.default" = "Unable to complete operation.";
"errors.app.empty_products" = "Unable to fetch products, please retry later.";
"errors.app.empty_profile_name" = "Profile name is empty.";
"errors.app.malformed_module" = "Module %@ is malformed. %@";
"errors.app.provider.required" = "No provider selected.";
"errors.app.default" = "Unable to complete operation.";
// MARK: Global (PassepartoutKit errors)
"errors.app.passepartout.connection_module_required" = "Routing module can only be enabled together with a connection.";
"errors.app.passepartout.corrupt_provider_module" = "Unable to connect to provider server (reason=%@).";
"errors.app.passepartout.incompatible_modules" = "Some active modules are incompatible, try to only activate one of them.";
@ -332,3 +317,10 @@
"errors.tunnel.timeout" = "Timeout";
"errors.tunnel.tls" = "TLS failed";
"errors.tunnel.generic" = "Failed";
// MARK: Global (Theme)
"theme.confirmation.ok" = "Confirm";
"theme.confirmation.cancel" = "Cancel";
"theme.confirmation.message" = "Are you sure you want to proceed with this operation?";
"theme.lock_screen.reason" = "%@ is locked";

View File

@ -99,7 +99,7 @@ public struct ThemeCloseLabel: View {
#if os(iOS) || os(tvOS)
ThemeImage(.close)
#else
Text(Strings.Global.cancel)
Text(Strings.Global.Actions.cancel)
#endif
}
}

View File

@ -99,7 +99,7 @@ private extension DonateView {
}
func thankYouActions() -> some View {
Button(Strings.Global.ok) {
Button(Strings.Global.Nouns.ok) {
dismiss()
}
}

View File

@ -38,7 +38,7 @@ public struct DebugLogView<Content>: View where Content: View {
public var body: some View {
content(currentLines)
.themeEmpty(if: currentLines.isEmpty, message: Strings.Global.noContent)
.themeEmpty(if: currentLines.isEmpty, message: Strings.Global.Nouns.noContent)
.toolbar(content: toolbarContent)
.task {
currentLines = await fetchLines()

View File

@ -172,13 +172,13 @@ private extension OpenVPNCredentialsView {
}
var usernameField: some View {
ThemeTextField(Strings.Global.username, text: $builder.username, placeholder: Strings.Placeholders.username)
ThemeTextField(Strings.Global.Nouns.username, text: $builder.username, placeholder: Strings.Placeholders.username)
.textContentType(.username)
.focused($focusedField, equals: .username)
}
var passwordField: some View {
ThemeSecureField(title: Strings.Global.password, text: $builder.password, placeholder: Strings.Placeholders.secret)
ThemeSecureField(title: Strings.Global.Nouns.password, text: $builder.password, placeholder: Strings.Placeholders.secret)
.textContentType(.password)
.focused($focusedField, equals: .password)
.onSubmit {

View File

@ -63,7 +63,7 @@ struct PaywallView: View {
.themeProgress(if: isFetchingProducts)
.toolbar(content: toolbarContent)
.alert(
Strings.Global.purchase,
Strings.Global.Actions.purchase,
isPresented: $isPurchasePendingConfirmation,
actions: pendingActions,
message: pendingMessage
@ -77,7 +77,7 @@ struct PaywallView: View {
private extension PaywallView {
var title: String {
Strings.Global.purchase
Strings.Global.Actions.purchase
}
var paywallView: some View {
@ -108,7 +108,7 @@ private extension PaywallView {
onComplete: onComplete,
onError: onError
)
.themeSection(header: Strings.Paywall.Sections.OneTime.header)
.themeSection(header: Strings.Views.Paywall.Sections.OneTime.header)
}
ForEach(recurringProducts, id: \.productIdentifier) {
PaywallProductView(
@ -120,13 +120,13 @@ private extension PaywallView {
onError: onError
)
}
.themeSection(header: Strings.Paywall.Sections.Recurring.header)
.themeSection(header: Strings.Views.Paywall.Sections.Recurring.header)
}
var requiredFeaturesView: some View {
FeatureListView(
style: .list,
header: Strings.Paywall.Sections.Features.Required.header,
header: Strings.Views.Paywall.Sections.Features.Required.header,
features: Array(features)
) {
Text($0.localizedDescription)
@ -137,7 +137,7 @@ private extension PaywallView {
var otherFeaturesView: some View {
FeatureListView(
style: otherFeaturesStyle,
header: Strings.Paywall.Sections.Features.Other.header,
header: Strings.Views.Paywall.Sections.Features.Other.header,
features: otherFeatures
) {
Text($0.localizedDescription)
@ -155,8 +155,8 @@ private extension PaywallView {
var restoreView: some View {
RestorePurchasesButton(errorHandler: errorHandler)
.themeSectionWithSingleRow(
header: Strings.Paywall.Sections.Restore.header,
footer: Strings.Paywall.Sections.Restore.footer,
header: Strings.Views.Paywall.Sections.Restore.header,
footer: Strings.Views.Paywall.Sections.Restore.footer,
above: true
)
}
@ -176,13 +176,13 @@ private extension PaywallView {
}
func pendingActions() -> some View {
Button(Strings.Global.ok) {
Button(Strings.Global.Nouns.ok) {
isPresented = false
}
}
func pendingMessage() -> some View {
Text(Strings.Paywall.Alerts.Pending.message)
Text(Strings.Views.Paywall.Alerts.Pending.message)
}
}
@ -245,7 +245,7 @@ private extension PaywallView {
}
func onError(_ error: Error, dismissing: Bool) {
errorHandler.handle(error, title: Strings.Global.purchase) {
errorHandler.handle(error, title: Strings.Global.Actions.purchase) {
if dismissing {
isPresented = false
}

View File

@ -54,6 +54,6 @@ public struct RestorePurchasesButton: View {
private extension RestorePurchasesButton {
var title: String {
Strings.Paywall.Rows.restorePurchases
Strings.Views.Paywall.Rows.restorePurchases
}
}

View File

@ -64,27 +64,27 @@ public struct PreferencesGroup: View {
private extension PreferencesGroup {
#if os(iOS)
var lockInBackgroundToggle: some View {
Toggle(Strings.Views.Settings.locksInBackground, isOn: $locksInBackground)
.themeSectionWithSingleRow(footer: Strings.Views.Settings.LocksInBackground.footer)
Toggle(Strings.Views.Preferences.locksInBackground, isOn: $locksInBackground)
.themeSectionWithSingleRow(footer: Strings.Views.Preferences.LocksInBackground.footer)
}
#elseif os(macOS)
var launchesOnLoginToggle: some View {
Toggle(Strings.Views.Settings.launchesOnLogin, isOn: $settings.launchesOnLogin)
.themeSectionWithSingleRow(footer: Strings.Views.Settings.LaunchesOnLogin.footer)
Toggle(Strings.Views.Preferences.launchesOnLogin, isOn: $settings.launchesOnLogin)
.themeSectionWithSingleRow(footer: Strings.Views.Preferences.LaunchesOnLogin.footer)
}
var keepsInMenuToggle: some View {
Toggle(Strings.Views.Settings.keepsInMenu, isOn: $settings.keepsInMenu)
.themeSectionWithSingleRow(footer: Strings.Views.Settings.KeepsInMenu.footer)
Toggle(Strings.Views.Preferences.keepsInMenu, isOn: $settings.keepsInMenu)
.themeSectionWithSingleRow(footer: Strings.Views.Preferences.KeepsInMenu.footer)
}
#endif
var eraseCloudKitButton: some View {
Button(Strings.Views.Settings.eraseIcloud, role: .destructive) {
Button(Strings.Views.Preferences.eraseIcloud, role: .destructive) {
isConfirmingEraseiCloud = true
}
.themeConfirmation(
isPresented: $isConfirmingEraseiCloud,
title: Strings.Views.Settings.eraseIcloud,
title: Strings.Views.Preferences.eraseIcloud,
isDestructive: true
) {
isErasingiCloud = true
@ -100,7 +100,7 @@ private extension PreferencesGroup {
}
.themeSectionWithSingleRow(
header: Strings.Unlocalized.iCloud,
footer: Strings.Views.Settings.EraseIcloud.footer,
footer: Strings.Views.Preferences.EraseIcloud.footer,
above: true
)
.disabled(isErasingiCloud)

View File

@ -41,7 +41,7 @@ public struct PreferencesView: View {
PreferencesGroup(profileManager: profileManager)
}
.themeForm()
.navigationTitle(Strings.Global.settings)
.navigationTitle(Strings.Global.Nouns.settings)
#if os(iOS)
.themeNavigationDetail()
.themeNavigationStack(closable: true, path: $path)

View File

@ -63,7 +63,7 @@ private extension ConnectionStatusText {
var desc = status.localizedDescription
if let profile = tunnel.currentProfile {
if profile.onDemand {
desc += Strings.Ui.ConnectionStatus.onDemandSuffix
desc += Strings.Views.Ui.ConnectionStatus.onDemandSuffix
}
}
return desc

View File

@ -99,7 +99,7 @@ private extension InteractiveCoordinator {
func modalToolbar() -> some ToolbarContent {
ToolbarItem(placement: .confirmationAction) {
Button(action: confirm) {
Text(Strings.Global.connect)
Text(Strings.Global.Actions.connect)
}
}
ToolbarItem(placement: .cancellationAction) {
@ -140,12 +140,12 @@ private extension InteractiveCoordinator {
var toolbar: some View {
VStack {
Button(action: confirm) {
Text(Strings.Global.connect)
Text(Strings.Global.Actions.connect)
.frame(maxWidth: .infinity)
}
if withCancel {
Button(role: .cancel, action: cancel) {
Text(Strings.Global.cancel)
Text(Strings.Global.Actions.cancel)
.frame(maxWidth: .infinity)
}
}

View File

@ -47,10 +47,10 @@ public struct PaywallModifier: ViewModifier {
public func body(content: Content) -> some View {
content
.alert(
Strings.Alerts.Iap.Restricted.title,
Strings.Views.Paywall.Alerts.Restricted.title,
isPresented: $isPresentingRestricted,
actions: {
Button(Strings.Global.ok) {
Button(Strings.Global.Nouns.ok) {
reason = nil
isPresentingRestricted = false
}
@ -102,7 +102,7 @@ private extension PaywallModifier {
guard case .purchase(let features, _) = reason else {
return ""
}
let msg = Strings.Alerts.Iap.Restricted.message
let msg = Strings.Views.Paywall.Alerts.Restricted.message
return msg + "\n\n" + iapManager
.excludingEligible(from: features)
.map(\.localizedDescription)

View File

@ -82,6 +82,6 @@ private extension PurchaseRequiredButton {
}
var helpMessage: String {
iapManager.isRestricted ? Strings.Ui.PurchaseRequired.Restricted.help : Strings.Ui.PurchaseRequired.Purchase.help
iapManager.isRestricted ? Strings.Views.Ui.PurchaseRequired.Restricted.help : Strings.Views.Ui.PurchaseRequired.Purchase.help
}
}

View File

@ -120,7 +120,7 @@ private extension TunnelToggleButton {
guard let provider = providerModule.provider else {
errorHandler.handle(
PassepartoutError(.providerRequired),
title: Strings.Global.connection
title: Strings.Global.Nouns.connection
)
return
}
@ -168,8 +168,8 @@ private extension TunnelToggleButton {
} catch {
errorHandler.handle(
error,
title: Strings.Global.connection,
message: Strings.Views.Profiles.Errors.tunnel
title: Strings.Global.Nouns.connection,
message: Strings.Views.App.Errors.tunnel
)
}
}