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() LinksView()
default: default:
Text(Strings.Global.noSelection) Text(Strings.Global.Nouns.noSelection)
.themeEmptyMessage() .themeEmptyMessage()
} }
} }
@ -124,7 +124,7 @@ extension AboutCoordinator {
} }
default: default:
Text(Strings.Global.noSelection) Text(Strings.Global.Nouns.noSelection)
.themeEmptyMessage() .themeEmptyMessage()
} }
} }

View File

@ -74,11 +74,11 @@ private extension AboutContentView {
.themeSection(header: Strings.Views.About.Sections.resources) .themeSection(header: Strings.Views.About.Sections.resources)
Section { Section {
linkContent(.diagnostics) linkContent(.diagnostics)
Text(Strings.Global.version) Text(Strings.Global.Nouns.version)
.themeTrailingValue(BundleConfiguration.mainVersionString) .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) .themeNavigationStack(closable: false, path: $path)
.toolbar { .toolbar {
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
Button(Strings.Global.ok) { Button(Strings.Global.Nouns.ok) {
dismiss() dismiss()
} }
} }

View File

@ -52,10 +52,10 @@ struct AddProfileMenu: View {
private extension AddProfileMenu { private extension AddProfileMenu {
var newProfileButton: some View { var newProfileButton: some View {
Button { Button {
let profile = profileManager.new(withName: Strings.Entities.Profile.Name.new) let profile = profileManager.new(withName: Strings.Placeholders.Profile.name)
onNewProfile(profile) onNewProfile(profile)
} label: { } label: {
ThemeImageLabel(Strings.Views.Profiles.Toolbar.newProfile, .profileEdit) ThemeImageLabel(Strings.Views.App.Toolbar.newProfile, .profileEdit)
} }
} }
@ -63,13 +63,13 @@ private extension AddProfileMenu {
Button { Button {
isImporting = true isImporting = true
} label: { } label: {
ThemeImageLabel(Strings.Views.Profiles.Toolbar.importProfile.withTrailingDots, .profileImport) ThemeImageLabel(Strings.Views.App.Toolbar.importProfile.withTrailingDots, .profileImport)
} }
} }
var migrateProfilesButton: some View { var migrateProfilesButton: some View {
Button(action: onMigrateProfiles) { 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 { var nameView: some View {
Text(profile?.name ?? Strings.Views.Profiles.Rows.notInstalled) Text(profile?.name ?? Strings.Views.App.Rows.notInstalled)
.font(.title2) .font(.title2)
.fontWeight(theme.relevantWeight) .fontWeight(theme.relevantWeight)
.themeTruncating(.tail) .themeTruncating(.tail)

View File

@ -70,7 +70,7 @@ struct ProfileAttributesView: View {
case .tv: case .tv:
return ( return (
isRemoteImportingEnabled ? .tvOn : .tvOff, 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) .font(.headline)
.themeTruncating() .themeTruncating()
Text(preview.subtitle ?? Strings.Views.Profiles.Rows.noModules) Text(preview.subtitle ?? Strings.Views.App.Rows.noModules)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)

View File

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

View File

@ -78,7 +78,7 @@ private extension ProfileContextMenu {
}, },
label: { label: {
ThemeImageLabel( ThemeImageLabel(
$0 ? Strings.Global.enable : Strings.Global.disable, $0 ? Strings.Global.Actions.enable : Strings.Global.Actions.disable,
$0 ? .tunnelEnable : .tunnelDisable $0 ? .tunnelEnable : .tunnelDisable
) )
} }
@ -89,7 +89,7 @@ private extension ProfileContextMenu {
profile? profile?
.selectedProvider .selectedProvider
.map { _ in .map { _ in
Button(Strings.Ui.ProfileContext.connectTo) { Button(Strings.Views.App.ProfileContext.connectTo) {
flow?.onEditProviderEntity(profile!) flow?.onEditProviderEntity(profile!)
} }
} }
@ -104,7 +104,7 @@ private extension ProfileContextMenu {
flow?.onPurchaseRequired($0) flow?.onPurchaseRequired($0)
}, },
label: { label: {
ThemeImageLabel(Strings.Global.restart, .tunnelRestart) ThemeImageLabel(Strings.Global.Actions.restart, .tunnelRestart)
} }
) )
} }
@ -113,7 +113,7 @@ private extension ProfileContextMenu {
Button { Button {
flow?.onEditProfile(preview) flow?.onEditProfile(preview)
} label: { } label: {
ThemeImageLabel(Strings.Global.edit.withTrailingDots, .profileEdit) ThemeImageLabel(Strings.Global.Actions.edit.withTrailingDots, .profileEdit)
} }
} }
@ -123,7 +123,7 @@ private extension ProfileContextMenu {
preview: preview, preview: preview,
errorHandler: errorHandler errorHandler: errorHandler
) { ) {
ThemeImageLabel(Strings.Global.duplicate, .contextDuplicate) ThemeImageLabel(Strings.Global.Actions.duplicate, .contextDuplicate)
} }
} }
@ -132,7 +132,7 @@ private extension ProfileContextMenu {
profileManager: profileManager, profileManager: profileManager,
preview: preview 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 { } catch {
errorHandler.handle( errorHandler.handle(
error, error,
title: Strings.Global.duplicate, title: Strings.Global.Actions.duplicate,
message: Strings.Views.Profiles.Errors.duplicate(preview.name) 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) .padding(.horizontal)
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -40,10 +40,10 @@ private extension MigrateButton {
var title: String { var title: String {
switch step { switch step {
case .initial, .fetching, .fetched: case .initial, .fetching, .fetched:
return Strings.Views.Migrate.Items.migrate return Strings.Views.Migration.Items.migrate
case .migrating, .migrated: 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 { 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]) .padding([.top, .leading, .trailing])
} }
@ -98,7 +98,7 @@ private extension MigrateContentView.ListView {
} }
.listStyle(.plain) .listStyle(.plain)
.disabled(!step.canSelect) .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> { func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {
@ -142,7 +142,7 @@ private extension MigrateContentView.ListView {
var body: some View { var body: some View {
HStack { HStack {
if isEditing { if isEditing {
Button(Strings.Global.cancel) { Button(Strings.Global.Actions.cancel) {
isEditing = false isEditing = false
} }
} }
@ -165,7 +165,7 @@ private extension MigrateContentView.ListView {
} }
var title: String { 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? { var role: ButtonRole? {

View File

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

View File

@ -81,7 +81,7 @@ struct MigrateView: View {
.themeAnimation(on: model, category: .profiles) .themeAnimation(on: model, category: .profiles)
.themeConfirmation( .themeConfirmation(
isPresented: $isDeleting, isPresented: $isDeleting,
title: Strings.Views.Migrate.Items.discard, title: Strings.Views.Migration.Items.discard,
message: messageForDeletion, message: messageForDeletion,
isDestructive: true, isDestructive: true,
action: confirmPendingDeletion action: confirmPendingDeletion
@ -96,7 +96,7 @@ struct MigrateView: View {
private extension MigrateView { private extension MigrateView {
var title: String { var title: String {
Strings.Views.Migrate.title Strings.Views.Migration.title
} }
var messageForDeletion: String? { var messageForDeletion: String? {
@ -105,7 +105,7 @@ private extension MigrateView {
.map(\.name) .map(\.name)
.joined(separator: "\n") .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 { var protocolSection: some View {
Section { Section {
Picker(Strings.Global.protocol, selection: draft.protocolType) { Picker(Strings.Global.Nouns.protocol, selection: draft.protocolType) {
ForEach(Self.allProtocols, id: \.self) { ForEach(Self.allProtocols, id: \.self) {
Text($0.localizedDescription) Text($0.localizedDescription)
} }
@ -77,7 +77,7 @@ private extension DNSView {
.labelsHidden() .labelsHidden()
case .tls: 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() .labelsHidden()
} }
} }
@ -85,9 +85,9 @@ private extension DNSView {
var domainSection: some View { var domainSection: some View {
Group { 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 { var serversSection: some View {

View File

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

View File

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

View File

@ -138,7 +138,7 @@ private extension IPView {
placeholder: Strings.Unlocalized.Placeholders.mtu 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 { var enabledSection: some View {
Section { 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) Toggle(Strings.Modules.OnDemand.ethernet, isOn: draft.withEthernetNetwork)
} }
} }
.themeSection(header: Strings.Global.networks) .themeSection(header: Strings.Global.Nouns.networks)
} }
var wifiSection: some View { var wifiSection: some View {

View File

@ -35,7 +35,7 @@ extension OpenVPNView {
let credentialsRoute: any Hashable let credentialsRoute: any Hashable
var body: some View { 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) moduleSection(for: remotesRows, header: Strings.Modules.Openvpn.remotes)
if !isServerPushed { if !isServerPushed {
moduleSection(for: pullRows, header: Strings.Modules.Openvpn.pull) moduleSection(for: pullRows, header: Strings.Modules.Openvpn.pull)
@ -56,7 +56,7 @@ extension OpenVPNView {
if !isServerPushed { if !isServerPushed {
moduleSection(for: tlsRows, header: Strings.Unlocalized.tls) 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] = [] var rows: [ModuleRow] = []
if let ip { if let ip {
ip.localizedDescription(optionalStyle: .address).map { 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 { 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 ip.includedRoutes
@ -118,7 +118,7 @@ private extension OpenVPNView.ConfigurationView {
} }
} }
routes?.forEach { 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 return rows.nilIfEmpty
} }
@ -147,14 +147,14 @@ private extension OpenVPNView.ConfigurationView {
.nilIfEmpty .nilIfEmpty
.map { .map {
rows.append(.textList( rows.append(.textList(
caption: Strings.Global.servers, caption: Strings.Global.Nouns.servers,
values: $0 values: $0
)) ))
} }
configuration.dnsDomain.map { configuration.dnsDomain.map {
rows.append(.copiableText( rows.append(.copiableText(
caption: Strings.Global.domain, caption: Strings.Global.Nouns.domain,
value: $0 value: $0
)) ))
} }
@ -237,10 +237,10 @@ private extension OpenVPNView.ConfigurationView {
rows.append(.longContentPreview(caption: Strings.Unlocalized.ca, value: $0.pem, preview: nil)) rows.append(.longContentPreview(caption: Strings.Unlocalized.ca, value: $0.pem, preview: nil))
} }
configuration.clientCertificate.map { 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 { 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 { configuration.tlsWrap.map {
rows.append(.longContentPreview( rows.append(.longContentPreview(
@ -256,7 +256,7 @@ private extension OpenVPNView.ConfigurationView {
var otherRows: [ModuleRow]? { var otherRows: [ModuleRow]? {
var rows: [ModuleRow] = [] var rows: [ModuleRow] = []
configuration.localizedDescription(optionalStyle: .keepAlive).map { 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 { configuration.localizedDescription(optionalStyle: .renegotiatesAfter).map {
rows.append(.text(caption: Strings.Modules.Openvpn.renegotiation, value: $0)) 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) { Button(Strings.Alerts.Import.Passphrase.ok) {
importConfiguration(from: .success(url)) importConfiguration(from: .success(url))
} }
Button(Strings.Global.cancel, role: .cancel) { Button(Strings.Global.Actions.cancel, role: .cancel) {
isImporting = false isImporting = false
} }
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,7 +60,7 @@ private extension StorageSection {
} }
var tvToggle: some View { 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) .disabled(!iapManager.isEligible(for: .appleTV) || !profileEditor.isShared)
} }
@ -87,7 +87,7 @@ private extension StorageSection {
var purchaseTVButton: some View { var purchaseTVButton: some View {
EmptyView() EmptyView()
.modifier(PurchaseButtonModifier( .modifier(PurchaseButtonModifier(
Strings.Modules.General.Rows.AppleTv.purchase, Strings.Modules.General.Rows.Appletv.purchase,
feature: .appleTV, feature: .appleTV,
suggesting: .Features.appleTV, suggesting: .Features.appleTV,
showsIfRestricted: false, showsIfRestricted: false,

View File

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

View File

@ -50,7 +50,7 @@ struct ModuleListView: View, Routable {
var body: some View { var body: some View {
List(selection: $selectedModuleId) { List(selection: $selectedModuleId) {
Section { Section {
NavigationLink(Strings.Global.general, value: ProfileSplitView.Detail.general) NavigationLink(Strings.Global.Nouns.general, value: ProfileSplitView.Detail.general)
.tag(Self.generalModuleId) .tag(Self.generalModuleId)
} }
Group { Group {
@ -61,7 +61,7 @@ struct ModuleListView: View, Routable {
} }
.onMove(perform: moveModules) .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) .onDeleteCommand(perform: removeSelectedModule)
.toolbar(content: toolbarContent) .toolbar(content: toolbarContent)

View File

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

View File

@ -82,7 +82,7 @@ private extension ProviderContentModifier {
providerRows providerRows
refreshButton { refreshButton {
HStack { HStack {
Text(Strings.Providers.refreshInfrastructure) Text(Strings.Views.Providers.refreshInfrastructure)
if providerManager.isLoading { if providerManager.isLoading {
Spacer() Spacer()
ProgressView() ProgressView()
@ -109,7 +109,7 @@ private extension ProviderContentModifier {
} }
Spacer() Spacer()
refreshButton { refreshButton {
Text(Strings.Providers.refreshInfrastructure) Text(Strings.Views.Providers.refreshInfrastructure)
} }
} }
} }
@ -148,9 +148,9 @@ private extension ProviderContentModifier {
var lastUpdatedString: String? { var lastUpdatedString: String? {
guard let lastUpdate else { 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 { var body: some View {
Picker(selection: $providerId) { Picker(selection: $providerId) {
if !providers.isEmpty { 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?) .tag(nil as ProviderID?)
ForEach(providers, id: \.id) { ForEach(providers, id: \.id) {
Text($0.description) Text($0.description)
.tag($0.id as ProviderID?) .tag($0.id as ProviderID?)
} }
} else { } else {
Text(isLoading ? Strings.Global.loading : Strings.Global.none) Text(isLoading ? Strings.Global.Nouns.loading : Strings.Global.Nouns.none)
.tag(providerId) // tag always exists .tag(providerId) // tag always exists
} }
} label: { } label: {
HStack { HStack {
Text(Strings.Global.provider) Text(Strings.Global.Nouns.provider)
PurchaseRequiredButton(for: providerId, paywallReason: $paywallReason) PurchaseRequiredButton(for: providerId, paywallReason: $paywallReason)
} }
} }

View File

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

View File

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

View File

@ -65,7 +65,7 @@ private extension VPNProviderServerCoordinator {
try await onSelect(entity) try await onSelect(entity)
} catch { } catch {
pp_log(.app, .fault, "Unable to select server \(server.serverId) for provider \(server.provider.id): \(error)") 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 let filtersWithSelection: Bool
var selectTitle = Strings.Providers.selectEntity var selectTitle = Strings.Views.Providers.selectEntity
let onSelect: (VPNServer, VPNPreset<Configuration>) -> Void let onSelect: (VPNServer, VPNPreset<Configuration>) -> Void
@ -163,7 +163,7 @@ extension VPNProviderServerView {
await reloadServers(filters: filtersViewModel.filters) await reloadServers(filters: filtersViewModel.filters)
} catch { } catch {
pp_log(.app, .error, "Unable to load VPN repository: \(error)") 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 .onReceive(filtersViewModel.$filters.dropFirst()) { newValue in
@ -184,7 +184,7 @@ extension VPNProviderServerView {
private extension VPNProviderServerView.ServersView { private extension VPNProviderServerView.ServersView {
var title: String { var title: String {
providerManager.provider(withId: providerId)?.description ?? Strings.Global.servers providerManager.provider(withId: providerId)?.description ?? Strings.Global.Nouns.servers
} }
var filteredServers: [VPNServer] { var filteredServers: [VPNServer] {

View File

@ -75,7 +75,7 @@ private extension VPNProviderServerView {
func body(content: Content) -> some View { func body(content: Content) -> some View {
NavigationStack { NavigationStack {
content content
.navigationTitle(Strings.Global.filters) .navigationTitle(Strings.Global.Nouns.filters)
.themeNavigationDetail() .themeNavigationDetail()
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
@ -127,7 +127,7 @@ private extension VPNProviderServerView.ServersSubview {
var listView: some View { var listView: some View {
List { List {
Section { Section {
Toggle(Strings.Providers.onlyFavorites, isOn: $filtersViewModel.onlyShowsFavorites) Toggle(Strings.Views.Providers.onlyFavorites, isOn: $filtersViewModel.onlyShowsFavorites)
} }
Group { Group {
if isFiltering || !servers.isEmpty { if isFiltering || !servers.isEmpty {
@ -142,7 +142,7 @@ private extension VPNProviderServerView.ServersSubview {
} }
} }
.themeSection( .themeSection(
header: filtersViewModel.filters.categoryName ?? Strings.Providers.Vpn.Category.any header: filtersViewModel.filters.categoryName ?? Strings.Views.Providers.Vpn.Category.any
) )
.onLoad { .onLoad {
if let selectedServer { if let selectedServer {
@ -153,7 +153,7 @@ private extension VPNProviderServerView.ServersSubview {
} }
var emptyView: some View { 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) .width(10.0)
TableColumn(Strings.Global.region) { server in TableColumn(Strings.Global.Nouns.region) { server in
ThemeCountryText(server.provider.countryCode, title: server.region) ThemeCountryText(server.provider.countryCode, title: server.region)
.help(server.region) .help(server.region)
.environmentObject(theme) // TODO: #873, Table loses environment .environmentObject(theme) // TODO: #873, Table loses environment
} }
TableColumn(Strings.Global.address, value: \.address) TableColumn(Strings.Global.Nouns.address, value: \.address)
TableColumn("􀋂") { server in TableColumn("􀋂") { server in
FavoriteToggle( FavoriteToggle(

View File

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

View File

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

View File

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

View File

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

View File

@ -26,5 +26,5 @@
import Foundation import Foundation
extension AppProduct { 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 let V = Strings.Features.self
switch self { switch self {
case .appleTV: case .appleTV:
return V.appleTV(Strings.Unlocalized.appleTV) return V.appletv(Strings.Unlocalized.appleTV)
case .dns: case .dns:
return V.dns(Strings.Unlocalized.dns) return V.dns(Strings.Unlocalized.dns)
@ -50,7 +50,7 @@ extension AppFeature: LocalizableEntity {
return V.providers return V.providers
case .routing: case .routing:
return V.routing(Strings.Global.routing) return V.routing(Strings.Global.Nouns.routing)
case .sharing: case .sharing:
return V.sharing(Strings.Unlocalized.iCloud) return V.sharing(Strings.Unlocalized.iCloud)

View File

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

View File

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

View File

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

View File

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

View File

@ -11,14 +11,6 @@ import Foundation
// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces // swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces
public enum Strings { public enum Strings {
public enum Alerts { 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 Import {
public enum Passphrase { public enum Passphrase {
/// Enter passphrase for '%@'. /// 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 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 { public enum Dns {
/// Search domains /// Search domains
public static let searchDomains = Strings.tr("Localizable", "entities.dns.search_domains", fallback: "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 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 { public enum TunnelStatus {
/// Activating /// Activating
public static let activating = Strings.tr("Localizable", "entities.tunnel_status.activating", fallback: "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 enum Features {
/// %@ /// %@
public static func appleTV(_ p1: Any) -> String { public static func appletv(_ p1: Any) -> String {
return Strings.tr("Localizable", "features.appleTV", String(describing: p1), fallback: "%@") return Strings.tr("Localizable", "features.appletv", String(describing: p1), fallback: "%@")
} }
/// %@ Settings /// %@ Settings
public static func dns(_ p1: Any) -> String { public static func dns(_ p1: Any) -> String {
@ -183,12 +151,12 @@ public enum Strings {
} }
/// %@ Settings /// %@ Settings
public static func httpProxy(_ p1: Any) -> String { 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 /// Interactive Login
public static let interactiveLogin = Strings.tr("Localizable", "features.interactiveLogin", fallback: "Interactive Login") public static let interactiveLogin = Strings.tr("Localizable", "features.interactiveLogin", fallback: "Interactive Login")
/// On-Demand Rules /// 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 /// All Providers
public static let providers = Strings.tr("Localizable", "features.providers", fallback: "All Providers") public static let providers = Strings.tr("Localizable", "features.providers", fallback: "All Providers")
/// Custom %@ /// Custom %@
@ -201,156 +169,160 @@ public enum Strings {
} }
} }
public enum Global { public enum Global {
/// About public enum Actions {
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 /// Cancel
public static let cancel = Strings.tr("Localizable", "global.cancel", fallback: "Cancel") public static let cancel = Strings.tr("Localizable", "global.actions.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 /// Connect
public static let connect = Strings.tr("Localizable", "global.connect", fallback: "Connect") public static let connect = Strings.tr("Localizable", "global.actions.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 /// Delete
public static let delete = Strings.tr("Localizable", "global.delete", fallback: "Delete") public static let delete = Strings.tr("Localizable", "global.actions.delete", fallback: "Delete")
/// Destination
public static let destination = Strings.tr("Localizable", "global.destination", fallback: "Destination")
/// Disable /// Disable
public static let disable = Strings.tr("Localizable", "global.disable", fallback: "Disable") public static let disable = Strings.tr("Localizable", "global.actions.disable", fallback: "Disable")
/// Disabled
public static let disabled = Strings.tr("Localizable", "global.disabled", fallback: "Disabled")
/// Disconnect /// Disconnect
public static let disconnect = Strings.tr("Localizable", "global.disconnect", fallback: "Disconnect") public static let disconnect = Strings.tr("Localizable", "global.actions.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 /// Duplicate
public static let duplicate = Strings.tr("Localizable", "global.duplicate", fallback: "Duplicate") public static let duplicate = Strings.tr("Localizable", "global.actions.duplicate", fallback: "Duplicate")
/// Edit /// Edit
public static let edit = Strings.tr("Localizable", "global.edit", fallback: "Edit") public static let edit = Strings.tr("Localizable", "global.actions.edit", fallback: "Edit")
/// Empty
public static let empty = Strings.tr("Localizable", "global.empty", fallback: "Empty")
/// Enable /// Enable
public static let enable = Strings.tr("Localizable", "global.enable", fallback: "Enable") public static let enable = Strings.tr("Localizable", "global.actions.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 /// Hide
public static let hide = Strings.tr("Localizable", "global.hide", fallback: "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 /// Hostname
public static let hostname = Strings.tr("Localizable", "global.hostname", fallback: "Hostname") public static let hostname = Strings.tr("Localizable", "global.nouns.hostname", fallback: "Hostname")
/// Interface /// Interface
public static let interface = Strings.tr("Localizable", "global.interface", fallback: "Interface") public static let interface = Strings.tr("Localizable", "global.nouns.interface", fallback: "Interface")
/// Keep-alive /// Keep-alive
public static let keepAlive = Strings.tr("Localizable", "global.keep_alive", fallback: "Keep-alive") public static let keepAlive = Strings.tr("Localizable", "global.nouns.keep_alive", fallback: "Keep-alive")
/// Key /// Key
public static let key = Strings.tr("Localizable", "global.key", fallback: "Key") public static let key = Strings.tr("Localizable", "global.nouns.key", fallback: "Key")
/// Last update /// Last update
public static let lastUpdate = Strings.tr("Localizable", "global.last_update", fallback: "Last update") public static let lastUpdate = Strings.tr("Localizable", "global.nouns.last_update", fallback: "Last update")
/// Loading /// Loading
public static let loading = Strings.tr("Localizable", "global.loading", fallback: "Loading") public static let loading = Strings.tr("Localizable", "global.nouns.loading", fallback: "Loading")
/// Method /// Method
public static let method = Strings.tr("Localizable", "global.method", fallback: "Method") public static let method = Strings.tr("Localizable", "global.nouns.method", fallback: "Method")
/// Modules /// Modules
public static let modules = Strings.tr("Localizable", "global.modules", fallback: "Modules") public static let modules = Strings.tr("Localizable", "global.nouns.modules", fallback: "Modules")
/// %d seconds /// %d seconds
public static func nSeconds(_ p1: Int) -> String { public static func nSeconds(_ p1: Int) -> String {
return Strings.tr("Localizable", "global.n_seconds", p1, fallback: "%d seconds") return Strings.tr("Localizable", "global.nouns.n_seconds", p1, fallback: "%d seconds")
} }
/// Name /// Name
public static let name = Strings.tr("Localizable", "global.name", fallback: "Name") public static let name = Strings.tr("Localizable", "global.nouns.name", fallback: "Name")
/// Networks /// Networks
public static let networks = Strings.tr("Localizable", "global.networks", fallback: "Networks") public static let networks = Strings.tr("Localizable", "global.nouns.networks", fallback: "Networks")
/// No content /// No content
public static let noContent = Strings.tr("Localizable", "global.no_content", fallback: "No content") public static let noContent = Strings.tr("Localizable", "global.nouns.no_content", fallback: "No content")
/// No selection /// No selection
public static let noSelection = Strings.tr("Localizable", "global.no_selection", fallback: "No selection") public static let noSelection = Strings.tr("Localizable", "global.nouns.no_selection", fallback: "No selection")
/// None /// None
public static let `none` = Strings.tr("Localizable", "global.none", fallback: "None") public static let `none` = Strings.tr("Localizable", "global.nouns.none", fallback: "None")
/// OK /// OK
public static let ok = Strings.tr("Localizable", "global.ok", fallback: "OK") public static let ok = Strings.tr("Localizable", "global.nouns.ok", fallback: "OK")
/// On-demand /// On-demand
public static let onDemand = Strings.tr("Localizable", "global.on_demand", fallback: "On-demand") public static let onDemand = Strings.tr("Localizable", "global.nouns.on_demand", fallback: "On-demand")
/// Other /// Other
public static let other = Strings.tr("Localizable", "global.other", fallback: "Other") public static let other = Strings.tr("Localizable", "global.nouns.other", fallback: "Other")
/// Password /// Password
public static let password = Strings.tr("Localizable", "global.password", fallback: "Password") public static let password = Strings.tr("Localizable", "global.nouns.password", fallback: "Password")
/// Port /// Port
public static let port = Strings.tr("Localizable", "global.port", fallback: "Port") public static let port = Strings.tr("Localizable", "global.nouns.port", fallback: "Port")
/// Private key /// Private key
public static let privateKey = Strings.tr("Localizable", "global.private_key", fallback: "Private key") public static let privateKey = Strings.tr("Localizable", "global.nouns.private_key", fallback: "Private key")
/// Profile /// Profile
public static let profile = Strings.tr("Localizable", "global.profile", fallback: "Profile") public static let profile = Strings.tr("Localizable", "global.nouns.profile", fallback: "Profile")
/// Protocol /// Protocol
public static let `protocol` = Strings.tr("Localizable", "global.protocol", fallback: "Protocol") public static let `protocol` = Strings.tr("Localizable", "global.nouns.protocol", fallback: "Protocol")
/// Provider /// Provider
public static let provider = Strings.tr("Localizable", "global.provider", fallback: "Provider") public static let provider = Strings.tr("Localizable", "global.nouns.provider", fallback: "Provider")
/// Public key /// Public key
public static let publicKey = Strings.tr("Localizable", "global.public_key", fallback: "Public key") public static let publicKey = Strings.tr("Localizable", "global.nouns.public_key", fallback: "Public key")
/// Purchase
public static let purchase = Strings.tr("Localizable", "global.purchase", fallback: "Purchase")
/// Region /// Region
public static let region = Strings.tr("Localizable", "global.region", fallback: "Region") public static let region = Strings.tr("Localizable", "global.nouns.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 /// Route
public static let route = Strings.tr("Localizable", "global.route", fallback: "Route") public static let route = Strings.tr("Localizable", "global.nouns.route", fallback: "Route")
/// Routes /// Routes
public static let routes = Strings.tr("Localizable", "global.routes", fallback: "Routes") public static let routes = Strings.tr("Localizable", "global.nouns.routes", fallback: "Routes")
/// Routing /// Routing
public static let routing = Strings.tr("Localizable", "global.routing", fallback: "Routing") public static let routing = Strings.tr("Localizable", "global.nouns.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 /// Server
public static let server = Strings.tr("Localizable", "global.server", fallback: "Server") public static let server = Strings.tr("Localizable", "global.nouns.server", fallback: "Server")
/// Servers /// Servers
public static let servers = Strings.tr("Localizable", "global.servers", fallback: "Servers") public static let servers = Strings.tr("Localizable", "global.nouns.servers", fallback: "Servers")
/// Settings /// Settings
public static let settings = Strings.tr("Localizable", "global.settings", fallback: "Settings") public static let settings = Strings.tr("Localizable", "global.nouns.settings", fallback: "Settings")
/// Show
public static let show = Strings.tr("Localizable", "global.show", fallback: "Show")
/// Status /// Status
public static let status = Strings.tr("Localizable", "global.status", fallback: "Status") public static let status = Strings.tr("Localizable", "global.nouns.status", fallback: "Status")
/// Subnet /// Subnet
public static let subnet = Strings.tr("Localizable", "global.subnet", fallback: "Subnet") public static let subnet = Strings.tr("Localizable", "global.nouns.subnet", fallback: "Subnet")
/// Unknown /// Unknown
public static let unknown = Strings.tr("Localizable", "global.unknown", fallback: "Unknown") public static let unknown = Strings.tr("Localizable", "global.nouns.unknown", fallback: "Unknown")
/// Username /// Username
public static let username = Strings.tr("Localizable", "global.username", fallback: "Username") public static let username = Strings.tr("Localizable", "global.nouns.username", fallback: "Username")
/// Version /// Version
public static let version = Strings.tr("Localizable", "global.version", fallback: "Version") public static let version = Strings.tr("Localizable", "global.nouns.version", fallback: "Version")
}
} }
public enum Modules { public enum Modules {
public enum Dns { public enum Dns {
@ -366,16 +338,16 @@ public enum Strings {
public enum General { public enum General {
public enum Rows { public enum Rows {
/// %@ /// %@
public static func appleTv(_ p1: Any) -> String { public static func appletv(_ p1: Any) -> String {
return Strings.tr("Localizable", "modules.general.rows.apple_tv", String(describing: p1), fallback: "%@") return Strings.tr("Localizable", "modules.general.rows.appletv", String(describing: p1), fallback: "%@")
} }
/// Import from file... /// Import from file...
public static let importFromFile = Strings.tr("Localizable", "modules.general.rows.import_from_file", fallback: "Import from file...") public static let importFromFile = Strings.tr("Localizable", "modules.general.rows.import_from_file", fallback: "Import from file...")
/// Enabled /// Enabled
public static let shared = Strings.tr("Localizable", "modules.general.rows.shared", fallback: "Enabled") public static let shared = Strings.tr("Localizable", "modules.general.rows.shared", fallback: "Enabled")
public enum AppleTv { public enum Appletv {
/// Drop TV restriction /// 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 { public enum Shared {
/// Share on iCloud /// 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 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 { public enum Placeholders {
/// secret /// secret
public static let secret = Strings.tr("Localizable", "placeholders.secret", fallback: "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 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 Theme {
public enum Confirmation { public enum Confirmation {
/// Cancel /// 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 Views {
public enum About { public enum About {
/// About /// About
@ -675,6 +557,64 @@ public enum Strings {
public static let resources = Strings.tr("Localizable", "views.about.sections.resources", fallback: "Resources") 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 { public enum Diagnostics {
/// Diagnostics /// Diagnostics
public static let title = Strings.tr("Localizable", "views.diagnostics.title", fallback: "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 /// 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 /// 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 Alerts {
public enum Delete { public enum Delete {
/// Do you want to discard these profiles? You will not be able to recover them later. /// Do you want to discard these profiles? You will not be able to recover them later.
/// ///
/// %@ /// %@
public static func message(_ p1: Any) -> String { 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 { public enum Items {
/// Discard /// 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 /// 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 Sections {
public enum Main { 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. /// 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 { 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 Profile {
public enum Alerts { public enum Alerts {
public enum Purchase { 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 static let addModule = Strings.tr("Localizable", "views.profile.rows.add_module", fallback: "Add module")
} }
} }
public enum Profiles { public enum Providers {
public enum Errors { /// Clear filters
/// Unable to duplicate profile '%@'. public static let clearFilters = Strings.tr("Localizable", "views.providers.clear_filters", fallback: "Clear filters")
public static func duplicate(_ p1: Any) -> String { /// Last updated on %@
return Strings.tr("Localizable", "views.profiles.errors.duplicate", String(describing: p1), fallback: "Unable to duplicate profile '%@'.") public static func lastUpdated(_ p1: Any) -> String {
return Strings.tr("Localizable", "views.providers.last_updated", String(describing: p1), fallback: "Last updated on %@")
} }
/// Unable to import profiles. /// None
public static let `import` = Strings.tr("Localizable", "views.profiles.errors.import", fallback: "Unable to import profiles.") public static let noProvider = Strings.tr("Localizable", "views.providers.no_provider", fallback: "None")
/// Unable to execute tunnel operation. /// Only favorites
public static let tunnel = Strings.tr("Localizable", "views.profiles.errors.tunnel", fallback: "Unable to execute tunnel operation.") 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 Folders { public enum Vpn {
/// Installed profile /// No servers
public static let activeProfile = Strings.tr("Localizable", "views.profiles.folders.active_profile", fallback: "Installed profile") public static let noServers = Strings.tr("Localizable", "views.providers.vpn.no_servers", fallback: "No servers")
/// Add profile /// Preset
public static let addProfile = Strings.tr("Localizable", "views.profiles.folders.add_profile", fallback: "Add profile") public static let preset = Strings.tr("Localizable", "views.providers.vpn.preset", fallback: "Preset")
/// My profiles public enum Category {
public static let `default` = Strings.tr("Localizable", "views.profiles.folders.default", fallback: "My profiles") /// All categories
/// No profiles public static let any = Strings.tr("Localizable", "views.providers.vpn.category.any", fallback: "All categories")
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...")
}
}
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 Settings { public enum Ui {
/// Erase iCloud store public enum ConnectionStatus {
public static let eraseIcloud = Strings.tr("Localizable", "views.settings.erase_icloud", fallback: "Erase iCloud store") /// (on-demand)
/// Keep in menu bar public static let onDemandSuffix = Strings.tr("Localizable", "views.ui.connection_status.on_demand_suffix", fallback: " (on-demand)")
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 KeepsInMenu { public enum PurchaseRequired {
/// Enable this to keep the app in the menu bar after closing it. public enum Purchase {
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.") /// Purchase required
public static let help = Strings.tr("Localizable", "views.ui.purchase_required.purchase.help", fallback: "Purchase required")
} }
public enum LaunchesOnLogin { public enum Restricted {
/// Open the app in background after login. /// Feature is restricted
public static let footer = Strings.tr("Localizable", "views.settings.launches_on_login.footer", fallback: "Open the app in background after login.") public static let help = Strings.tr("Localizable", "views.ui.purchase_required.restricted.help", fallback: "Feature is restricted")
} }
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.")
} }
} }
} }

View File

@ -1,154 +1,7 @@
// MARK: Global // MARK: Views
"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.";
"views.about.title" = "About"; "views.about.title" = "About";
"views.about.sections.resources" = "Resources"; "views.about.sections.resources" = "Resources";
"views.about.links.title" = "Links"; "views.about.links.title" = "Links";
"views.about.links.sections.support" = "Support"; "views.about.links.sections.support" = "Support";
"views.about.links.sections.web" = "Web"; "views.about.links.sections.web" = "Web";
@ -157,22 +10,29 @@
"views.about.links.rows.home_page" = "Home page"; "views.about.links.rows.home_page" = "Home page";
"views.about.links.rows.disclaimer" = "Disclaimer"; "views.about.links.rows.disclaimer" = "Disclaimer";
"views.about.links.rows.privacy_policy" = "Privacy policy"; "views.about.links.rows.privacy_policy" = "Privacy policy";
"views.about.credits.title" = "Credits"; "views.about.credits.title" = "Credits";
"views.about.credits.licenses" = "Licenses"; "views.about.credits.licenses" = "Licenses";
"views.about.credits.notices" = "Notices"; "views.about.credits.notices" = "Notices";
"views.about.credits.translations" = "Translations"; "views.about.credits.translations" = "Translations";
"views.migrate.title" = "Migrate"; "views.app.rows.not_installed" = "Select a profile";
"views.migrate.no_profiles" = "Nothing to migrate"; "views.app.rows.no_modules" = "No active modules";
"views.migrate.items.discard" = "Discard"; "views.app.folders.active_profile" = "Installed profile";
"views.migrate.items.migrate" = "Proceed"; "views.app.folders.default" = "My profiles";
"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.app.folders.add_profile" = "Add profile";
"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.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.app.profile_context.connect_to" = "Connect to...";
"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_menu.items.quit" = "Quit %@";
"views.diagnostics.title" = "Diagnostics"; "views.diagnostics.title" = "Diagnostics";
"views.diagnostics.sections.live" = "Live log"; "views.diagnostics.sections.live" = "Live log";
@ -181,18 +41,73 @@
"views.diagnostics.rows.tunnel" = "Tunnel"; "views.diagnostics.rows.tunnel" = "Tunnel";
"views.diagnostics.rows.include_private_data" = "Include private data"; "views.diagnostics.rows.include_private_data" = "Include private data";
"views.diagnostics.rows.remove_tunnel_logs" = "Delete all logs"; "views.diagnostics.rows.remove_tunnel_logs" = "Delete all logs";
"views.diagnostics.openvpn.rows.server_configuration" = "Server configuration"; "views.diagnostics.openvpn.rows.server_configuration" = "Server configuration";
"views.diagnostics.report_issue.title" = "Report issue"; "views.diagnostics.report_issue.title" = "Report issue";
"views.diagnostics.alerts.report_issue.email" = "The device is not configured to send e-mails."; "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.header" = "%@";
"modules.general.sections.storage.footer" = "Profiles are stored to %@ encrypted."; "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.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.general.rows.import_from_file" = "Import from file...";
"modules.dns.servers.add" = "Add address"; "modules.dns.servers.add" = "Add address";
@ -240,78 +155,148 @@
"modules.wireguard.preshared_key" = "Pre-shared key"; "modules.wireguard.preshared_key" = "Pre-shared key";
"modules.wireguard.allowed_ips" = "Allowed IPs"; "modules.wireguard.allowed_ips" = "Allowed IPs";
// MARK: - Providers // MARK: - Entities
"providers.no_provider" = "None"; "entities.tunnel_status.inactive" = "Inactive";
"providers.select_provider" = "Select a provider"; "entities.tunnel_status.activating" = "Activating";
"providers.select_entity" = "Select"; "entities.tunnel_status.active" = "Active";
"providers.only_favorites" = "Only favorites"; "entities.tunnel_status.deactivating" = "Deactivating";
"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";
// 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"; "entities.http_proxy.bypass_domains" = "Bypass domains";
"theme.confirmation.cancel" = "Cancel";
"theme.confirmation.message" = "Are you sure you want to proceed with this operation?";
"theme.lock_screen.reason" = "%@ is locked";
// 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)"; "entities.openvpn.compression_algorithm.other" = "Unsupported";
"ui.profile_context.connect_to" = "Connect to..."; "entities.openvpn.otp_method.none" = "None";
"ui.purchase_required.purchase.help" = "Purchase required"; "entities.openvpn.otp_method.append" = "Append";
"ui.purchase_required.restricted.help" = "Feature is restricted"; "entities.openvpn.otp_method.encode" = "Encode";
// MARK: - Paywall // MARK: - Features
"paywall.sections.recurring.header" = "All features"; "features.appletv" = "%@";
"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.dns" = "%@ Settings"; "features.dns" = "%@ Settings";
"features.httpProxy" = "%@ Settings"; "features.http_proxy" = "%@ Settings";
"features.interactiveLogin" = "Interactive Login"; "features.interactiveLogin" = "Interactive Login";
"features.onDemand" = "On-Demand Rules"; "features.on_demand" = "On-Demand Rules";
"features.providers" = "All Providers"; "features.providers" = "All Providers";
"features.routing" = "Custom %@"; "features.routing" = "Custom %@";
"features.sharing" = "%@"; "features.sharing" = "%@";
"modules.general.sections.storage.footer.purchase.tv_beta" = "TV profiles do not work in beta builds."; // MARK: - Global
"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: - 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.message" = "Enter passphrase for '%@'.";
"alerts.import.passphrase.ok" = "Decrypt"; "alerts.import.passphrase.ok" = "Decrypt";
"alerts.iap.restricted.title" = "Restricted"; // MARK: Global (App errors)
"alerts.iap.restricted.message" = "Some features are unavailable in this build.";
// MARK: - Errors
"errors.app.default" = "Unable to complete operation.";
"errors.app.empty_products" = "Unable to fetch products, please retry later."; "errors.app.empty_products" = "Unable to fetch products, please retry later.";
"errors.app.empty_profile_name" = "Profile name is empty."; "errors.app.empty_profile_name" = "Profile name is empty.";
"errors.app.malformed_module" = "Module %@ is malformed. %@"; "errors.app.malformed_module" = "Module %@ is malformed. %@";
"errors.app.provider.required" = "No provider selected."; "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.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.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."; "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.timeout" = "Timeout";
"errors.tunnel.tls" = "TLS failed"; "errors.tunnel.tls" = "TLS failed";
"errors.tunnel.generic" = "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) #if os(iOS) || os(tvOS)
ThemeImage(.close) ThemeImage(.close)
#else #else
Text(Strings.Global.cancel) Text(Strings.Global.Actions.cancel)
#endif #endif
} }
} }

View File

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

View File

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

View File

@ -172,13 +172,13 @@ private extension OpenVPNCredentialsView {
} }
var usernameField: some View { 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) .textContentType(.username)
.focused($focusedField, equals: .username) .focused($focusedField, equals: .username)
} }
var passwordField: some View { 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) .textContentType(.password)
.focused($focusedField, equals: .password) .focused($focusedField, equals: .password)
.onSubmit { .onSubmit {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -82,6 +82,6 @@ private extension PurchaseRequiredButton {
} }
var helpMessage: String { 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 { guard let provider = providerModule.provider else {
errorHandler.handle( errorHandler.handle(
PassepartoutError(.providerRequired), PassepartoutError(.providerRequired),
title: Strings.Global.connection title: Strings.Global.Nouns.connection
) )
return return
} }
@ -168,8 +168,8 @@ private extension TunnelToggleButton {
} catch { } catch {
errorHandler.handle( errorHandler.handle(
error, error,
title: Strings.Global.connection, title: Strings.Global.Nouns.connection,
message: Strings.Views.Profiles.Errors.tunnel message: Strings.Views.App.Errors.tunnel
) )
} }
} }