diff --git a/assets/data/i18n/en.json b/assets/data/i18n/en.json new file mode 100644 index 000000000..c125fd7e6 --- /dev/null +++ b/assets/data/i18n/en.json @@ -0,0 +1,937 @@ +{ + "common": { + "save": "Save", + "cancel": "Cancel", + "close": "Close", + "delete": "Delete", + "edit": "Edit", + "add": "Add", + "remove": "Remove", + "confirm": "Confirm", + "yes": "Yes", + "no": "No", + "ok": "OK", + "loading": "Loading...", + "search": "Search", + "filter": "Filter", + "clear": "Clear", + "reset": "Reset", + "apply": "Apply", + "back": "Back", + "next": "Next", + "previous": "Previous", + "enabled": "Enabled", + "enable": "Enable", + "disabled": "Disabled", + "active": "Active", + "inactive": "Inactive", + "all": "All", + "none": "None", + "other": "Other", + "unknown": "Unknown", + "never": "never", + "now": "now", + "today": "today", + "yesterday": "yesterday", + "error": "Error", + "warning": "Warning", + "info": "Info", + "success": "Success", + "import": "Import", + "customize": "Customize", + "custom": "Custom", + "tip": "Tip", + "status": "Status", + "apps": "Apps", + "copy": "Copy", + "title": "Title", + "faq": "FAQ", + "where": "where", + "by": "by", + "beta": "Beta", + "experimental": "Experimental", + "advanced": "Advanced", + "developer": "Developer", + "home": "Home", + "exit": "Exit", + "noLocation": "No Location", + "community": "Community", + "prompting": "Prompting", + "destination": "Destination", + "interferingSettings": "The following enabled settings may interfere", + "groupBy": "Group By", + "sort": "Sort", + "automatic": "Automatic", + "getPro": "Get Pro", + "yourDevice": "Your Device", + "asOrgNotInDB": "AS Organization not in DB", + "logout": "Logout", + "refresh": "Refresh", + "reload": "Reload", + "clearAll": "Clear All", + "openAccountPage": "Open Account Page", + "saved": "Saved", + "restartRequired": "Restart required", + "reloadRequired": "Reload required", + "invalidValue": "Invalid Value", + "lastReload": "Last Reload", + "showMore": "Show More Identities", + "showLess": "Show Less Identities", + "more": "More", + "comingSoon": "coming soon", + "availableIn": "Available in", + "upgrade": "Upgrade" + }, + "search": { + "placeholder": "Search for connections, press ENTER to apply", + "fullTextSearch": "Full-Text Search", + "filterBy": "Filter by", + "searchFaqs": "Search FAQs", + "summarizeReport": "Summarize your report" + }, + "nav": { + "dashboard": "Dashboard", + "networkActivity": "Network Activity", + "appsAndProfiles": "Apps and Profiles", + "spn": "Safing Privacy Network", + "globalSettings": "Global Settings", + "getHelp": "Get Help", + "versionAndTools": "Version and Tools", + "version": "Version", + "releaseChannel": "Release Channel", + "shutdown": "Shutdown", + "restart": "Restart", + "pause": "Pause", + "resume": "Resume", + "pauseSpn": "Pause SPN", + "pauseAll": "Pause All Protection", + "notifications": "Notifications", + "language": "Language", + "checkUpdates": "Check for Updates", + "viewChangelog": "View Changelog", + "reloadUI": "Reload UI", + "showIntro": "Show Intro Screen", + "reinitSPN": "Re-Initialize SPN", + "logoutCompletely": "Logout Completely", + "resetBroadcast": "Reset Broadcast State", + "clearDNS": "Clear DNS Cache", + "openDataDir": "Open Data Directory", + "copyDebugInfo": "Copy Debug Info", + "cleanupHistory": "Cleanup Network History", + "pauseSpn5min": "Pause SPN for 5 minutes", + "pauseSpn15min": "Pause SPN for 15 minutes", + "pauseSpn1hour": "Pause SPN for 1 hour", + "pause5min": "Pause for 5 minutes", + "pause15min": "Pause for 15 minutes", + "pause1hour": "Pause for 1 hour", + "resumeNow": "Resume now", + "pauseAndResume": "Pause and Resume", + "shutdownAndRestart": "Shutdown and Restart" + }, + "dashboard": { + "title": "Dashboard", + "welcomeBack": "Welcome back", + "welcomeBackUser": "Welcome back, {{username}}!", + "currentPlan": "Your current plan is", + "portmasterFree": "Portmaster Free", + "endsIn": "and ends in", + "endsNever": "never", + "autoRenewsIn": "and auto-renews in", + "accountDetails": "Account Details", + "loginSubscribe": "Login / Subscribe", + "features": "Features", + "recentActivity": "Recent Activity", + "connectionsBlocked": "Connections Blocked", + "activeConnections": "Active Connections", + "activeApps": "Active Apps", + "dataReceived": "Data Received", + "dataSent": "Data Sent", + "spnIdentities": "SPN Identities", + "availableInPlus": "Available in Portmaster Plus", + "availableInPro": "Available in Portmaster Pro", + "activeBlockedConnections": "Active/Blocked Connections", + "connectionsTunneledSpn": "Connections Tunneled through SPN", + "recentConnectionsPerCountry": "Recent Connections per Country", + "recentlyBlockedApps": "Recently Blocked Applications", + "noBlockedApps": "No applications have been blocked in the last 10 minutes.", + "recentConnectionCountries": "Recent Connection Countries", + "recentTopConsumers": "Recent Top Consumers", + "recentBandwidthUsage": "Recent Bandwidth Usage", + "news": "News", + "newsUnavailable": "News is only available if intel data updates are enabled", + "newsLoading": "Just a second, we're loading the latest news..." + }, + "monitor": { + "title": "Network Monitor", + "networkActivity": "Network Activity", + "connections": "connections", + "historyAvailable": "Network history data available as of", + "noHistoryData": "No network history data available.", + "searchHint": "Use the search bar and drop downs to search and filter the last 10 minutes of network traffic. Optionally, search all network history data if enabled.", + "blocked": "Blocked", + "allowed": "Allowed", + "failed": "Failed", + "tunneled": "Tunneled", + "encrypted": "Encrypted", + "internal": "Internal", + "details": "Details", + "process": "Process", + "domain": "Domain", + "ip": "IP Address", + "port": "Port", + "protocol": "Protocol", + "country": "Country", + "started": "Started", + "ended": "Ended", + "duration": "Duration", + "bytesIn": "Bytes In", + "bytesOut": "Bytes Out", + "verdict": "Verdict", + "reason": "Reason", + "profile": "Profile", + "scope": "Scope", + "exitNode": "Exit Node", + "entryNode": "Entry Node", + "route": "Route", + "dataUsage": "Data Usage", + "copyJSON": "Copy JSON", + "internetPeerToPeer": "Internet Peer-to-Peer", + "internetMulticast": "Internet Multicast", + "deviceLocal": "Device-Local", + "lanPeerToPeer": "LAN Peer-to-Peer", + "lanMulticast": "LAN Multicast", + "spnTunnel": "SPN Tunnel", + "results": "Results", + "ofTotalConnections": "of {{count}} total connections", + "useAsFilter": "Use as filter", + "copyQueryToClipboard": "Copy query to clipboard", + "internalConnectionsDevMode": "Internal connections are only displayed in Developer Mode", + "app": "App", + "global": "Global", + "proTipShiftClick": "Use
Shift + Click
to add connection attributes to the current filter.", + "proTipShiftHighlight": "Hold
Shift
to highlight attributes that can be used in the filter.", + "proTipCtrlCopy": "Hold
CTRL
and click attributes to copy them to the clipboard." + }, + "apps": { + "title": "Applications", + "overview": "Overview", + "settings": "Settings", + "history": "History", + "active": "Active", + "recentlyUsed": "Recently Used", + "recentlyEdited": "Recently Edited", + "other": "Other", + "allApps": "All Apps", + "noApps": "No applications found", + "noAppsMatch": "No applications match your search term.", + "blockConnections": "Block Connections", + "useSPN": "Use SPN", + "keepHistory": "Keep History", + "viewActive": "View Active", + "viewAll": "View All", + "quickSettings": "Quick Settings", + "appSettings": "App Settings", + "globalDefault": "Global Default", + "appSpecific": "App Specific", + "createProfile": "Create profile", + "importProfile": "Import Profile", + "mergeOrDelete": "Merge or Delete profiles", + "manage": "Manage", + "mergeProfiles": "Merge Profiles", + "deleteProfiles": "Delete Profiles", + "selected": "selected", + "settingsEdited": "Settings Edited", + "connections": "Connections", + "dataUsage": "Data Usage", + "countries": "Countries", + "blocked": "Blocked", + "received": "Received", + "sent": "Sent", + "availableInPlus": "Available in Plus", + "more": "More", + "editAppProfile": "Edit App Profile", + "exportAppProfile": "Export App Profile", + "deleteAppProfile": "Delete App Profile", + "searchSettings": "Search Settings", + "getHelp": "Get Help", + "sortBy": "Sort By", + "appSpecificSettings": "App Specific Settings", + "usingGlobalSettings": "is fully using the global settings.", + "startCreatingExceptions": "Start creating exceptions for it now.", + "editSettings": "Edit Settings", + "details": "Details", + "name": "Name", + "path": "Path", + "binary": "Binary", + "activeConnections": "Active Connections", + "networkHistory": "Network History", + "asOf": "As of", + "removeConnections": "Remove all", + "none": "None", + "networkHistoryTooltip": "Network History feature is available in Portmaster Plus", + "internal": "Internal", + "source": "Source", + "revision": "Revision", + "layers": "Layers", + "description": "Description", + "fingerprints": "Fingerprints", + "fingerprintsDescription": "This profile will be applied to processes that match one of the following fingerprints:", + "deleteProfile": "Delete Profile", + "deleteProfileDescription": "You can completely delete this profile to get rid of any settings. The profile will be automatically re-created with default settings as soon as the application starts to use the network.", + "debugging": "Debugging", + "debuggingDescription": "When reporting issues with this app please make sure to include the following debug information:", + "copyDebugInfo": "Copy Debug Information", + "insights": "Insights", + "delete": "Delete" + }, + "settings": { + "title": "Global Settings", + "general": "General", + "privacy": "Privacy", + "security": "Security", + "network": "Network", + "dns": "DNS", + "connections": "Connections", + "filter": "Filter", + "spn": "SPN", + "advanced": "Advanced", + "resetToDefault": "Reset to Default", + "importSettings": "Import Settings", + "exportSettings": "Export Settings", + "searchSettings": "Search settings...", + "openSettings": "Open Settings", + "useGlobalSetting": "Use global setting", + "cancelExport": "Cancel Export", + "quickSettings": "Quick Settings", + "globalSettings": "Global Settings", + "stackedOnGlobal": "This setting stacks on top of the following", + "globalSetting": "global setting", + "inheritedFromGlobal": "Inherited from", + "appSpecificConfig": "App specific configuration" + }, + "rules": { + "addRule": "Add Rule", + "removeRules": "Remove Rules", + "noEntries": "No entries available", + "applied": "applied", + "fromGlobalSettings": "from Global Settings", + "fromAppSettings": "from Application Settings" + }, + "spn": { + "title": "Safing Privacy Network", + "status": "Status", + "connected": "Connected", + "disconnected": "Disconnected", + "connecting": "Connecting", + "disabled": "Disabled", + "enableToStart": "Enable SPN to start using.", + "exit": "SPN Exit", + "increaseProtection": "Increase privacy protection", + "youreProtected": "You're protected", + "connectingToSPN": "Connecting to the SPN ...", + "map": "Map", + "nodes": "Nodes", + "routes": "Routes", + "identities": "Identities", + "home": "Home", + "exitNode": "Exit Node", + "entryNode": "Entry Node", + "transit": "Transit", + "load": "Load", + "latency": "Latency", + "countries": "Countries", + "loadingData": "Loading data, please wait ...", + "login": "Login", + "enableSPN": "Enable the SPN", + "pricing": "Pricing", + "proTip": "Pro Tip", + "statistics": "Statistics", + "totalNodes": "Total Nodes", + "bySafing": "by Safing", + "byCommunity": "by Community", + "safingNodes": "Safing Nodes", + "communityNodes": "Community Nodes", + "usedAsTransit": "used as Transit", + "usedAsExit": "used as Exit", + "clickCountryForDetails": "Click country for details", + "exitNodes": "Exit Nodes", + "nodesInUse": "Nodes In Use", + "appsExitingInCountry": "The following Apps have connections that are routed through the SPN and use an exit node in {{countryName}} ({{countryCode}}):", + "connections": "connections", + "name": "Name", + "operator": "Operator", + "usedAs": "Used As", + "capacity": "Capacity", + "homeNode": "Home Node", + "transitNode": "Transit Node", + "showDetails": "Show Details", + "showExitConnections": "Show exit connections", + "copyNodeID": "Copy Node ID", + "runBy": "Run By", + "nodeRunBy": "This SPN Node is run by", + "verifiedOperator": "Verified operator: {{owner}}", + "community": "Community", + "nodeOffline": "Node is Offline", + "nodeHasIssues": "Node has Issues", + "details": "Details", + "verifiedOwner": "Verified Owner", + "firstSeen": "First Seen", + "states": "States", + "sessionActive": "Session Active", + "hopDistance": "Hop Distance", + "hops": "HOPS", + "exitConnections": "Exit Connections", + "showExitInMonitor": "Show exit connections in monitor.", + "route": "Route", + "connectedNodes": "Connected Nodes", + "homeNodeHelp": "This node does not know the destinations of your connections but may know where you are", + "exitNodeHelp": "This node does not know who you are but knows the destination of connections for which it is used as an exit node", + "transitNodeHelp": "This node does not know who you are and where you are connecting to", + "failsafeBlocking": "Fail-safe blocking enabled", + "failedToConnect": "SPN failed to connect", + "homeEntryNode": "SPN Home (Entry) Node", + "connectedTo": "Connected to", + "uplinkEncrypted": "Uplink is always encrypted", + "builtWithTransport": "Built with transport/decoy", + "tips": { + "ctrlClick": "Hold
CTRL
key and click a node on the map to immediately open the node details dialog.", + "shiftClick": "Hold
SHIFT
key to open more than one node overlay when clicking the node icon.", + "moveOverlay": "To keep node overlays open move them using the move icon. Double click to revert the overlay position on the map.", + "countryClick": "Click on a country to get more information about all nodes in that country and a list of Apps that use nodes in the country as an identity." + }, + "carousel": { + "multipleIdentities": "Get Multiple Identities for Each App", + "multipleIdentitiesDesc": "Automatically get a vast amount of identities (IP addresses). The SPN calculates an individual path for every connection through the privacy network. Spread your connections across the globe, without any effort.", + "accessRegional": "Simply Access Regional Content", + "accessRegionalDesc": "Is a website blocked or restricted in your country? Because SPN makes that connection exit near the destination server, it will automatically unblock the content. SPN has best coverage in Europe and North America.", + "adjustPrivacy": "Easily Adjust", + "yourPrivacy": "Your Privacy", + "adjustPrivacyDesc": "SPN just works and does the heavy lifting for you. But of course you can easily configure the settings, so it fits your needs: Exclude certain apps and domains from the SPN. Or never exit in specific countries. And so much more...", + "builtFromScratch": "Built from Scratch, for", + "builtFromScratchDesc": "SPN is built from the ground up. Privacy is cooked right into it. Inspired by Tor, it comes with onion routing and state of the art encryption. Fully open source so all our claims can be validated.", + "byeByeVpns": "Bye Bye, VPNs", + "byeByeVpnsDesc": "VPN technology was NOT built for user privacy, but for company security. Because of that, you can only trust a VPN provider's policy - and many have been caught abusing user data. Honestly, the best way forward: just stop paying for outdated technology.", + "mostVpns": "Most VPNs", + "readComparisonBlog": "Read Comparison Blog", + "multipleIdentitiesSimultaneous": "Multiple Identities (simultaneous)", + "individualAppsSettings": "Individual Apps Settings", + "easySetup": "Easy Setup", + "browserOnly": "Browser Only", + "availability": "Availability", + "openSource": "Open Source", + "builtForPrivacy": "Built for Privacy" + }, + "networkStatus": { + "title": "Network Status", + "loading": "Loading Network Status ...", + "openOnGithub": "Open on Github", + "opened": "opened", + "closed": "closed", + "by": "by" + } + }, + "support": { + "title": "Support", + "getHelp": "Get Help", + "reportBug": "Report a Bug", + "documentation": "Documentation", + "community": "Community", + "email": "Email Support", + "github": "GitHub Issues", + "discord": "Discord", + "includedDebugInfo": "Included Debug Info", + "ticket": "Support Ticket", + "submitTicket": "Submit Ticket", + "debugInfo": "Debug Information", + "systemInfo": "System Information", + "logs": "Logs", + "copyToClipboard": "Copy to Clipboard", + "downloadLogs": "Download Logs", + "openOnGithub": "Open on GitHub", + "createOnGithub": "Create on GitHub", + "sendPrivateTicket": "Send private Ticket", + "relatedIssues": "Related Issues", + "publicIssuesRelated": "Public issues related to your title:", + "noRelatedIssuesFound": "No related issues were found.", + "opened": "opened", + "closed": "closed", + "debugInfoDescription": "The following debug information will be sent together with your report. Please check it and remove potentially sensitive information. The debug information sent with your reports will be saved on Safing's self-hosted pastebin server and is viewable via its created url. The data is automatically destroyed after one month.", + "portmasterVersion": "Portmaster Version", + "builtOn": "built on" + }, + "notifications": { + "title": "Notifications", + "notification": "Notification", + "broadcastNotification": "Broadcast Notification", + "noNotifications": "No notifications", + "markAllRead": "Mark all as read", + "clearAll": "Clear all", + "new": "New", + "read": "Read" + }, + "prompts": { + "title": "Connection Prompts", + "noPrompts": "No Prompts", + "allow": "Allow", + "block": "Block", + "allowOnce": "Allow Once", + "blockOnce": "Block Once", + "allowAlways": "Always Allow", + "blockAlways": "Always Block", + "remember": "Remember this choice", + "perConnection": "Per Connection", + "allowAll": "Allow All", + "blockAll": "Block All", + "defaultAction": "Default Action", + "allowApp": "Allow App", + "blockApp": "Block App", + "changeDefault": "Change Default", + "more": "more", + "showLess": "Show less" + }, + "network": { + "rating": "Network Rating", + "trusted": "Trusted", + "untrusted": "Untrusted", + "danger": "Danger", + "home": "Home", + "public": "Public", + "unknown": "Unknown" + }, + "security": { + "status": "Security Status", + "secure": "Secure", + "warning": "Warning", + "critical": "Critical", + "protection": "Protection", + "enabled": "Enabled", + "disabled": "Disabled", + "paused": "Paused" + }, + "time": { + "created": "Created", + "lastEdited": "Last Edited", + "seconds": "{{count}} seconds", + "minutes": "{{count}} minutes", + "hours": "{{count}} hours", + "days": "{{count}} days", + "weeks": "{{count}} weeks", + "months": "{{count}} months", + "years": "{{count}} years", + "ago": "ago", + "in": "in" + }, + "account": { + "details": "Account Details", + "yourPackage": "Your Package", + "accessUntil": "Access Until", + "yourSubscription": "Your Subscription", + "nextPaymentDate": "Next Payment Date", + "accessPaidUntil": "Access Paid Until", + "username": "Username", + "password": "Password", + "deviceName": "Device Name", + "accountState": "Account State", + "features": "Features", + "deviceID": "Device ID", + "loggedInSince": "Logged in Since", + "login": { + "title": "Safing Account Login", + "unlockFeatures": "Unlock powerful features.", + "forcedLogout": "You have been logged out by the account server.", + "checkAccount": "Please check your account.", + "yourAccount": "your account", + "signIn": "SIGN IN", + "signUpSubscribe": "Sign Up and Subscribe" + } + }, + "errors": { + "connectionFailed": "Connection failed", + "loadingFailed": "Failed to load data", + "saveFailed": "Failed to save", + "unknownError": "An unknown error occurred", + "networkError": "Network error", + "serverError": "Server error", + "notFound": "Not found", + "unauthorized": "Unauthorized", + "forbidden": "Forbidden" + }, + "service": { + "connecting": "Connecting to System Service ...", + "notRunning": "Portmaster System Service is not running:", + "startNow": "Start Now", + "notFound": "Failed to find Portmaster System Service.", + "reinstall": "Please reinstall the application.", + "getHelp": "Get Help", + "unsupportedManager": "Your System Service Manager is not supported. Please make sure Portmaster is running.", + "unknownError": "Unknown error:" + }, + "intro": { + "step1": { + "title": "Portmaster Protects Your Privacy", + "description": "Portmaster enhances your privacy with powerful defaults - no configuration needed! Of course you can customize everything to your specific needs.", + "quickSetup": "Quick Setup" + }, + "step2": { + "title": "Trackers Are Blocked System-Wide", + "description": "Portmaster automatically blocks ads, trackers and malware hosts on your whole device. Portmaster knows what to block through trusted domain lists, which are also used by Ad-Blockers in browsers, etc. You can always customize this in the settings." + }, + "step3": { + "title": "Secure DNS for All Connections", + "description": "Portmaster automatically encrypts all your DNS queries to safeguard them from prying eyes. Portmaster sets a default provider, but you can always switch to a custom DNS-over-TLS provider in the global settings." + }, + "step4": { + "title": "Learn More as You Explore", + "description": "Portmaster has a lot more to offer. When you decide to dive deeper you can always click on an information icon to learn more about a certain feature. Look out for those!", + "clickMe": "Click Me!" + } + }, + "dialogs": { + "mergeProfileDescription": "Please select the primary profile. All other selected profiles will be merged into the primary profile by copying metadata, fingerprints and icons into a new profile. Only the settings of the primary profile will be kept.", + "primaryProfile": "Primary Profile", + "newProfileName": "Name for the new Profile", + "newProfileNamePlaceholder": "New Profile Name", + "merge": "Merge", + "editAppProfile": "Edit App Profile", + "createNewAppProfile": "Create New App Profile", + "general": "General", + "processMatching": "Process Matching", + "configureBasicInfo": "Configure basic profile information like the profile name, its description and optionally the profile icon.", + "chooseIcon": "Choose Icon", + "resetIcon": "Reset Icon", + "iconRequirements": "The icon must be smaller than 10kB and its dimensions must not exceed 512x512 px. Only JPG and PNG files are supported.", + "fingerprintDescription": "This profile will be applied to processes that match one of the following fingerprints:", + "noFingerprints": "No fingerprints configured. Please press 'Add New' to get started.", + "addNew": "Add New", + "save": "Save", + "cancel": "Cancel", + "confirmDelete": "Are you sure you want to delete this?", + "import": "Import", + "export": "Export", + "importSettings": "Import Settings", + "exportSettings": "Export Settings", + "selectFile": "Select File", + "downloadFile": "Download File", + "closeUI": "Close User Interface", + "closeUIMessage": "Closing the User Interface does not shut down the Portmaster. You can shut down the Portmaster in the Settings or the Tray Notifier.", + "neverShowAgain": "Never Show Again", + "closeUIButton": "Close UI", + "progress": { + "status": "Status", + "uploadingDebug": "Uploading debug data ...", + "creatingIssue": "Creating GitHub issue ...", + "creatingTicket": "Creating private support ticket ...", + "ticketPrepared": "Ticket prepared successfully", + "ticketCreated": "Ticket created successfully!", + "useButtonToOpen": "Use the following button to open the pre-filled GitHub issue form:", + "createIssue": "Create Issue", + "issueCreatedSuccess": "We successfully created the issue on GitHub for you.", + "useFollowingLink": "Use the following link to check for updates:", + "willContactYou": "We will contact you as soon as possible.", + "failedToCreate": "Failed to create Support Ticket", + "errorOccurred": "An error occurred while creating your support ticket:", + "openIssue": "Open Issue", + "copyUrl": "Copy URL" + }, + "exportDialog": { + "settingsExport": "Settings Export", + "profileExport": "Profile Export", + "copyToClipboard": "Copy To Clipboard" + }, + "importDialog": { + "importSettings": "Import Settings", + "importProfile": "Import Profile", + "pasteContent": "Please paste the \"Export Content\" or use \"Choose File\" to select one from your hard disk.", + "configuration": "Configuration", + "resetSettings": "Reset all settings to default before importing", + "allowUnknown": "Allow unknown settings", + "allowReplace": "Allow replacing an existing profile", + "autoRestart": "Automatically restart Portmaster after a successful import", + "warningUnknown": "This export contains unknown settings. To import it, you must enable \"Allow unknown settings\".", + "warningOverwriteSettings": "This export will overwrite settings that have been changed by you.", + "warningOverwriteProfile": "This export will overwrite an existing profile.", + "deleteMergedProfiles": "And deletes {{count}} previously merged profile(s)", + "warningRestart": "This export will require a restart of the Portmaster to take effect.", + "chooseFile": "Choose File" + }, + "processDetails": { + "title": "Process Details", + "general": "General", + "name": "Name", + "user": "User", + "processId": "Process ID", + "processGroupId": "Process Group ID", + "parentProcessId": "Parent Process ID", + "path": "Path", + "createProfileForPath": "Create a new profile for all processes with this path", + "executableName": "Executable Name", + "commandLine": "Command Line", + "createProfileForCmdline": "Create a new profile for all processes with this command line", + "tags": "Tags", + "noTags": "This process does not have any tags.", + "environment": "Environment", + "noEnvVars": "This process does not have any environment variables.", + "createProfileForEnv": "Create a new profile for all processes with this environment variable" + }, + "edit": { + "idPlaceholder": "A unique identifier for profile. Leave empty to generate a random one.", + "name": "Name", + "namePlaceholder": "A name for the profile", + "description": "Description", + "descriptionPlaceholder": "An optional description of the profile", + "icon": "Icon", + "tag": "Tag", + "commandLine": "Command Line", + "environment": "Environment", + "path": "Path", + "key": "Key", + "equals": "Equals", + "prefix": "Prefix", + "regex": "Regex", + "value": "Value", + "copySettings": "Copy Settings", + "selectProfileToCopy": "Select a Profile to copy settings from:", + "searchProfiles": "Search Profiles", + "add": "Add", + "copySettingsInfo": "Settings will be copied from all specified profiles in order with settings from higher profiles taking precedence. Existing settings may be overwritten." + } + }, + "config": { + "core/apiKeys": { + "name": "API Keys", + "description": "Define API keys for privileged access" + }, + "core/automaticIntelUpdates": { + "name": "Auto Intel Updates", + "description": "Automatically download intelligence data" + }, + "core/automaticUpdates": { + "name": "Auto Software Updates", + "description": "Automatically download software updates" + }, + "core/devMode": { + "name": "Development Mode", + "description": "Lift security restrictions for debugging" + }, + "core/enableProcessDetection": { + "name": "Process Detection", + "description": "Attribute network traffic to processes" + }, + "core/expertiseLevel": { + "name": "UI Mode", + "description": "Amount of settings shown" + }, + "core/listenAddress": { + "name": "API Address", + "description": "IP and port for internal API" + }, + "core/locale": { + "name": "Time Format", + "description": "Time and date format" + }, + "core/log/level": { + "name": "Log Level", + "description": "Logging detail level" + }, + "core/metrics/comment": { + "name": "Metrics Comment", + "description": "Metrics comment label" + }, + "core/metrics/instance": { + "name": "Metrics Instance", + "description": "Prometheus instance name" + }, + "core/metrics/push": { + "name": "Push Metrics", + "description": "URL for metrics push" + }, + "core/networkService": { + "name": "Network Service", + "description": "Use as network service" + }, + "core/releaseChannel": { + "name": "Release Channel", + "description": "Stable for stability, Beta for new features" + }, + "core/releaseLevel": { + "name": "Feature Stability", + "description": "Experiment with unstable features" + }, + "core/useSystemNotifications": { + "name": "Notifications", + "description": "Send desktop notifications" + }, + "dns/dontResolveSpecialDomains": { + "name": "Block Special TLDs", + "description": "Block .onion, .i2p, etc." + }, + "dns/listenAddress": { + "name": "DNS Server Address", + "description": "IP and port for internal DNS" + }, + "dns/nameserverRetryRate": { + "name": "DNS Server Retry", + "description": "Retry interval for failing DNS" + }, + "dns/nameservers": { + "name": "DNS Servers", + "description": "Servers for DNS resolution" + }, + "dns/noAssignedNameservers": { + "name": "Ignore System DNS", + "description": "Ignore system DNS servers" + }, + "dns/noInsecureProtocols": { + "name": "Secure DNS Only", + "description": "Don't use plain DNS" + }, + "dns/noMulticastDNS": { + "name": "Ignore Multicast DNS", + "description": "Don't use mDNS" + }, + "dns/useStaleCache": { + "name": "Use DNS Cache", + "description": "Use cache even if expired" + }, + "filter/askTimeout": { + "name": "Prompt Timeout", + "description": "Time to wait for prompt reply" + }, + "filter/askWithSystemNotifications": { + "name": "Desktop Prompts", + "description": "Send prompts to desktop" + }, + "filter/blockInbound": { + "name": "Block Incoming", + "description": "Block incoming connections" + }, + "filter/blockInternet": { + "name": "Block Internet", + "description": "Block internet access" + }, + "filter/blockLAN": { + "name": "Block LAN", + "description": "Block local network" + }, + "filter/blockLocal": { + "name": "Block Localhost", + "description": "Block internal connections" + }, + "filter/blockP2P": { + "name": "Block P2P", + "description": "Block direct connections" + }, + "filter/customListFile": { + "name": "Custom Filter List", + "description": "Path to filter file" + }, + "filter/defaultAction": { + "name": "Default Action", + "description": "Action when no rule matches" + }, + "filter/disableAutoPermit": { + "name": "Disable Auto Allow", + "description": "Disable automatic allowing" + }, + "filter/dnsQueryInterception": { + "name": "Seamless DNS", + "description": "Intercept DNS queries" + }, + "filter/domainHeuristics": { + "name": "Domain Heuristics", + "description": "Block suspicious domains" + }, + "filter/enable": { + "name": "Privacy Filter", + "description": "Enable the privacy filter" + }, + "filter/endpoints": { + "name": "Outgoing Rules", + "description": "Rules for outgoing connections" + }, + "filter/includeCNAMEs": { + "name": "Block Domain Aliases", + "description": "Block CNAME aliases" + }, + "filter/includeSubdomains": { + "name": "Block Subdomains", + "description": "Block subdomains from lists" + }, + "filter/lists": { + "name": "Filter Lists", + "description": "Enable filter lists" + }, + "filter/permanentVerdicts": { + "name": "Permanent Verdicts", + "description": "Improve performance" + }, + "filter/preventBypassing": { + "name": "Prevent DNS Bypass", + "description": "Prevent DNS bypass" + }, + "filter/removeBlockedDNS": { + "name": "Reject Blocked IPs", + "description": "Reject blocked IP addresses" + }, + "filter/removeOutOfScopeDNS": { + "name": "Split-View DNS", + "description": "Split global/private DNS" + }, + "filter/serviceEndpoints": { + "name": "Incoming Rules", + "description": "Rules for incoming connections" + }, + "history/enable": { + "name": "Network History", + "description": "Save connection history" + }, + "history/keep": { + "name": "Keep History", + "description": "Days to keep history" + }, + "spn/dnsExitPolicy": { + "name": "DNS Exit Rules", + "description": "Countries for DNS exit nodes" + }, + "spn/enable": { + "name": "SPN Module", + "description": "Enable SPN module" + }, + "spn/exitHubPolicy": { + "name": "Exit Rules", + "description": "Countries for exit nodes" + }, + "spn/homePolicy": { + "name": "Home Node Rules", + "description": "Countries for home node" + }, + "spn/routingAlgorithm": { + "name": "SPN Algorithm", + "description": "Balance speed and privacy" + }, + "spn/specialAccessCode": { + "name": "SPN Access Code", + "description": "Code for test access" + }, + "spn/transitHubPolicy": { + "name": "Transit Nodes", + "description": "Countries for transit nodes" + }, + "spn/trustNodes": { + "name": "Trust Nodes", + "description": "Community nodes to trust" + }, + "spn/usagePolicy": { + "name": "SPN Rules", + "description": "Sites to route through SPN" + }, + "spn/use": { + "name": "Use SPN", + "description": "Protect traffic through SPN" + }, + "spn/useCommunityNodes": { + "name": "Community Nodes", + "description": "Use non-Safing nodes" + } + } +} diff --git a/assets/data/i18n/ru.json b/assets/data/i18n/ru.json new file mode 100644 index 000000000..8c0c8dcb5 --- /dev/null +++ b/assets/data/i18n/ru.json @@ -0,0 +1,937 @@ +{ + "common": { + "save": "Сохранить", + "cancel": "Отмена", + "close": "Закрыть", + "delete": "Удалить", + "edit": "Редактировать", + "add": "Добавить", + "remove": "Убрать", + "confirm": "Подтвердить", + "yes": "Да", + "no": "Нет", + "ok": "ОК", + "loading": "Загрузка...", + "search": "Поиск", + "filter": "Фильтр", + "clear": "Очистить", + "reset": "Сбросить", + "apply": "Применить", + "back": "Назад", + "next": "Далее", + "previous": "Предыдущий", + "enabled": "Включено", + "enable": "Включить", + "disabled": "Отключено", + "active": "Активно", + "inactive": "Неактивно", + "all": "Все", + "none": "Нет", + "other": "Другое", + "unknown": "Неизвестно", + "never": "никогда", + "now": "сейчас", + "today": "сегодня", + "yesterday": "вчера", + "error": "Ошибка", + "warning": "Предупреждение", + "info": "Информация", + "success": "Успех", + "import": "Импортировать", + "customize": "Настроить", + "custom": "Пользовательский", + "tip": "Совет", + "status": "Статус", + "apps": "Приложения", + "copy": "Копировать", + "title": "Заголовок", + "faq": "ЧаВо", + "where": "где", + "by": "от", + "beta": "Бета", + "experimental": "Экспериментальный", + "advanced": "Расширенный", + "developer": "Разработчик", + "home": "Домашний", + "exit": "Выходной", + "noLocation": "Местоположение неизвестно", + "community": "Сообщество", + "prompting": "Запрос", + "destination": "Назначение", + "interferingSettings": "Следующие включённые настройки могут влиять на работу", + "groupBy": "Группировать по", + "sort": "Сортировка", + "automatic": "Автоматически", + "getPro": "Получить Pro", + "yourDevice": "Ваше устройство", + "asOrgNotInDB": "Организация AS отсутствует в БД", + "logout": "Выйти", + "refresh": "Обновить", + "reload": "Перезагрузить", + "clearAll": "Очистить всё", + "openAccountPage": "Открыть страницу аккаунта", + "saved": "Сохранено", + "restartRequired": "Требуется перезапуск", + "reloadRequired": "Требуется перезагрузка", + "invalidValue": "Недопустимое значение", + "lastReload": "Последняя загрузка", + "showMore": "Показать больше идентичностей", + "showLess": "Показать меньше идентичностей", + "more": "Ещё", + "comingSoon": "скоро будет", + "availableIn": "Доступно в", + "upgrade": "Улучшить" + }, + "search": { + "placeholder": "Поиск соединений, нажмите ENTER для применения", + "fullTextSearch": "Полнотекстовый поиск", + "filterBy": "Фильтр по", + "searchFaqs": "Поиск по ЧаВо", + "summarizeReport": "Опишите вашу проблему" + }, + "nav": { + "dashboard": "Панель управления", + "networkActivity": "Сетевая активность", + "appsAndProfiles": "Приложения и профили", + "spn": "Сеть конфиденциальности Safing", + "globalSettings": "Глобальные настройки", + "getHelp": "Получить помощь", + "versionAndTools": "Версия и инструменты", + "version": "Версия", + "releaseChannel": "Канал обновлений", + "shutdown": "Выключить", + "restart": "Перезапустить", + "pause": "Приостановить", + "resume": "Возобновить", + "pauseSpn": "Приостановить SPN", + "pauseAll": "Приостановить всю защиту", + "notifications": "Уведомления", + "language": "Язык", + "checkUpdates": "Проверить обновления", + "viewChangelog": "Просмотр изменений", + "reloadUI": "Перезагрузить интерфейс", + "showIntro": "Показать вступление", + "reinitSPN": "Переинициализировать SPN", + "logoutCompletely": "Полный выход", + "resetBroadcast": "Сбросить состояние рассылки", + "clearDNS": "Очистить кэш DNS", + "openDataDir": "Открыть папку данных", + "copyDebugInfo": "Копировать отладочную информацию", + "cleanupHistory": "Очистить историю сети", + "pauseSpn5min": "Приостановить SPN на 5 минут", + "pauseSpn15min": "Приостановить SPN на 15 минут", + "pauseSpn1hour": "Приостановить SPN на 1 час", + "pause5min": "Приостановить на 5 минут", + "pause15min": "Приостановить на 15 минут", + "pause1hour": "Приостановить на 1 час", + "resumeNow": "Возобновить сейчас", + "pauseAndResume": "Пауза и возобновление", + "shutdownAndRestart": "Выключение и перезапуск" + }, + "dashboard": { + "title": "Панель управления", + "welcomeBack": "С возвращением", + "welcomeBackUser": "С возвращением, {{username}}!", + "currentPlan": "Ваш текущий план", + "portmasterFree": "Portmaster Бесплатный", + "endsIn": "заканчивается через", + "endsNever": "никогда", + "autoRenewsIn": "автопродление через", + "accountDetails": "Данные аккаунта", + "loginSubscribe": "Войти / Подписаться", + "features": "Функции", + "recentActivity": "Последняя активность", + "connectionsBlocked": "Заблокировано соединений", + "activeConnections": "Активных соединений", + "activeApps": "Активных приложений", + "dataReceived": "Получено данных", + "dataSent": "Отправлено данных", + "spnIdentities": "Идентификаторы SPN", + "availableInPlus": "Доступно в Portmaster Plus", + "availableInPro": "Доступно в Portmaster Pro", + "activeBlockedConnections": "Активные/Заблокированные соединения", + "connectionsTunneledSpn": "Соединения через SPN", + "recentConnectionsPerCountry": "Последние соединения по странам", + "recentlyBlockedApps": "Недавно заблокированные приложения", + "noBlockedApps": "За последние 10 минут приложения не блокировались.", + "recentConnectionCountries": "Страны последних соединений", + "recentTopConsumers": "Недавние крупные потребители", + "recentBandwidthUsage": "Недавнее использование трафика", + "news": "Новости", + "newsUnavailable": "Новости доступны только при включённых обновлениях данных", + "newsLoading": "Секунду, загружаем последние новости..." + }, + "monitor": { + "title": "Монитор сети", + "networkActivity": "Сетевая активность", + "connections": "соединений", + "historyAvailable": "Данные истории сети доступны с", + "noHistoryData": "Данные истории сети недоступны.", + "searchHint": "Используйте строку поиска и фильтры для поиска и фильтрации сетевого трафика за последние 10 минут. При включённой истории можно искать по всем данным.", + "blocked": "Заблокировано", + "allowed": "Разрешено", + "failed": "Ошибка", + "tunneled": "Туннелировано", + "encrypted": "Зашифровано", + "internal": "Внутреннее", + "details": "Подробности", + "process": "Процесс", + "domain": "Домен", + "ip": "IP-адрес", + "port": "Порт", + "protocol": "Протокол", + "country": "Страна", + "started": "Начато", + "ended": "Завершено", + "duration": "Длительность", + "bytesIn": "Получено байт", + "bytesOut": "Отправлено байт", + "verdict": "Вердикт", + "reason": "Причина", + "profile": "Профиль", + "scope": "Область", + "exitNode": "Выходной узел", + "entryNode": "Входной узел", + "route": "Маршрут", + "dataUsage": "Использование данных", + "copyJSON": "Копировать JSON", + "internetPeerToPeer": "Интернет (P2P)", + "internetMulticast": "Интернет (мультикаст)", + "deviceLocal": "Локальное устройство", + "lanPeerToPeer": "LAN (P2P)", + "lanMulticast": "LAN (мультикаст)", + "spnTunnel": "SPN туннель", + "results": "Результатов", + "ofTotalConnections": "из {{count}} всего соединений", + "useAsFilter": "Использовать как фильтр", + "copyQueryToClipboard": "Копировать запрос в буфер", + "internalConnectionsDevMode": "Внутренние соединения отображаются только в режиме разработчика", + "app": "Приложение", + "global": "Глобальный", + "proTipShiftClick": "Используйте
Shift + Click
чтобы добавить атрибуты соединения в текущий фильтр.", + "proTipShiftHighlight": "Удерживайте
Shift
чтобы подсветить атрибуты, которые можно использовать в фильтре.", + "proTipCtrlCopy": "Удерживайте
CTRL
и кликните по атрибутам, чтобы скопировать их в буфер обмена." + }, + "apps": { + "title": "Приложения", + "overview": "Обзор", + "settings": "Настройки", + "history": "История", + "active": "Активные", + "recentlyUsed": "Недавно использованные", + "recentlyEdited": "Недавно изменённые", + "other": "Другие", + "allApps": "Все приложения", + "noApps": "Приложения не найдены", + "noAppsMatch": "Приложения не соответствуют поисковому запросу.", + "blockConnections": "Блокировать соединения", + "useSPN": "Использовать SPN", + "keepHistory": "Сохранять историю", + "viewActive": "Показать активные", + "viewAll": "Показать все", + "quickSettings": "Быстрые настройки", + "appSettings": "Настройки приложения", + "globalDefault": "Глобальные по умолчанию", + "appSpecific": "Для приложения", + "createProfile": "Создать профиль", + "importProfile": "Импортировать профиль", + "mergeOrDelete": "Объединить или удалить профили", + "manage": "Управление", + "mergeProfiles": "Объединить профили", + "deleteProfiles": "Удалить профили", + "selected": "выбрано", + "settingsEdited": "Настройки изменены", + "connections": "Соединения", + "dataUsage": "Использование данных", + "countries": "Страны", + "blocked": "Заблокировано", + "received": "Получено", + "sent": "Отправлено", + "availableInPlus": "Доступно в Plus", + "more": "Ещё", + "editAppProfile": "Редактировать профиль", + "exportAppProfile": "Экспортировать профиль", + "deleteAppProfile": "Удалить профиль", + "searchSettings": "Поиск настроек", + "getHelp": "Помощь", + "sortBy": "Сортировать по", + "appSpecificSettings": "Настройки приложения", + "usingGlobalSettings": "полностью использует глобальные настройки.", + "startCreatingExceptions": "Начните создавать исключения сейчас.", + "editSettings": "Редактировать настройки", + "details": "Подробности", + "name": "Имя", + "path": "Путь", + "binary": "Бинарный файл", + "activeConnections": "Активные соединения", + "networkHistory": "История сети", + "asOf": "С", + "removeConnections": "Удалить все", + "none": "Нет", + "networkHistoryTooltip": "Функция истории сети доступна в Portmaster Plus", + "internal": "Внутренний", + "source": "Источник", + "revision": "Ревизия", + "layers": "Слои", + "description": "Описание", + "fingerprints": "Отпечатки", + "fingerprintsDescription": "Этот профиль будет применён к процессам, которые соответствуют одному из следующих отпечатков:", + "deleteProfile": "Удалить профиль", + "deleteProfileDescription": "Вы можете полностью удалить этот профиль вместе со всеми настройками. Профиль будет автоматически создан заново с настройками по умолчанию, как только приложение начнёт использовать сеть.", + "debugging": "Отладка", + "debuggingDescription": "При сообщении о проблемах с этим приложением обязательно включите следующую отладочную информацию:", + "copyDebugInfo": "Скопировать отладочную информацию", + "insights": "Аналитика", + "delete": "Удалить" + }, + "settings": { + "title": "Глобальные настройки", + "general": "Общие", + "privacy": "Конфиденциальность", + "security": "Безопасность", + "network": "Сеть", + "dns": "DNS", + "connections": "Соединения", + "filter": "Фильтр", + "spn": "SPN", + "advanced": "Дополнительно", + "resetToDefault": "Сбросить по умолчанию", + "importSettings": "Импорт настроек", + "exportSettings": "Экспорт настроек", + "searchSettings": "Поиск настроек...", + "openSettings": "Открыть настройки", + "useGlobalSetting": "Использовать глобальную настройку", + "cancelExport": "Отмена экспорта", + "quickSettings": "Быстрые настройки", + "globalSettings": "Глобальные настройки", + "stackedOnGlobal": "Эта настройка дополняет следующую", + "globalSetting": "глобальную настройку", + "inheritedFromGlobal": "Унаследовано от", + "appSpecificConfig": "Настройка для приложения" + }, + "rules": { + "addRule": "Добавить правило", + "removeRules": "Удалить правила", + "noEntries": "Нет записей", + "applied": "применено", + "fromGlobalSettings": "из глобальных настроек", + "fromAppSettings": "из настроек приложения" + }, + "spn": { + "title": "Сеть конфиденциальности Safing", + "status": "Статус", + "connected": "Подключено", + "disconnected": "Отключено", + "connecting": "Подключение", + "disabled": "Выключено", + "enableToStart": "Включите SPN для использования.", + "exit": "Выход SPN", + "increaseProtection": "Повысить защиту конфиденциальности", + "youreProtected": "Вы защищены", + "connectingToSPN": "Подключение к SPN ...", + "map": "Карта", + "nodes": "Узлы", + "routes": "Маршруты", + "identities": "Идентификаторы", + "home": "Домой", + "exitNode": "Выходной узел", + "entryNode": "Входной узел", + "transit": "Транзит", + "load": "Нагрузка", + "latency": "Задержка", + "countries": "Страны", + "loadingData": "Загрузка данных, пожалуйста подождите...", + "login": "Войти", + "enableSPN": "Включить SPN", + "pricing": "Тарифы", + "proTip": "Совет", + "statistics": "Статистика", + "totalNodes": "Всего узлов", + "bySafing": "от Safing", + "byCommunity": "от сообщества", + "safingNodes": "Узлы Safing", + "communityNodes": "Узлы сообщества", + "usedAsTransit": "используется как транзит", + "usedAsExit": "используется как выход", + "clickCountryForDetails": "Нажмите на страну для подробностей", + "exitNodes": "Выходные узлы", + "nodesInUse": "Используемые узлы", + "appsExitingInCountry": "Следующие приложения имеют соединения, которые маршрутизируются через SPN и используют выходной узел в {{countryName}} ({{countryCode}}):", + "connections": "соединений", + "name": "Имя", + "operator": "Оператор", + "usedAs": "Используется как", + "capacity": "Ёмкость", + "homeNode": "Домашний узел", + "transitNode": "Транзитный узел", + "showDetails": "Показать детали", + "showExitConnections": "Показать выходные соединения", + "copyNodeID": "Скопировать ID узла", + "runBy": "Управляется", + "nodeRunBy": "Этот SPN узел управляется", + "verifiedOperator": "Верифицированный оператор: {{owner}}", + "community": "Сообщество", + "nodeOffline": "Узел не в сети", + "nodeHasIssues": "Узел имеет проблемы", + "details": "Детали", + "verifiedOwner": "Верифицированный владелец", + "firstSeen": "Первое обнаружение", + "states": "Состояния", + "sessionActive": "Сессия активна", + "hopDistance": "Количество переходов", + "hops": "ПЕРЕХОДЫ", + "exitConnections": "Выходные соединения", + "showExitInMonitor": "Показать выходные соединения в мониторе.", + "route": "Маршрут", + "connectedNodes": "Подключённые узлы", + "homeNodeHelp": "Этот узел не знает назначения ваших соединений, но может знать, где вы находитесь", + "exitNodeHelp": "Этот узел не знает, кто вы, но знает назначение соединений, для которых он используется как выходной узел", + "transitNodeHelp": "Этот узел не знает, кто вы и куда вы подключаетесь", + "failsafeBlocking": "Аварийная блокировка включена", + "failedToConnect": "Не удалось подключиться к SPN", + "homeEntryNode": "Домашний (входной) узел SPN", + "connectedTo": "Подключено к", + "uplinkEncrypted": "Исходящий канал всегда зашифрован", + "builtWithTransport": "Использует транспорт/маскировку", + "tips": { + "ctrlClick": "Удерживайте
CTRL
и кликните на узел на карте, чтобы сразу открыть диалог с деталями узла.", + "shiftClick": "Удерживайте
SHIFT
, чтобы открыть несколько оверлеев узлов при клике на иконку узла.", + "moveOverlay": "Чтобы оставить оверлей узла открытым, переместите его с помощью иконки перемещения. Дважды кликните, чтобы вернуть оверлей в исходное положение на карте.", + "countryClick": "Кликните на страну, чтобы получить информацию обо всех узлах в этой стране и список приложений, использующих узлы этой страны как идентификатор." + }, + "carousel": { + "multipleIdentities": "Множество идентичностей для каждого приложения", + "multipleIdentitiesDesc": "Автоматически получайте множество идентичностей (IP-адресов). SPN вычисляет индивидуальный путь для каждого соединения через сеть конфиденциальности. Распределяйте соединения по всему миру без усилий.", + "accessRegional": "Легкий доступ к региональному контенту", + "accessRegionalDesc": "Сайт заблокирован или ограничен в вашей стране? Поскольку SPN выводит соединение рядом с целевым сервером, контент автоматически разблокируется. SPN лучше всего покрывает Европу и Северную Америку.", + "adjustPrivacy": "Легко настраивайте", + "yourPrivacy": "Вашу конфиденциальность", + "adjustPrivacyDesc": "SPN просто работает и делает всю тяжёлую работу за вас. Но конечно, вы можете легко настроить параметры под свои нужды: исключить определённые приложения и домены из SPN, запретить выход в определённых странах и многое другое...", + "builtFromScratch": "Создан с нуля для", + "builtFromScratchDesc": "SPN создан с нуля. Конфиденциальность встроена в его основу. Вдохновлённый Tor, он использует луковую маршрутизацию и современное шифрование. Полностью открытый исходный код позволяет проверить все наши заявления.", + "byeByeVpns": "Прощай, VPN", + "byeByeVpnsDesc": "Технология VPN создавалась НЕ для конфиденциальности пользователей, а для безопасности компаний. Поэтому вы можете только доверять политике VPN-провайдера — и многих уже ловили на злоупотреблении данными пользователей. Честно говоря, лучше просто перестать платить за устаревшие технологии.", + "mostVpns": "Большинство VPN", + "readComparisonBlog": "Читать сравнение", + "multipleIdentitiesSimultaneous": "Множество идентичностей (одновременно)", + "individualAppsSettings": "Индивидуальные настройки приложений", + "easySetup": "Простая настройка", + "browserOnly": "Только браузер", + "availability": "Доступность", + "openSource": "Открытый исходный код", + "builtForPrivacy": "Создан для конфиденциальности" + }, + "networkStatus": { + "title": "Состояние сети", + "loading": "Загрузка состояния сети ...", + "openOnGithub": "Открыть на Github", + "opened": "открыт", + "closed": "закрыт", + "by": "от" + } + }, + "support": { + "title": "Поддержка", + "getHelp": "Получить помощь", + "reportBug": "Сообщить об ошибке", + "documentation": "Документация", + "community": "Сообщество", + "email": "Email поддержка", + "github": "GitHub Issues", + "discord": "Discord", + "includedDebugInfo": "Включённая отладочная информация", + "ticket": "Тикет поддержки", + "submitTicket": "Отправить тикет", + "debugInfo": "Отладочная информация", + "systemInfo": "Системная информация", + "logs": "Логи", + "copyToClipboard": "Копировать в буфер", + "downloadLogs": "Скачать логи", + "openOnGithub": "Открыть на GitHub", + "createOnGithub": "Создать на GitHub", + "sendPrivateTicket": "Отправить приватный тикет", + "relatedIssues": "Связанные вопросы", + "publicIssuesRelated": "Публичные вопросы, связанные с вашей темой:", + "noRelatedIssuesFound": "Связанных вопросов не найдено.", + "opened": "открыт", + "closed": "закрыт", + "debugInfoDescription": "Следующая отладочная информация будет отправлена вместе с вашим отчётом. Пожалуйста, проверьте её и удалите потенциально конфиденциальные данные. Отладочная информация сохраняется на pastebin-сервере Safing и доступна по созданной ссылке. Данные автоматически удаляются через один месяц.", + "portmasterVersion": "Версия Portmaster", + "builtOn": "собрана" + }, + "notifications": { + "title": "Уведомления", + "notification": "Уведомление", + "broadcastNotification": "Широковещательное уведомление", + "noNotifications": "Нет уведомлений", + "markAllRead": "Отметить все как прочитанные", + "clearAll": "Очистить все", + "new": "Новые", + "read": "Прочитанные" + }, + "prompts": { + "title": "Запросы соединений", + "noPrompts": "Нет запросов", + "allow": "Разрешить", + "block": "Заблокировать", + "allowOnce": "Разрешить один раз", + "blockOnce": "Заблокировать один раз", + "allowAlways": "Всегда разрешать", + "blockAlways": "Всегда блокировать", + "remember": "Запомнить выбор", + "perConnection": "Для каждого соединения", + "allowAll": "Разрешить всё", + "blockAll": "Заблокировать всё", + "defaultAction": "Действие по умолчанию", + "allowApp": "Разрешить приложение", + "blockApp": "Заблокировать приложение", + "changeDefault": "Изменить по умолчанию", + "more": "ещё", + "showLess": "Показать меньше" + }, + "network": { + "rating": "Рейтинг сети", + "trusted": "Доверенная", + "untrusted": "Недоверенная", + "danger": "Опасная", + "home": "Домашняя", + "public": "Публичная", + "unknown": "Неизвестная" + }, + "security": { + "status": "Статус безопасности", + "secure": "Защищено", + "warning": "Предупреждение", + "critical": "Критично", + "protection": "Защита", + "enabled": "Включена", + "disabled": "Отключена", + "paused": "Приостановлена" + }, + "time": { + "created": "Создано", + "lastEdited": "Последнее изменение", + "seconds": "{{count}} секунд", + "minutes": "{{count}} минут", + "hours": "{{count}} часов", + "days": "{{count}} дней", + "weeks": "{{count}} недель", + "months": "{{count}} месяцев", + "years": "{{count}} лет", + "ago": "назад", + "in": "через" + }, + "account": { + "details": "Детали аккаунта", + "yourPackage": "Ваш пакет", + "accessUntil": "Доступ до", + "yourSubscription": "Ваша подписка", + "nextPaymentDate": "Дата следующего платежа", + "accessPaidUntil": "Доступ оплачен до", + "username": "Имя пользователя", + "password": "Пароль", + "deviceName": "Имя устройства", + "accountState": "Состояние аккаунта", + "features": "Функции", + "deviceID": "ID устройства", + "loggedInSince": "Авторизован с", + "login": { + "title": "Вход в аккаунт Safing", + "unlockFeatures": "Разблокируйте мощные функции.", + "forcedLogout": "Вы были отключены сервером аккаунтов.", + "checkAccount": "Пожалуйста, проверьте ваш аккаунт.", + "yourAccount": "ваш аккаунт", + "signIn": "ВОЙТИ", + "signUpSubscribe": "Зарегистрироваться и подписаться" + } + }, + "errors": { + "connectionFailed": "Ошибка соединения", + "loadingFailed": "Не удалось загрузить данные", + "saveFailed": "Не удалось сохранить", + "unknownError": "Произошла неизвестная ошибка", + "networkError": "Сетевая ошибка", + "serverError": "Ошибка сервера", + "notFound": "Не найдено", + "unauthorized": "Не авторизован", + "forbidden": "Доступ запрещён" + }, + "service": { + "connecting": "Подключение к системной службе ...", + "notRunning": "Системная служба Portmaster не запущена:", + "startNow": "Запустить", + "notFound": "Не удалось найти системную службу Portmaster.", + "reinstall": "Пожалуйста, переустановите приложение.", + "getHelp": "Получить помощь", + "unsupportedManager": "Ваш менеджер системных служб не поддерживается. Убедитесь, что Portmaster запущен.", + "unknownError": "Неизвестная ошибка:" + }, + "intro": { + "step1": { + "title": "Portmaster защищает вашу конфиденциальность", + "description": "Portmaster повышает вашу конфиденциальность с мощными настройками по умолчанию — никакой конфигурации не требуется! Конечно, вы можете настроить всё под свои потребности.", + "quickSetup": "Быстрая настройка" + }, + "step2": { + "title": "Трекеры блокируются по всей системе", + "description": "Portmaster автоматически блокирует рекламу, трекеры и вредоносные хосты на всём вашем устройстве. Portmaster знает, что блокировать, благодаря доверенным спискам доменов, которые также используются блокировщиками рекламы в браузерах. Вы всегда можете настроить это в параметрах." + }, + "step3": { + "title": "Безопасный DNS для всех соединений", + "description": "Portmaster автоматически шифрует все ваши DNS-запросы, чтобы защитить их от посторонних глаз. Portmaster устанавливает провайдера по умолчанию, но вы всегда можете переключиться на пользовательский DNS-over-TLS провайдер в глобальных настройках." + }, + "step4": { + "title": "Узнавайте больше по мере изучения", + "description": "Portmaster предлагает гораздо больше. Когда вы решите углубиться, вы всегда можете нажать на иконку информации, чтобы узнать больше о конкретной функции. Обращайте на них внимание!", + "clickMe": "Нажми на меня!" + } + }, + "dialogs": { + "mergeProfileDescription": "Выберите основной профиль. Все остальные выбранные профили будут объединены с основным путём копирования метаданных, отпечатков и иконок в новый профиль. Только настройки основного профиля будут сохранены.", + "primaryProfile": "Основной профиль", + "newProfileName": "Имя нового профиля", + "newProfileNamePlaceholder": "Имя нового профиля", + "merge": "Объединить", + "editAppProfile": "Редактировать профиль приложения", + "createNewAppProfile": "Создать новый профиль приложения", + "general": "Основные", + "processMatching": "Сопоставление процессов", + "configureBasicInfo": "Настройте основную информацию о профиле: название, описание и, при желании, иконку профиля.", + "chooseIcon": "Выбрать иконку", + "resetIcon": "Сбросить иконку", + "iconRequirements": "Иконка должна быть меньше 10КБ, а её размеры не должны превышать 512x512 пикселей. Поддерживаются только файлы JPG и PNG.", + "fingerprintDescription": "Этот профиль будет применён к процессам, которые соответствуют одному из следующих отпечатков:", + "noFingerprints": "Отпечатки не настроены. Нажмите 'Добавить новый' для начала.", + "addNew": "Добавить новый", + "save": "Сохранить", + "cancel": "Отмена", + "confirmDelete": "Вы уверены, что хотите удалить это?", + "import": "Импорт", + "export": "Экспорт", + "importSettings": "Импорт настроек", + "exportSettings": "Экспорт настроек", + "selectFile": "Выбрать файл", + "downloadFile": "Скачать файл", + "closeUI": "Закрыть интерфейс", + "closeUIMessage": "Закрытие интерфейса не останавливает Portmaster. Вы можете остановить Portmaster в настройках или через иконку в трее.", + "neverShowAgain": "Больше не показывать", + "closeUIButton": "Закрыть UI", + "progress": { + "status": "Статус", + "uploadingDebug": "Загрузка отладочных данных ...", + "creatingIssue": "Создание GitHub-issue ...", + "creatingTicket": "Создание приватного тикета поддержки ...", + "ticketPrepared": "Тикет успешно подготовлен", + "ticketCreated": "Тикет успешно создан!", + "useButtonToOpen": "Используйте кнопку ниже для открытия предзаполненной формы GitHub issue:", + "createIssue": "Создать Issue", + "issueCreatedSuccess": "Мы успешно создали issue на GitHub для вас.", + "useFollowingLink": "Используйте следующую ссылку для отслеживания обновлений:", + "willContactYou": "Мы свяжемся с вами как можно скорее.", + "failedToCreate": "Не удалось создать тикет поддержки", + "errorOccurred": "Произошла ошибка при создании тикета поддержки:", + "openIssue": "Открыть Issue", + "copyUrl": "Скопировать URL" + }, + "exportDialog": { + "settingsExport": "Экспорт настроек", + "profileExport": "Экспорт профиля", + "copyToClipboard": "Скопировать в буфер обмена" + }, + "importDialog": { + "importSettings": "Импорт настроек", + "importProfile": "Импорт профиля", + "pasteContent": "Вставьте \"Экспортированный контент\" или нажмите \"Выбрать файл\" для выбора файла с диска.", + "configuration": "Конфигурация", + "resetSettings": "Сбросить все настройки до значений по умолчанию перед импортом", + "allowUnknown": "Разрешить неизвестные настройки", + "allowReplace": "Разрешить замену существующего профиля", + "autoRestart": "Автоматически перезапустить Portmaster после успешного импорта", + "warningUnknown": "Этот экспорт содержит неизвестные настройки. Для импорта необходимо включить \"Разрешить неизвестные настройки\".", + "warningOverwriteSettings": "Этот экспорт перезапишет настройки, которые были изменены вами.", + "warningOverwriteProfile": "Этот экспорт перезапишет существующий профиль.", + "deleteMergedProfiles": "И удаляет {{count}} ранее объединённых профилей", + "warningRestart": "Этот экспорт потребует перезапуска Portmaster для применения изменений.", + "chooseFile": "Выбрать файл" + }, + "processDetails": { + "title": "Детали процесса", + "general": "Основные", + "name": "Имя", + "user": "Пользователь", + "processId": "ID процесса", + "processGroupId": "ID группы процессов", + "parentProcessId": "ID родительского процесса", + "path": "Путь", + "createProfileForPath": "Создать новый профиль для всех процессов с этим путём", + "executableName": "Имя исполняемого файла", + "commandLine": "Командная строка", + "createProfileForCmdline": "Создать новый профиль для всех процессов с этой командной строкой", + "tags": "Теги", + "noTags": "У этого процесса нет тегов.", + "environment": "Окружение", + "noEnvVars": "У этого процесса нет переменных окружения.", + "createProfileForEnv": "Создать новый профиль для всех процессов с этой переменной окружения" + }, + "edit": { + "idPlaceholder": "Уникальный идентификатор профиля. Оставьте пустым для автоматической генерации.", + "name": "Название", + "namePlaceholder": "Название профиля", + "description": "Описание", + "descriptionPlaceholder": "Опциональное описание профиля", + "icon": "Иконка", + "tag": "Тег", + "commandLine": "Командная строка", + "environment": "Окружение", + "path": "Путь", + "key": "Ключ", + "equals": "Равно", + "prefix": "Префикс", + "regex": "Регулярное выражение", + "value": "Значение", + "copySettings": "Копировать настройки", + "selectProfileToCopy": "Выберите профиль для копирования настроек:", + "searchProfiles": "Поиск профилей", + "add": "Добавить", + "copySettingsInfo": "Настройки будут скопированы из всех указанных профилей по порядку, при этом настройки из верхних профилей имеют приоритет. Существующие настройки могут быть перезаписаны." + } + }, + "config": { + "core/apiKeys": { + "name": "API Ключи", + "description": "Определите API ключи для доступа к API" + }, + "core/automaticIntelUpdates": { + "name": "Авто-обновления Данных", + "description": "Автоматически скачивать обновления данных" + }, + "core/automaticUpdates": { + "name": "Авто-обновления ПО", + "description": "Автоматически скачивать обновления ПО" + }, + "core/devMode": { + "name": "Режим Разработчика", + "description": "Снять ограничения безопасности для отладки" + }, + "core/enableProcessDetection": { + "name": "Определение Процессов", + "description": "Привязка сетевого трафика к процессам" + }, + "core/expertiseLevel": { + "name": "Режим Интерфейса", + "description": "Объём отображаемых настроек" + }, + "core/listenAddress": { + "name": "Адрес API", + "description": "IP и порт внутреннего API" + }, + "core/locale": { + "name": "Формат Времени", + "description": "Формат времени и даты" + }, + "core/log/level": { + "name": "Уровень Логирования", + "description": "Уровень детализации журнала" + }, + "core/metrics/comment": { + "name": "Комментарий Метрик", + "description": "Метка комментария метрик" + }, + "core/metrics/instance": { + "name": "Экземпляр Метрик", + "description": "Имя экземпляра prometheus" + }, + "core/metrics/push": { + "name": "Отправка Метрик", + "description": "URL для отправки метрик" + }, + "core/networkService": { + "name": "Сетевой Сервис", + "description": "Использовать как сетевой сервис" + }, + "core/releaseChannel": { + "name": "Канал Релиза", + "description": "Stable для стабильности, Beta для новых функций" + }, + "core/releaseLevel": { + "name": "Стабильность Функций", + "description": "Экспериментировать с нестабильными функциями" + }, + "core/useSystemNotifications": { + "name": "Уведомления", + "description": "Отправлять уведомления на рабочий стол" + }, + "dns/dontResolveSpecialDomains": { + "name": "Блокировать Спец. TLD", + "description": "Блокировать .onion, .i2p и др." + }, + "dns/listenAddress": { + "name": "Адрес DNS Сервера", + "description": "IP и порт внутреннего DNS" + }, + "dns/nameserverRetryRate": { + "name": "Повтор DNS Серверов", + "description": "Интервал повтора к отказавшим DNS" + }, + "dns/nameservers": { + "name": "DNS Серверы", + "description": "Серверы для разрешения DNS" + }, + "dns/noAssignedNameservers": { + "name": "Игн. Системные DNS", + "description": "Игнорировать системные DNS" + }, + "dns/noInsecureProtocols": { + "name": "Только Безопасные DNS", + "description": "Не использовать обычный DNS" + }, + "dns/noMulticastDNS": { + "name": "Игн. Multicast DNS", + "description": "Не использовать mDNS" + }, + "dns/useStaleCache": { + "name": "Использовать DNS Кэш", + "description": "Использовать кэш даже если истёк" + }, + "filter/askTimeout": { + "name": "Таймаут Запроса", + "description": "Время ожидания ответа на запрос" + }, + "filter/askWithSystemNotifications": { + "name": "Запросы на Рабочий Стол", + "description": "Отправлять запросы на рабочий стол" + }, + "filter/blockInbound": { + "name": "Блокировать Входящие", + "description": "Блокировать входящие соединения" + }, + "filter/blockInternet": { + "name": "Блокировать Интернет", + "description": "Блокировать доступ в Интернет" + }, + "filter/blockLAN": { + "name": "Блокировать LAN", + "description": "Блокировать локальную сеть" + }, + "filter/blockLocal": { + "name": "Блокировать Localhost", + "description": "Блокировать внутренние соединения" + }, + "filter/blockP2P": { + "name": "Блокировать P2P", + "description": "Блокировать прямые соединения" + }, + "filter/customListFile": { + "name": "Свой Список Фильтров", + "description": "Путь к файлу фильтров" + }, + "filter/defaultAction": { + "name": "Действие по Умолчанию", + "description": "Действие когда ничего не подходит" + }, + "filter/disableAutoPermit": { + "name": "Откл. Авто-Разрешение", + "description": "Отключить авторазрешение" + }, + "filter/dnsQueryInterception": { + "name": "Бесшовный DNS", + "description": "Перехват DNS запросов" + }, + "filter/domainHeuristics": { + "name": "Эвристика Доменов", + "description": "Блокировать подозрительные домены" + }, + "filter/enable": { + "name": "Фильтр Приватности", + "description": "Включить фильтр приватности" + }, + "filter/endpoints": { + "name": "Исходящие Правила", + "description": "Правила для исходящих соединений" + }, + "filter/includeCNAMEs": { + "name": "Блок. Алиасы Доменов", + "description": "Блокировать CNAME алиасы" + }, + "filter/includeSubdomains": { + "name": "Блокировать Поддомены", + "description": "Блокировать поддомены из списков" + }, + "filter/lists": { + "name": "Списки Фильтров", + "description": "Включить списки блокировки" + }, + "filter/permanentVerdicts": { + "name": "Постоянные Вердикты", + "description": "Улучшить производительность" + }, + "filter/preventBypassing": { + "name": "Защита от Обхода DNS", + "description": "Предотвратить обход DNS" + }, + "filter/removeBlockedDNS": { + "name": "Отклонять Блок. IP", + "description": "Отклонять заблокированные IP" + }, + "filter/removeOutOfScopeDNS": { + "name": "Split-View DNS", + "description": "Разделение глобальных/приватных DNS" + }, + "filter/serviceEndpoints": { + "name": "Входящие Правила", + "description": "Правила для входящих соединений" + }, + "history/enable": { + "name": "История Сети", + "description": "Сохранять историю соединений" + }, + "history/keep": { + "name": "Хранить Историю", + "description": "Дней хранения истории" + }, + "spn/dnsExitPolicy": { + "name": "Правила DNS Выхода", + "description": "Страны для DNS выходных узлов" + }, + "spn/enable": { + "name": "Модуль SPN", + "description": "Включить SPN модуль" + }, + "spn/exitHubPolicy": { + "name": "Правила Выхода", + "description": "Страны для выходных узлов" + }, + "spn/homePolicy": { + "name": "Правила Домашнего Узла", + "description": "Страны для домашнего узла" + }, + "spn/routingAlgorithm": { + "name": "Алгоритм SPN", + "description": "Баланс скорости и приватности" + }, + "spn/specialAccessCode": { + "name": "Код Доступа SPN", + "description": "Код для тестового доступа" + }, + "spn/transitHubPolicy": { + "name": "Транзитные Узлы", + "description": "Страны для транзитных узлов" + }, + "spn/trustNodes": { + "name": "Доверенные Узлы", + "description": "Узлы сообщества для доверия" + }, + "spn/usagePolicy": { + "name": "Правила SPN", + "description": "Сайты для маршрутизации через SPN" + }, + "spn/use": { + "name": "Использовать SPN", + "description": "Защитить трафик через SPN" + }, + "spn/useCommunityNodes": { + "name": "Узлы Сообщества", + "description": "Использовать узлы не от Safing" + } + } +} diff --git a/base/api/endpoints_config.go b/base/api/endpoints_config.go index 42ce4567d..c0849b4a8 100644 --- a/base/api/endpoints_config.go +++ b/base/api/endpoints_config.go @@ -2,6 +2,7 @@ package api import ( "github.com/safing/portmaster/base/config" + "github.com/safing/portmaster/base/i18n" ) func registerConfigEndpoints() error { @@ -11,7 +12,7 @@ func registerConfigEndpoints() error { MimeType: MimeTypeJSON, StructFunc: listConfig, Name: "Export Configuration Options", - Description: "Returns a list of all registered configuration options and their metadata. This does not include the current active or default settings.", + Description: "Returns a list of all registered configuration options and their metadata. This does not include the current active or default settings. Use ?lang=ru for localized names.", }); err != nil { return err } @@ -19,6 +20,44 @@ func registerConfigEndpoints() error { return nil } +// LocalizedOption is an Option with localized Name and Description. +type LocalizedOption struct { + *config.Option + Name string `json:"Name"` + Description string `json:"Description"` +} + func listConfig(ar *Request) (i interface{}, err error) { - return config.ExportOptions(), nil + // Get language from query parameter + lang := ar.URL.Query().Get("lang") + if lang == "" { + lang = i18n.GetLanguage() + } + + // Get original options + opts := config.ExportOptions() + + // If English or no translations, return as-is + if lang == "en" || lang == "" { + return opts, nil + } + + // Set language for translation + i18n.SetLanguage(lang) + + // Create localized copies + localizedOpts := make([]*LocalizedOption, len(opts)) + for idx, opt := range opts { + // Get translated name and description with fallback to original + name := i18n.GetConfigName(opt.Key, opt.Name) + desc := i18n.GetConfigDescription(opt.Key, opt.Description) + + localizedOpts[idx] = &LocalizedOption{ + Option: opt, + Name: name, + Description: desc, + } + } + + return localizedOpts, nil } diff --git a/base/api/main.go b/base/api/main.go index d8e52f851..47801b525 100644 --- a/base/api/main.go +++ b/base/api/main.go @@ -7,6 +7,7 @@ import ( "os" "time" + "github.com/safing/portmaster/base/i18n" "github.com/safing/portmaster/service/mgr" ) @@ -23,6 +24,12 @@ func init() { } func prep() error { + // Initialize i18n system + if err := i18n.Init(); err != nil { + // Log warning but don't fail - translations are optional + _ = err + } + // Register endpoints. if err := registerConfig(); err != nil { return err diff --git a/base/i18n/i18n.go b/base/i18n/i18n.go new file mode 100644 index 000000000..78f272717 --- /dev/null +++ b/base/i18n/i18n.go @@ -0,0 +1,156 @@ +// Package i18n provides internationalization support for the Portmaster. +package i18n + +import ( + "embed" + "encoding/json" + "fmt" + "sync" + + "github.com/safing/portmaster/base/log" +) + +//go:embed translations/*.json +var translationsFS embed.FS + +// Translation represents a single translation entry. +type Translation struct { + Name string `json:"name"` + Description string `json:"description"` + Category string `json:"category,omitempty"` +} + +// Translations holds all translations for a language. +type Translations struct { + Language string `json:"language"` + Config map[string]Translation `json:"config"` + UI map[string]string `json:"ui"` +} + +var ( + currentLang = "en" + translations = make(map[string]*Translations) + translationsMu sync.RWMutex + initialized bool +) + +// SupportedLanguages returns list of supported languages. +var SupportedLanguages = []string{"en", "ru"} + +// Init initializes the i18n system. +func Init() error { + translationsMu.Lock() + defer translationsMu.Unlock() + + if initialized { + return nil + } + + for _, lang := range SupportedLanguages { + data, err := translationsFS.ReadFile(fmt.Sprintf("translations/%s.json", lang)) + if err != nil { + log.Warningf("i18n: failed to load translations for %s: %s", lang, err) + continue + } + + var t Translations + if err := json.Unmarshal(data, &t); err != nil { + log.Warningf("i18n: failed to parse translations for %s: %s", lang, err) + continue + } + + t.Language = lang + translations[lang] = &t + log.Infof("i18n: loaded %d config translations for %s", len(t.Config), lang) + } + + initialized = true + return nil +} + +// SetLanguage sets the current language. +func SetLanguage(lang string) error { + translationsMu.Lock() + defer translationsMu.Unlock() + + if _, ok := translations[lang]; !ok { + return fmt.Errorf("i18n: language %s not supported", lang) + } + + currentLang = lang + log.Infof("i18n: language set to %s", lang) + return nil +} + +// GetLanguage returns the current language. +func GetLanguage() string { + translationsMu.RLock() + defer translationsMu.RUnlock() + return currentLang +} + +// T returns translated string for UI key. +func T(key string) string { + translationsMu.RLock() + defer translationsMu.RUnlock() + + if t, ok := translations[currentLang]; ok { + if val, ok := t.UI[key]; ok { + return val + } + } + + // Fallback to English + if t, ok := translations["en"]; ok { + if val, ok := t.UI[key]; ok { + return val + } + } + + return key +} + +// GetConfigTranslation returns translation for a config key. +func GetConfigTranslation(key string) *Translation { + translationsMu.RLock() + defer translationsMu.RUnlock() + + if t, ok := translations[currentLang]; ok { + if val, ok := t.Config[key]; ok { + return &val + } + } + + // Fallback to English + if t, ok := translations["en"]; ok { + if val, ok := t.Config[key]; ok { + return &val + } + } + + return nil +} + +// GetConfigName returns translated name for a config key. +func GetConfigName(key, fallback string) string { + if t := GetConfigTranslation(key); t != nil && t.Name != "" { + return t.Name + } + return fallback +} + +// GetConfigDescription returns translated description for a config key. +func GetConfigDescription(key, fallback string) string { + if t := GetConfigTranslation(key); t != nil && t.Description != "" { + return t.Description + } + return fallback +} + +// GetConfigCategory returns translated category for a config key. +func GetConfigCategory(key, fallback string) string { + if t := GetConfigTranslation(key); t != nil && t.Category != "" { + return t.Category + } + return fallback +} diff --git a/base/i18n/translations/en.json b/base/i18n/translations/en.json new file mode 100644 index 000000000..e3c3ffadb --- /dev/null +++ b/base/i18n/translations/en.json @@ -0,0 +1,241 @@ +{ + "config": { + "core/apiKeys": { + "name": "API Keys", + "description": "Define API keys for privileged access to the API." + }, + "core/automaticIntelUpdates": { + "name": "Automatic Intelligence Data Updates", + "description": "Automatically check for and download intelligence data updates." + }, + "core/automaticUpdates": { + "name": "Automatic Software Updates", + "description": "Automatically check for and download software updates." + }, + "core/devMode": { + "name": "Development Mode", + "description": "In Development Mode, security restrictions are lifted/softened." + }, + "core/enableProcessDetection": { + "name": "Process Detection", + "description": "This option enables the attribution of network traffic to processes." + }, + "core/expertiseLevel": { + "name": "UI Mode", + "description": "Control the default amount of settings and information shown." + }, + "core/listenAddress": { + "name": "API Listen Address", + "description": "Defines the IP address and port on which the internal API listens." + }, + "core/locale": { + "name": "Time and Date Format", + "description": "Configures the time and date format for the user interface." + }, + "core/log/level": { + "name": "Log Level", + "description": "Configure the logging level." + }, + "core/metrics/comment": { + "name": "Metrics Comment Label", + "description": "Define a metrics comment label." + }, + "core/metrics/instance": { + "name": "Metrics Instance Name", + "description": "Define the prometheus instance label for all exported metrics." + }, + "core/metrics/push": { + "name": "Push Prometheus Metrics", + "description": "Push metrics to this URL in the prometheus format." + }, + "core/networkService": { + "name": "Network Service", + "description": "Use the Portmaster as a network service." + }, + "core/releaseChannel": { + "name": "Release Channel", + "description": "Use \"Stable\" for the best experience." + }, + "core/releaseLevel": { + "name": "Feature Stability", + "description": "Decide if you want to experiment with unstable features." + }, + "core/useSystemNotifications": { + "name": "Desktop Notifications", + "description": "In addition to showing notifications in the Portmaster App, also send them to the Desktop." + }, + "dns/dontResolveSpecialDomains": { + "name": "Block Unofficial TLDs", + "description": "Block .onion, .i2p, .loki, .bit, .eth and other unofficial domains." + }, + "dns/listenAddress": { + "name": "Internal DNS Server Listen Address", + "description": "Defines the IP address and port on which the internal DNS Server listens." + }, + "dns/nameserverRetryRate": { + "name": "Retry Failing DNS Servers", + "description": "Duration in seconds how often failing DNS server should be retried." + }, + "dns/nameservers": { + "name": "DNS Servers", + "description": "DNS servers to use for resolving DNS requests." + }, + "dns/noAssignedNameservers": { + "name": "Ignore System/Network Servers", + "description": "Ignore DNS servers configured in your system or network." + }, + "dns/noInsecureProtocols": { + "name": "Use Secure Protocols Only", + "description": "Never resolve using insecure protocols, ie. plain DNS." + }, + "dns/noMulticastDNS": { + "name": "Ignore Multicast DNS", + "description": "Do not resolve using Multicast DNS." + }, + "dns/useStaleCache": { + "name": "Always Use DNS Cache", + "description": "Always use the DNS cache, even if entries have expired." + }, + "filter/askTimeout": { + "name": "Prompt Timeout", + "description": "How long the Portmaster will wait for a reply to a prompt notification." + }, + "filter/askWithSystemNotifications": { + "name": "Prompt Desktop Notifications", + "description": "In addition to showing prompt notifications in the Portmaster App, also send them to the Desktop." + }, + "filter/blockInbound": { + "name": "Force Block Incoming Connections", + "description": "Connections initiated towards your device from the LAN or Internet." + }, + "filter/blockInternet": { + "name": "Force Block Internet Access", + "description": "Force Block connections from and to the Internet." + }, + "filter/blockLAN": { + "name": "Force Block LAN", + "description": "Force Block all connections from and to the Local Area Network." + }, + "filter/blockLocal": { + "name": "Force Block Device-Local Connections", + "description": "Force Block all internal connections on your own device." + }, + "filter/blockP2P": { + "name": "Force Block P2P/Direct Connections", + "description": "These are connections that are established directly to an IP address." + }, + "filter/customListFile": { + "name": "Custom Filter List", + "description": "Specify the file path to a custom filter list (.txt)." + }, + "filter/defaultAction": { + "name": "Default Network Action", + "description": "The default network action is applied when nothing else allows or blocks a connection." + }, + "filter/disableAutoPermit": { + "name": "Disable Auto Allow", + "description": "Auto Allow searches for a relation between an app and the destination of a connection." + }, + "filter/dnsQueryInterception": { + "name": "Seamless DNS Integration", + "description": "Intercept and redirect astray DNS queries to the Portmaster's internal DNS server." + }, + "filter/domainHeuristics": { + "name": "Enable Domain Heuristics", + "description": "Checks for suspicious domain names and blocks them." + }, + "filter/enable": { + "name": "Enable Privacy Filter", + "description": "Enable the Privacy Filter. If turned off, all privacy filter protections are fully disabled." + }, + "filter/endpoints": { + "name": "Outgoing Rules", + "description": "Rules that apply to outgoing network connections." + }, + "filter/includeCNAMEs": { + "name": "Block Domain Aliases", + "description": "Block a domain if a resolved CNAME (alias) is blocked by a rule." + }, + "filter/includeSubdomains": { + "name": "Block Subdomains of Filter List Entries", + "description": "Additionally block all subdomains of entries in selected filter lists." + }, + "filter/lists": { + "name": "Filter Lists", + "description": "Block connections that match enabled filter lists." + }, + "filter/permanentVerdicts": { + "name": "Permanent Verdicts", + "description": "Making these verdicts permanent brings a major performance increase." + }, + "filter/preventBypassing": { + "name": "Block Secure DNS Bypassing", + "description": "Prevent apps from bypassing Portmaster's Secure DNS resolver." + }, + "filter/removeBlockedDNS": { + "name": "Reject Blocked IPs", + "description": "Reject blocked IP addresses directly from the DNS response." + }, + "filter/removeOutOfScopeDNS": { + "name": "Enforce Global/Private Split-View", + "description": "Reject private IP addresses from public DNS responses." + }, + "filter/serviceEndpoints": { + "name": "Incoming Rules", + "description": "Rules that apply to incoming network connections." + }, + "history/enable": { + "name": "Enable Network History", + "description": "Save connections in a database to view and search them later." + }, + "history/keep": { + "name": "Keep Network History", + "description": "Specify how many days the network history data should be kept." + }, + "spn/dnsExitPolicy": { + "name": "DNS Exit Node Rules", + "description": "Customize which countries should be used as DNS Exit Nodes." + }, + "spn/enable": { + "name": "SPN Module", + "description": "Start the Safing Privacy Network module." + }, + "spn/exitHubPolicy": { + "name": "Exit Node Rules", + "description": "Customize which countries should be used for your Exit Nodes." + }, + "spn/homePolicy": { + "name": "Home Node Rules", + "description": "Customize which countries should be used for your Home Node." + }, + "spn/routingAlgorithm": { + "name": "Select SPN Routing Algorithm", + "description": "Select the routing algorithm for your connections through the SPN." + }, + "spn/specialAccessCode": { + "name": "Special Access Code", + "description": "Special Access Codes grant access to the SPN for testing." + }, + "spn/transitHubPolicy": { + "name": "Transit Node Rules", + "description": "Customize which countries should be used as Transit Nodes." + }, + "spn/trustNodes": { + "name": "Trust Nodes", + "description": "Specify which community nodes to additionally trust." + }, + "spn/usagePolicy": { + "name": "SPN Rules", + "description": "Customize which websites should be routed through the SPN." + }, + "spn/use": { + "name": "Use SPN", + "description": "Protect network traffic with the Safing Privacy Network." + }, + "spn/useCommunityNodes": { + "name": "Use Community Nodes", + "description": "Use nodes (servers) not operated by Safing themselves." + } + }, + "ui": {} +} diff --git a/base/i18n/translations/ru.json b/base/i18n/translations/ru.json new file mode 100644 index 000000000..cacf6c8af --- /dev/null +++ b/base/i18n/translations/ru.json @@ -0,0 +1,241 @@ +{ + "config": { + "core/apiKeys": { + "name": "API Ключи", + "description": "Определите API ключи для привилегированного доступа к API. Каждая запись — отдельный API ключ с соответствующими разрешениями." + }, + "core/automaticIntelUpdates": { + "name": "Автоматические Обновления Данных", + "description": "Автоматически проверять и скачивать обновления данных разведки. Включает списки фильтров, geo-ip данные и другое." + }, + "core/automaticUpdates": { + "name": "Автоматические Обновления ПО", + "description": "Автоматически проверять и скачивать обновления программного обеспечения." + }, + "core/devMode": { + "name": "Режим Разработчика", + "description": "В режиме разработчика ограничения безопасности снимаются/ослабляются для неограниченного доступа при отладке и тестировании." + }, + "core/enableProcessDetection": { + "name": "Определение Процессов", + "description": "Эта опция включает привязку сетевого трафика к процессам. Без неё настройки приложений фактически отключены." + }, + "core/expertiseLevel": { + "name": "Режим Интерфейса", + "description": "Управляйте объёмом отображаемых настроек и информации. Скрытые настройки всё равно действуют." + }, + "core/listenAddress": { + "name": "Адрес Прослушивания API", + "description": "Определяет IP-адрес и порт, на котором прослушивает внутренний API." + }, + "core/locale": { + "name": "Формат Времени и Даты", + "description": "Настраивает формат времени и даты для пользовательского интерфейса." + }, + "core/log/level": { + "name": "Уровень Журнала", + "description": "Настройте уровень журналирования." + }, + "core/metrics/comment": { + "name": "Метка Комментария Метрик", + "description": "Определите метку комментария метрик, которая добавляется к информационной метрике." + }, + "core/metrics/instance": { + "name": "Имя Экземпляра Метрик", + "description": "Определите метку prometheus instance для всех экспортируемых метрик." + }, + "core/metrics/push": { + "name": "Отправка Метрик Prometheus", + "description": "Отправлять метрики на этот URL в формате prometheus." + }, + "core/networkService": { + "name": "Сетевой Сервис", + "description": "Используйте Portmaster как сетевой сервис, где это применимо." + }, + "core/releaseChannel": { + "name": "Канал Релиза", + "description": "Используйте \"Stable\" для лучшего опыта. Канал \"Beta\" будет иметь новейшие функции и исправления." + }, + "core/releaseLevel": { + "name": "Стабильность Функций", + "description": "Решите, хотите ли вы экспериментировать с нестабильными функциями." + }, + "core/useSystemNotifications": { + "name": "Уведомления Рабочего Стола", + "description": "В дополнение к показу уведомлений в приложении Portmaster, также отправлять их на рабочий стол." + }, + "dns/dontResolveSpecialDomains": { + "name": "Блокировать Неофициальные TLD", + "description": "Блокировать .onion, .i2p, .loki, .bit, .eth и другие неофициальные домены." + }, + "dns/listenAddress": { + "name": "Адрес Внутреннего DNS Сервера", + "description": "Определяет IP-адрес и порт, на котором прослушивает внутренний DNS сервер." + }, + "dns/nameserverRetryRate": { + "name": "Повторные Попытки DNS Серверов", + "description": "Длительность в секундах для повторных попыток к отказавшим DNS серверам." + }, + "dns/nameservers": { + "name": "DNS Серверы", + "description": "DNS серверы для разрешения DNS запросов." + }, + "dns/noAssignedNameservers": { + "name": "Игнорировать Системные DNS", + "description": "Игнорировать DNS серверы, настроенные в вашей системе или сети." + }, + "dns/noInsecureProtocols": { + "name": "Только Безопасные Протоколы", + "description": "Никогда не разрешать через небезопасные протоколы, т.е. обычный DNS." + }, + "dns/noMulticastDNS": { + "name": "Игнорировать Multicast DNS", + "description": "Не разрешать через Multicast DNS." + }, + "dns/useStaleCache": { + "name": "Всегда Использовать DNS Кэш", + "description": "Всегда использовать DNS кэш, даже если записи истекли." + }, + "filter/askTimeout": { + "name": "Таймаут Запроса", + "description": "Как долго Portmaster будет ждать ответа на уведомление-запрос." + }, + "filter/askWithSystemNotifications": { + "name": "Запросы через Уведомления", + "description": "В дополнение к показу уведомлений-запросов в приложении, отправлять их на рабочий стол." + }, + "filter/blockInbound": { + "name": "Принудительно Блокировать Входящие", + "description": "Соединения, инициированные к вашему устройству из LAN или Интернета." + }, + "filter/blockInternet": { + "name": "Принудительно Блокировать Интернет", + "description": "Принудительно блокировать соединения из и в Интернет." + }, + "filter/blockLAN": { + "name": "Принудительно Блокировать LAN", + "description": "Принудительно блокировать все соединения из и в локальную сеть." + }, + "filter/blockLocal": { + "name": "Принудительно Блокировать Localhost", + "description": "Принудительно блокировать все внутренние соединения на вашем устройстве." + }, + "filter/blockP2P": { + "name": "Принудительно Блокировать P2P", + "description": "Соединения, устанавливаемые напрямую к IP-адресу без предварительного разрешения DNS." + }, + "filter/customListFile": { + "name": "Пользовательский Список Фильтров", + "description": "Укажите путь к пользовательскому списку фильтров (.txt)." + }, + "filter/defaultAction": { + "name": "Действие Сети по Умолчанию", + "description": "Действие по умолчанию применяется, когда ничто другое не разрешает или блокирует соединение." + }, + "filter/disableAutoPermit": { + "name": "Отключить Авто-Разрешение", + "description": "Авто-Разрешение ищет связь между приложением и назначением соединения." + }, + "filter/dnsQueryInterception": { + "name": "Бесшовная Интеграция DNS", + "description": "Перехватывать и перенаправлять DNS запросы на внутренний DNS сервер Portmaster." + }, + "filter/domainHeuristics": { + "name": "Эвристика Доменов", + "description": "Проверяет подозрительные доменные имена и блокирует их." + }, + "filter/enable": { + "name": "Включить Фильтр Приватности", + "description": "Включить фильтр приватности. При отключении все защиты фильтра полностью отключены." + }, + "filter/endpoints": { + "name": "Исходящие Правила", + "description": "Правила для исходящих сетевых соединений." + }, + "filter/includeCNAMEs": { + "name": "Блокировать Алиасы Доменов", + "description": "Блокировать домен, если разрешённый CNAME (алиас) заблокирован правилом." + }, + "filter/includeSubdomains": { + "name": "Блокировать Поддомены", + "description": "Дополнительно блокировать все поддомены записей в выбранных списках фильтров." + }, + "filter/lists": { + "name": "Списки Фильтров", + "description": "Блокировать соединения, соответствующие включённым спискам фильтров." + }, + "filter/permanentVerdicts": { + "name": "Постоянные Вердикты", + "description": "Постоянные вердикты значительно улучшают производительность." + }, + "filter/preventBypassing": { + "name": "Блокировать Обход DNS", + "description": "Предотвращает обход приложениями защищённого DNS резолвера Portmaster." + }, + "filter/removeBlockedDNS": { + "name": "Отклонять Заблокированные IP", + "description": "Отклонять заблокированные IP-адреса напрямую из DNS ответа." + }, + "filter/removeOutOfScopeDNS": { + "name": "Разделение Глобальных/Приватных DNS", + "description": "Отклонять приватные IP-адреса из публичных DNS ответов." + }, + "filter/serviceEndpoints": { + "name": "Входящие Правила", + "description": "Правила для входящих сетевых соединений." + }, + "history/enable": { + "name": "Включить Историю Сети", + "description": "Сохранять соединения в базе данных для последующего просмотра и поиска." + }, + "history/keep": { + "name": "Хранить Историю Сети", + "description": "Укажите, сколько дней хранить данные истории сети." + }, + "spn/dnsExitPolicy": { + "name": "Правила DNS Выходных Узлов", + "description": "Настройте, какие страны должны использоваться как DNS выходные узлы." + }, + "spn/enable": { + "name": "Модуль SPN", + "description": "Запустить модуль Safing Privacy Network." + }, + "spn/exitHubPolicy": { + "name": "Правила Выходных Узлов", + "description": "Настройте, какие страны использовать для выходных узлов." + }, + "spn/homePolicy": { + "name": "Правила Домашнего Узла", + "description": "Настройте, какие страны использовать для домашнего узла." + }, + "spn/routingAlgorithm": { + "name": "Алгоритм Маршрутизации SPN", + "description": "Выберите алгоритм маршрутизации для соединений через SPN." + }, + "spn/specialAccessCode": { + "name": "Специальный Код Доступа", + "description": "Специальные коды доступа предоставляют доступ к SPN для тестирования." + }, + "spn/transitHubPolicy": { + "name": "Правила Транзитных Узлов", + "description": "Настройте, какие страны использовать как транзитные узлы." + }, + "spn/trustNodes": { + "name": "Доверенные Узлы", + "description": "Укажите, каким узлам сообщества дополнительно доверять." + }, + "spn/usagePolicy": { + "name": "Правила SPN", + "description": "Настройте, какие сайты маршрутизировать через SPN." + }, + "spn/use": { + "name": "Использовать SPN", + "description": "Защитить сетевой трафик с помощью Safing Privacy Network." + }, + "spn/useCommunityNodes": { + "name": "Использовать Узлы Сообщества", + "description": "Использовать узлы, не управляемые Safing." + } + }, + "ui": {} +} diff --git a/desktop/angular/.gitignore b/desktop/angular/.gitignore index d86cd691d..4d7176a52 100644 --- a/desktop/angular/.gitignore +++ b/desktop/angular/.gitignore @@ -2,4 +2,5 @@ node_modules dist dist-extension dist-lib -.angular \ No newline at end of file +.angular +src/assets \ No newline at end of file diff --git a/desktop/angular/package-lock.json b/desktop/angular/package-lock.json index 6a5f0ec1d..bd5f489ce 100644 --- a/desktop/angular/package-lock.json +++ b/desktop/angular/package-lock.json @@ -24,6 +24,8 @@ "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@ngx-translate/core": "^15.0.0", + "@ngx-translate/http-loader": "^8.0.0", "@tauri-apps/api": ">=2.1.1", "@tauri-apps/plugin-cli": ">=2.0.0", "@tauri-apps/plugin-clipboard-manager": ">=2.0.0", @@ -294,6 +296,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -400,6 +403,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -474,6 +478,7 @@ "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -736,6 +741,7 @@ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.12.tgz", "integrity": "sha512-MD0ElviEfAJY8qMOd6/jjSSvtqER2RDAi0lxe6EtUacC1DHCYkaPrKW4vLqY+tmZBg1yf+6n+uS77pXcHHcA3w==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -769,6 +775,7 @@ "integrity": "sha512-aqfNYZ45ndrf36i+7AhQ9R8BCm025j7TtYaUmvvjT4LwiUg6f6KtlZPB/ivBlXmd1g9oXqW4advL0AIi8A/Ozg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@angular-devkit/architect": "0.1602.16", "@angular-devkit/core": "16.2.16", @@ -803,6 +810,7 @@ "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.12.tgz", "integrity": "sha512-B+WY/cT2VgEaz9HfJitBmgdk4I333XG/ybC98CMC4Wz8E49T8yzivmmxXB3OD6qvjcOB6ftuicl6WBqLbZNg2w==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -819,6 +827,7 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.12.tgz", "integrity": "sha512-6SMXUgSVekGM7R6l1Z9rCtUGtlg58GFmgbpMCsGf+VXxP468Njw8rjT2YZkf5aEPxEuRpSHhDYjqz7n14cwCXQ==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -839,6 +848,7 @@ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.12.tgz", "integrity": "sha512-pWSrr152562ujh6lsFZR8NfNc5Ljj+zSTQO44DsuB0tZjwEpnRcjJEgzuhGXr+CoiBf+jTSPZKemtSktDk5aaA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "7.23.2", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -954,6 +964,7 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.12.tgz", "integrity": "sha512-GLLlDeke/NjroaLYOks0uyzFVo6HyLl7VOm0K1QpLXnYvW63W9Ql/T3yguRZa7tRkOAeFZ3jw+1wnBD4O8MoUA==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -970,6 +981,7 @@ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.12.tgz", "integrity": "sha512-1Eao89hlBgLR3v8tU91vccn21BBKL06WWxl7zLpQmG6Hun+2jrThgOE4Pf3os4fkkbH4Apj0tWL2fNIWe/blbw==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -1114,6 +1126,7 @@ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.12.tgz", "integrity": "sha512-NnH7ju1iirmVEsUq432DTm0nZBGQsBrU40M3ZeVHMQ2subnGiyUs3QyzDz8+VWLL/T5xTxWLt9BkDn65vgzlIQ==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -1154,6 +1167,7 @@ "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.12.tgz", "integrity": "sha512-aU6QnYSza005V9P3W6PpkieL56O0IHps96DjqI1RS8yOJUl3THmokqYN4Fm5+HXy4f390FN9i6ftadYQDKeWmA==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -1257,6 +1271,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", @@ -3740,6 +3755,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -4054,6 +4070,36 @@ "webpack": "^5.54.0" } }, + "node_modules/@ngx-translate/core": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-15.0.0.tgz", + "integrity": "sha512-Am5uiuR0bOOxyoercDnAA3rJVizo4RRqJHo8N3RqJ+XfzVP/I845yEnMADykOHvM6HkVm4SZSnJBOiz0Anx5BA==", + "license": "SEE LICENSE IN LICENSE", + "peer": true, + "engines": { + "node": "^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "rxjs": "^6.5.5 || ^7.4.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-8.0.0.tgz", + "integrity": "sha512-SFMsdUcmHF5OdZkL1CHEoSAwbP5EbAOPTLLboOCRRoOg21P4GJx+51jxGdJeGve6LSKLf4Pay7BkTwmE6vxYlg==", + "license": "SEE LICENSE IN LICENSE", + "engines": { + "node": "^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@ngx-translate/core": ">=15.0.0", + "rxjs": "^6.5.5 || ^7.4.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5323,7 +5369,6 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -5335,7 +5380,6 @@ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -5829,6 +5873,7 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -6451,6 +6496,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6597,6 +6643,7 @@ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -7457,6 +7504,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -8596,16 +8644,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cytoscape": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.31.0.tgz", - "integrity": "sha512-zDGn1K/tfZwEnoGOcHc0H4XazqAAXAuDpcYw9mUnUjATjqljyCNGJv8uEvbvxGaGHaVshxMecyl6oc6uKzRfbw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/cytoscape-cose-bilkent": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", @@ -8966,6 +9004,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -10172,6 +10211,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -13611,6 +13651,7 @@ "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -14758,6 +14799,7 @@ "integrity": "sha512-VTJ7Qtge52+1subkhmF5nOqLNbVutA8/igJ0A5vH6Mgpb8Z/3HeZomtD1SHzZF5Dqp+p+QPHE548FWYu1MdMSQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.0", @@ -16015,6 +16057,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@nrwl/tao": "16.0.2", "@parcel/watcher": "2.0.4", @@ -17065,6 +17108,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -17469,6 +17513,7 @@ "deprecated": "We have news to share - Protractor is deprecated and will reach end-of-life by Summer 2023. To learn more and find out about other options please refer to this post on the Angular blog. Thank you for using and contributing to Protractor. https://goo.gle/state-of-e2e-in-angular", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/q": "^0.0.32", "@types/selenium-webdriver": "^3.0.0", @@ -18654,6 +18699,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -18746,6 +18792,7 @@ "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -20146,6 +20193,7 @@ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -20789,7 +20837,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsutils": { "version": "3.21.0", @@ -21447,6 +21496,7 @@ "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -21865,6 +21915,7 @@ "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -22067,7 +22118,6 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -22082,7 +22132,6 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } diff --git a/desktop/angular/package.json b/desktop/angular/package.json index 663412931..5f7692f8e 100644 --- a/desktop/angular/package.json +++ b/desktop/angular/package.json @@ -6,11 +6,9 @@ "start": "npm install && npm run build-libs:dev && ng serve --proxy-config ./proxy.json", "build-libs": "cross-env NODE_ENV=production ng build --configuration production @safing/ui && cross-env NODE_ENV=production ng build --configuration production @safing/portmaster-api", "build-libs:dev": "ng build --configuration development @safing/ui && ng build --configuration development @safing/portmaster-api", - "build-ui:dev:watch": "ng build --configuration development @safing/ui --watch", "build-api:dev:watch": "ng build --configuration development @safing/portmaster-api --watch", "serve-with-lib-watch": "ng serve --proxy-config ./proxy.json --poll=2000", - "serve": "npm run build-libs:dev && ng serve --proxy-config ./proxy.json", "build:dev": "npm run build-libs:dev && ng build", "test": "ng test", @@ -43,6 +41,8 @@ "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@ngx-translate/core": "^15.0.0", + "@ngx-translate/http-loader": "^8.0.0", "@tauri-apps/api": ">=2.1.1", "@tauri-apps/plugin-cli": ">=2.0.0", "@tauri-apps/plugin-clipboard-manager": ">=2.0.0", diff --git a/desktop/angular/portmaster.zip b/desktop/angular/portmaster.zip new file mode 100644 index 000000000..153c939c5 Binary files /dev/null and b/desktop/angular/portmaster.zip differ diff --git a/desktop/angular/portmaster_new.zip b/desktop/angular/portmaster_new.zip new file mode 100644 index 000000000..1668079e7 Binary files /dev/null and b/desktop/angular/portmaster_new.zip differ diff --git a/desktop/angular/src/app/app.module.ts b/desktop/angular/src/app/app.module.ts index b083642c3..6a7da5372 100644 --- a/desktop/angular/src/app/app.module.ts +++ b/desktop/angular/src/app/app.module.ts @@ -4,8 +4,11 @@ import { PortalModule } from '@angular/cdk/portal'; import { ScrollingModule } from '@angular/cdk/scrolling'; import { CdkTableModule } from '@angular/cdk/table'; import { CommonModule, registerLocaleData } from '@angular/common'; +import { HttpClient, HttpClientModule } from '@angular/common/http'; import { APP_INITIALIZER, LOCALE_ID, NgModule } from '@angular/core'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; @@ -15,7 +18,8 @@ import { far } from '@fortawesome/free-regular-svg-icons'; import { fas } from '@fortawesome/free-solid-svg-icons'; import { ConfigService, PortmasterAPIModule, StringSetting, getActualValue } from '@safing/portmaster-api'; import { OverlayStepperModule, SfngAccordionModule, SfngDialogModule, SfngDropDownModule, SfngPaginationModule, SfngSelectModule, SfngTipUpModule, SfngToggleSwitchModule, SfngTooltipModule, TabModule, UiModule } from '@safing/ui'; -import MyYamlFile from 'js-yaml-loader!../i18n/helptexts.yaml'; +import helptextsEn from 'js-yaml-loader!../i18n/helptexts.yaml'; +import helptextsRu from 'js-yaml-loader!../i18n/helptexts.ru.yaml'; import * as i18n from 'ng-zorro-antd/i18n'; import { MarkdownModule } from 'ngx-markdown'; import { firstValueFrom } from 'rxjs'; @@ -69,6 +73,12 @@ import { MergeProfileDialogComponent } from './pages/app-view/merge-profile-dial import { AppInsightsComponent } from './pages/app-view/app-insights/app-insights.component'; import { INTEGRATION_SERVICE, integrationServiceFactory } from './integration'; import { SupportProgressDialogComponent } from './pages/support/progress-dialog'; +import { LanguageSelectorComponent } from './shared/language-selector/language-selector.component'; + +// Factory for ngx-translate HttpLoader +export function HttpLoaderFactory(http: HttpClient) { + return new TranslateHttpLoader(http, './assets/i18n/', '.json'); +} function loadAndSetLocaleInitializer(configService: ConfigService) { return async function () { @@ -88,6 +98,11 @@ function loadAndSetLocaleInitializer(configService: ConfigService) { angularLocaleID = 'en-GB' nzLocaleID = 'en_GB' break; + case 'ru': + case 'ru-RU': + angularLocaleID = 'ru' + nzLocaleID = 'ru_RU' + break; default: console.error(`Unsupported locale value: ${currentValue}, defaulting to en-GB`) @@ -103,14 +118,14 @@ function loadAndSetLocaleInitializer(configService: ConfigService) { localeModuleID = "en"; } - /* webpackInclude: /(en|en-GB)\.mjs$/ */ + /* webpackInclude: /(en|en-GB|ru)\.mjs$/ */ /* webpackChunkName: "./l10n-base/[request]"*/ await import(`../../node_modules/@angular/common/locales/${localeModuleID}.mjs`) .then(locale => { registerLocaleData(locale.default) localeConfig.localeId = angularLocaleID; - localeConfig.nzLocale = (i18n as any)[nzLocaleID]; + localeConfig.nzLocale = (i18n as any)[nzLocaleID] || i18n.en_GB; }) } catch (err) { console.error(`failed to load locale module for ${angularLocaleID}:`, err) @@ -118,6 +133,26 @@ function loadAndSetLocaleInitializer(configService: ConfigService) { } } +// Get stored language for helptexts selection +function getStoredLanguage(): string { + try { + return localStorage.getItem('portmaster-language') || 'en'; + } catch { + return 'en'; + } +} + +// Select helptexts based on stored language +function getHelpTexts() { + const lang = getStoredLanguage(); + switch (lang) { + case 'ru': + return helptextsRu; + default: + return helptextsEn; + } +} + const localeConfig = { nzLocale: i18n.en_GB, localeId: 'en-GB' @@ -167,6 +202,15 @@ const localeConfig = { BrowserAnimationsModule, FormsModule, ReactiveFormsModule, + HttpClientModule, + TranslateModule.forRoot({ + defaultLanguage: 'en', + loader: { + provide: TranslateLoader, + useFactory: HttpLoaderFactory, + deps: [HttpClient] + } + }), AppRoutingModule, FontAwesomeModule, OverlayModule, @@ -177,7 +221,7 @@ const localeConfig = { ScrollingModule, SfngAccordionModule, TabModule, - SfngTipUpModule.forRoot(MyYamlFile, NotificationsService), + SfngTipUpModule.forRoot(getHelpTexts(), NotificationsService), SfngTooltipModule, ActionIndicatorModule, SfngDialogModule, @@ -199,6 +243,7 @@ const localeConfig = { CommonPipesModule, UiModule, SPNModule, + LanguageSelectorComponent, PortmasterAPIModule.forRoot({ httpAPI: environment.httpAPI, websocketAPI: environment.portAPI, diff --git a/desktop/angular/src/app/intro/intro.module.ts b/desktop/angular/src/app/intro/intro.module.ts index 8be0f36b8..16405d296 100644 --- a/desktop/angular/src/app/intro/intro.module.ts +++ b/desktop/angular/src/app/intro/intro.module.ts @@ -3,6 +3,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { SfngDropDownModule, SfngTipUpModule, StepperConfig } from "@safing/ui"; +import { TranslateModule } from "@ngx-translate/core"; import { ConfigModule } from "../shared/config"; import { Step1WelcomeComponent } from "./step-1-welcome"; import { Step2TrackersComponent } from "./step-2-trackers"; @@ -24,6 +25,7 @@ const steps = [ SfngDropDownModule, ConfigModule, SfngTipUpModule, + TranslateModule, ], declarations: steps }) diff --git a/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.html b/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.html index 2c8010c66..00a2cdd05 100644 --- a/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.html +++ b/desktop/angular/src/app/intro/step-1-welcome/step-1-welcome.html @@ -1,14 +1,13 @@ -

Portmaster Protects Your Privacy

+

{{ 'intro.step1.title' | translate }}

- Portmaster enhances your privacy with powerful defaults - no configuration needed! Of course you can customize - everything to your specific needs. + {{ 'intro.step1.description' | translate }}

diff --git a/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.html b/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.html index bfd16cd18..38afc3896 100644 --- a/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.html +++ b/desktop/angular/src/app/intro/step-2-trackers/step-2-trackers.html @@ -1,10 +1,8 @@ -

Trackers Are Blocked System-Wide

+

{{ 'intro.step2.title' | translate }}

-

Portmaster automatically blocks ads, trackers and malware hosts on your whole device. Portmaster knows what to block - through trusted domain lists, which are also used by Ad-Blockers in browsers, etc. You can always customize this in - the settings.

+

{{ 'intro.step2.description' | translate }}

- + diff --git a/desktop/angular/src/app/intro/step-3-dns/step-3-dns.html b/desktop/angular/src/app/intro/step-3-dns/step-3-dns.html index aa17288af..94ab6af57 100644 --- a/desktop/angular/src/app/intro/step-3-dns/step-3-dns.html +++ b/desktop/angular/src/app/intro/step-3-dns/step-3-dns.html @@ -1,9 +1,8 @@ -

Secure DNS for All Connections

+

{{ 'intro.step3.title' | translate }}

-

Portmaster automatically encrypts all your DNS queries to safeguard them from prying eyes. Portmaster sets a default - provider, but you can always switch to a custom DNS-over-TLS provider in the global settings.

+

{{ 'intro.step3.description' | translate }}

- +
diff --git a/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.html b/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.html index f15afd369..3873a22b8 100644 --- a/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.html +++ b/desktop/angular/src/app/intro/step-4-tipups/step-4-tipups.html @@ -1,10 +1,9 @@ -

Learn More as You Explore

+

{{ 'intro.step4.title' | translate }}

-

Portmaster has a lot more to offer. When you decide to dive deeper you can always click on an information icon to - learn more about a certain feature. Look out for those!

+

{{ 'intro.step4.description' | translate }}

- Click Me! + {{ 'intro.step4.clickMe' | translate }}
diff --git a/desktop/angular/src/app/layout/navigation/navigation.html b/desktop/angular/src/app/layout/navigation/navigation.html index 8d9989dd0..67b6a4e74 100644 --- a/desktop/angular/src/app/layout/navigation/navigation.html +++ b/desktop/angular/src/app/layout/navigation/navigation.html @@ -89,7 +89,7 @@ diff --git a/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.html b/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.html index cf87f2546..d3532abab 100644 --- a/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.html +++ b/desktop/angular/src/app/pages/app-view/qs-internet/qs-internet.html @@ -13,12 +13,12 @@ Prompting + [queryParams]="{setting: 'filter/defaultAction', tab: 'settings'}">{{ 'common.prompting' | translate }}
- The following enabled settings may interfere: + {{ 'common.interferingSettings' | translate }}:
  • - SPN Exit + {{ 'spn.exit' | translate }} - Get Pro + {{ 'common.getPro' | translate }} - Automatic + {{ 'common.automatic' | translate }} @@ -32,8 +32,8 @@ - Disabled + [sfng-tooltip]="'spn.enableToStart' | translate"> + {{ 'spn.disabled' | translate }} diff --git a/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.html b/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.html index 58ceb09dd..9f6b55ca7 100644 --- a/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.html +++ b/desktop/angular/src/app/pages/app-view/qs-use-spn/qs-use-spn.html @@ -3,11 +3,11 @@ snfgTooltipPosition="right" [sfng-tooltip]="interferingSettings.length > 0 ? tooltipTemplate : null"> - Use SPN + {{ 'apps.useSPN' | translate }} - Get Pro + {{ 'common.getPro' | translate }} - Disabled + [sfng-tooltip]="'spn.enableToStart' | translate"> + {{ 'spn.disabled' | translate }} diff --git a/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.html b/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.html index 65fd80cf1..85f505727 100644 --- a/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.html +++ b/desktop/angular/src/app/pages/dashboard/dashboard-widget/dashboard-widget.component.html @@ -6,7 +6,7 @@ - BETA + {{ 'common.beta' | translate | uppercase }} diff --git a/desktop/angular/src/app/pages/dashboard/dashboard.component.html b/desktop/angular/src/app/pages/dashboard/dashboard.component.html index 23fa62ff2..52d3c4616 100644 --- a/desktop/angular/src/app/pages/dashboard/dashboard.component.html +++ b/desktop/angular/src/app/pages/dashboard/dashboard.component.html @@ -2,18 +2,18 @@ - +
    @@ -70,52 +69,52 @@

    - +
    - + {{ blockedConnections }}
    - + {{ activeConnections }}
    - + {{ activeProfiles }}
    - + {{ dataIncoming | bytes }} - Available in
    Portmaster Plus + {{ 'dashboard.availableInPlus' | translate }}
    - + {{ dataOutgoing | bytes }} - Available in
    Portmaster Plus + {{ 'dashboard.availableInPlus' | translate }}
    - + {{ activeIdentities }} - Available in
    Portmaster Pro + {{ 'dashboard.availableInPro' | translate }}
    @@ -125,14 +124,14 @@

    +
    - +
    @@ -179,7 +178,7 @@

    - No applications have been blocked in the last 10 minutes. + {{ 'dashboard.noBlockedApps' | translate }} @@ -199,37 +198,37 @@

    - + - + - Available in Portmaster Plus + {{ 'dashboard.availableInPlus' | translate }} - + - Available in Portmaster Plus + {{ 'dashboard.availableInPlus' | translate }} - +
    - News is only available if intel data updates are enabled - + {{ 'dashboard.newsUnavailable' | translate }} +
    - Just a second, we're loading the latest news... + {{ 'dashboard.newsLoading' | translate }}
    diff --git a/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.html b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.html index 0695555ef..4d86acfc7 100644 --- a/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.html +++ b/desktop/angular/src/app/pages/dashboard/feature-card/feature-card.component.html @@ -31,14 +31,14 @@ - BETA + {{ 'common.beta' | translate | uppercase }}
    - {{ (disabled ? 'Available in ' : '') + 'Portmaster ' + feature?.InPackage?.Name}} - {{ comingSoon ? ' - coming soon' : '' }} + {{ 'common.availableIn' | translate }} Portmaster {{ feature?.InPackage?.Name }} + - {{ 'common.comingSoon' | translate }} {{ feature?.Comment }}
    @@ -49,7 +49,7 @@

    - Active + {{ 'common.active' | translate }}
    diff --git a/desktop/angular/src/app/pages/monitor/monitor.html b/desktop/angular/src/app/pages/monitor/monitor.html index 1d6fb177e..04f1e9e25 100644 --- a/desktop/angular/src/app/pages/monitor/monitor.html +++ b/desktop/angular/src/app/pages/monitor/monitor.html @@ -1,6 +1,6 @@
    @@ -8,7 +8,7 @@

    - Network Activity + {{ 'monitor.networkActivity' | translate }}

    @@ -16,27 +16,26 @@

    - Network history data available as of {{ data.first | date }}. ({{ data.count }} connections) + {{ 'monitor.historyAvailable' | translate }} {{ data.first | date }}. ({{ data.count }} {{ 'monitor.connections' | translate }}) - Clear + {{ 'common.clear' | translate }} - No network history data available. + {{ 'monitor.noHistoryData' | translate }} - Enable + {{ 'common.enable' | translate }} - Available in Portmaster Plus + {{ 'dashboard.availableInPlus' | translate }} - Use the search bar and drop downs to search and filter the last 10 minutes of network traffic. - Optionally, search all network history data if enabled. + {{ 'monitor.searchHint' | translate }}

    diff --git a/desktop/angular/src/app/pages/settings/settings.html b/desktop/angular/src/app/pages/settings/settings.html index f85c752b8..abd325378 100644 --- a/desktop/angular/src/app/pages/settings/settings.html +++ b/desktop/angular/src/app/pages/settings/settings.html @@ -1,5 +1,5 @@
    - + @@ -8,7 +8,7 @@ - Get Help + {{ 'apps.getHelp' | translate }} @@ -16,7 +16,7 @@

    - Global Settings + {{ 'settings.title' | translate }}

    diff --git a/desktop/angular/src/app/pages/spn/country-details/country-details.html b/desktop/angular/src/app/pages/spn/country-details/country-details.html index f4e439637..dc479e815 100644 --- a/desktop/angular/src/app/pages/spn/country-details/country-details.html +++ b/desktop/angular/src/app/pages/spn/country-details/country-details.html @@ -11,19 +11,19 @@

    - + - +
    @@ -31,7 +31,7 @@

    - by Safing + {{ 'spn.bySafing' | translate }}

    @@ -40,14 +40,14 @@

    - by Community + {{ 'spn.byCommunity' | translate }}

    @@ -55,7 +55,7 @@

    - by Safing + {{ 'spn.bySafing' | translate }}

    @@ -64,14 +64,14 @@

    - by Community + {{ 'spn.byCommunity' | translate }}

    @@ -79,7 +79,7 @@

    - by Safing + {{ 'spn.bySafing' | translate }}

    @@ -88,7 +88,7 @@

    - by Community + {{ 'spn.byCommunity' | translate }}

    @@ -99,11 +99,9 @@

    - +
    - The following Apps have connections that are routed through the - SPN and use an - exit node in {{ countryName }} ({{ countryCode }}): + {{ 'spn.appsExitingInCountry' | translate:{ countryName: countryName, countryCode: countryCode } }}

    - Total Nodes + {{ 'spn.totalNodes' | translate }} {{ totalAliveCount }}
    {{ safingNodeCount }}{{ communityNodeCount }}
    - Exit Nodes + {{ 'spn.exitNodes' | translate }} {{ exitNodeCount }}
    {{ safingExitNodeCount }}{{ communityExitNodeCount }}
    - Nodes In Use + {{ 'spn.nodesInUse' | translate }} {{ activeNodeCount }}
    {{ activeSafingNodeCount }}{{ activeCommunityNodeCount }}
    @@ -11,7 +11,7 @@ @@ -20,7 +20,7 @@ @@ -28,7 +28,7 @@ @@ -36,7 +36,7 @@ @@ -45,7 +45,7 @@ diff --git a/desktop/angular/src/app/pages/spn/pin-details/pin-details.html b/desktop/angular/src/app/pages/spn/pin-details/pin-details.html index 8e53befc6..d52779a3a 100644 --- a/desktop/angular/src/app/pages/spn/pin-details/pin-details.html +++ b/desktop/angular/src/app/pages/spn/pin-details/pin-details.html @@ -9,38 +9,38 @@

    - This SPN Node is run by - - {{ pin.pin.VerifiedOwner || 'Community' }} + {{ pin.pin.VerifiedOwner || ('spn.community' | translate) }}
    - Node is Offline + {{ 'spn.nodeOffline' | translate }}
    - Node has Issues + {{ 'spn.nodeHasIssues' | translate }}
    - +
    - {{ app.count }} connections + {{ app.count }} {{ 'spn.connections' | translate }}
    diff --git a/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.html b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.html index 6ea2166f0..af0680068 100644 --- a/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.html +++ b/desktop/angular/src/app/pages/spn/country-overlay/country-overlay.html @@ -8,18 +8,18 @@ - Safing Nodes: + {{ 'spn.safingNodes' | translate }}: {{ safingNodes.length }} - Community Nodes: + {{ 'spn.communityNodes' | translate }}: {{ communityNodes.length }} - Click country for details + {{ 'spn.clickCountryForDetails' | translate }} diff --git a/desktop/angular/src/app/pages/spn/map-legend/map-legend.html b/desktop/angular/src/app/pages/spn/map-legend/map-legend.html index 07cf2a9b4..66c7b68d0 100644 --- a/desktop/angular/src/app/pages/spn/map-legend/map-legend.html +++ b/desktop/angular/src/app/pages/spn/map-legend/map-legend.html @@ -3,7 +3,7 @@
    - Safing Nodes + {{ 'spn.safingNodes' | translate }} {{ safingNodeCount }}
    - used as Transit + {{ 'spn.usedAsTransit' | translate }} {{ safingActiveCount }} - used as Exit + {{ 'spn.usedAsExit' | translate }} {{ safingExitCount }}
    - Community Nodes + {{ 'spn.communityNodes' | translate }} {{ communityNodeCount }}
    - used as Transit + {{ 'spn.usedAsTransit' | translate }} {{ communityActiveCount }} - used as Exit + {{ 'spn.usedAsExit' | translate }} {{ communityExitCount }}
    - + - + @@ -74,30 +74,30 @@

    - + - + - + - +
    ID {{ pin.pin.ID }}
    Verified Owner{{ 'spn.verifiedOwner' | translate }}
    {{ pin.pin.VerifiedOwner }}
    First Seen{{ 'spn.firstSeen' | translate }} {{ pin.pin.FirstSeen | date:'medium' }}
    States{{ 'spn.states' | translate }}
    {{ pin.pin.States.join(", ") }}
    SessionActive{{ 'spn.sessionActive' | translate }}
    {{ pin.pin.SessionActive }}
    HopDistance{{ 'spn.hopDistance' | translate }}
    {{ pin.pin.HopDistance }}
    Exit Connections{{ 'spn.exitConnections' | translate }}
    {{ exitConnectionCount }}
    -
    - +
    - + diff --git a/desktop/angular/src/app/pages/spn/pin-list/pin-list.html b/desktop/angular/src/app/pages/spn/pin-list/pin-list.html index b21077e4e..3e8c5fb73 100644 --- a/desktop/angular/src/app/pages/spn/pin-list/pin-list.html +++ b/desktop/angular/src/app/pages/spn/pin-list/pin-list.html @@ -1,10 +1,10 @@ - - - - - + + + + + @@ -37,7 +37,7 @@
    - - - diff --git a/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.html b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.html index 4bcd2f4c9..d49618513 100644 --- a/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.html +++ b/desktop/angular/src/app/pages/spn/pin-overlay/pin-overlay.html @@ -4,9 +4,9 @@ cdkDrag (cdkDragReleased)="onDragRelease($event)" (cdkDragStart)="onDragStart()">
    - Show Details - Show exit connections - Copy Node ID + {{ 'spn.showDetails' | translate }} + {{ 'spn.showExitConnections' | translate }} + {{ 'spn.copyNodeID' | translate }} @@ -45,7 +45,7 @@ {{ mapPin.pin.EntityV6?.IP || 'N/A' }}
    - Run By + {{ 'spn.runBy' | translate }}
    - Used As + {{ 'spn.usedAs' | translate }}
    @@ -69,9 +69,9 @@ d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" /> - Home Node + {{ 'spn.homeNode' | translate }} + *ngTemplateOutlet="helpText; context: {$implicit: ('spn.homeNodeHelp' | translate)}"> @@ -82,9 +82,9 @@ d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" /> - Exit Node + {{ 'spn.exitNode' | translate }} + *ngTemplateOutlet="helpText; context: {$implicit: ('spn.exitNodeHelp' | translate)}"> @@ -95,9 +95,9 @@ d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" /> - Transit Node + {{ 'spn.transitNode' | translate }} + *ngTemplateOutlet="helpText; context: {$implicit: ('spn.transitNodeHelp' | translate)}"> diff --git a/desktop/angular/src/app/pages/spn/pin-route/pin-route.html b/desktop/angular/src/app/pages/spn/pin-route/pin-route.html index 1927c5cdf..216880483 100644 --- a/desktop/angular/src/app/pages/spn/pin-route/pin-route.html +++ b/desktop/angular/src/app/pages/spn/pin-route/pin-route.html @@ -8,7 +8,7 @@ - Your Device + {{ 'common.yourDevice' | translate }} @@ -18,22 +18,22 @@ - {{ node.entity.Country || 'No Location' }} + {{ node.entity.Country || ('common.noLocation' | translate) }} {{ node.entity.IP || '' }} - Home - Exit + {{ 'common.home' | translate }} + {{ 'common.exit' | translate }}
    {{ node.pin.Name }} - by + {{ 'common.by' | translate }} - {{ node.pin.VerifiedOwner || 'Community' }} + {{ node.pin.VerifiedOwner || ('common.community' | translate) }}

    AS{{ node.entity.ASN }} - {{ node.entity.ASOrg || - 'AS Organization not in DB' + ('common.asOrgNotInDB' | translate) }}

    {{ node.pin.ID }}
    @@ -45,7 +45,7 @@ - Destination + {{ 'common.destination' | translate }} diff --git a/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.html b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.html index b73683f42..e0af8d0c4 100644 --- a/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.html +++ b/desktop/angular/src/app/pages/spn/spn-feature-carousel/spn-feature-carousel.html @@ -9,12 +9,9 @@
    -

    Get - Multiple Identities for Each App

    +

    {{ 'spn.carousel.multipleIdentities' | translate }}

    - Automatically get a vast amount of identities (IP addresses). The SPN calculates an individual path for - every - connection through the privacy network. Spread your connections across the globe, without any effort. + {{ 'spn.carousel.multipleIdentitiesDesc' | translate }}
    @@ -38,11 +35,9 @@

    Simply Access Regional Content

    -

    Easily Adjust Your Privacy

    +

    {{ 'spn.carousel.adjustPrivacy' | translate }} {{ 'spn.carousel.yourPrivacy' | translate }}

    - SPN just works and does the heavy lifting for you. But of course you can easily configure the settings, so - it fits your needs: Exclude certain apps and domains from the SPN. Or never exit in specific countries. And - so much more... + {{ 'spn.carousel.adjustPrivacyDesc' | translate }}
    @@ -52,10 +47,9 @@

    Easily Adjust Your Privacy

    -

    Built from Scratch, for Your Privacy

    +

    {{ 'spn.carousel.builtFromScratch' | translate }} {{ 'spn.carousel.yourPrivacy' | translate }}

    - SPN is built from the ground up. Privacy is cooked right into it. Inspired by Tor, it comes with onion - routing and state of the art encryption. Fully open source so all our claims can be validated. + {{ 'spn.carousel.builtFromScratchDesc' | translate }}
    @@ -65,11 +59,9 @@

    Built from Scratch, for Your Privacy

    -

    Bye Bye, VPNs

    +

    {{ 'spn.carousel.byeByeVpns' | translate }}

    - VPN technology was NOT built for user privacy, but for company security. Because of that, you can only trust - a VPN provider's policy - and many have been caught abusing user data. Honestly, the best way forward: just - stop paying for outdated technology. + {{ 'spn.carousel.byeByeVpnsDesc' | translate }}
    @@ -148,9 +140,9 @@

    Bye Bye, VPNs

    @@ -159,7 +151,7 @@

    Bye Bye, VPNs

    - + @@ -183,7 +175,7 @@

    Bye Bye, VPNs

    --> - + @@ -195,17 +187,17 @@

    Bye Bye, VPNs

    - + - + - + - + @@ -242,7 +234,7 @@

    Bye Bye, VPNs

    - + diff --git a/desktop/angular/src/app/pages/spn/spn-page.html b/desktop/angular/src/app/pages/spn/spn-page.html index 28a61450d..545f3bc15 100644 --- a/desktop/angular/src/app/pages/spn/spn-page.html +++ b/desktop/angular/src/app/pages/spn/spn-page.html @@ -7,7 +7,7 @@ class="flex flex-row items-center justify-center w-full gap-2 px-4 bg-gray-300 rounded-sm bg-opacity-90 text-xxs" [@fadeIn] *ngIf="loading"> - Loading data, please wait ... + {{ 'spn.loadingData' | translate }} @@ -24,17 +24,17 @@
    - Pricing + {{ 'spn.pricing' | translate }}
    @@ -67,7 +67,7 @@ - Pro Tip: + {{ 'spn.proTip' | translate }}: @@ -77,26 +77,18 @@ - Hold -
    CTRL
    key and click a node on the map to immediately open the node details dialog. +
    - Hold -
    SHIFT
    key to open more than one node overlay when clicking the node icon. +
    - To keep node overlays open move them using - - - . Double click to revert the overlay position on the map. + {{ 'spn.tips.moveOverlay' | translate }} - Click on a country to get more information about all nodes in that country and a list of Apps that use nodes in the - country as an identity. + {{ 'spn.tips.countryClick' | translate }}
    diff --git a/desktop/angular/src/app/pages/spn/spn.module.ts b/desktop/angular/src/app/pages/spn/spn.module.ts index 737ae25f6..481f11c3d 100644 --- a/desktop/angular/src/app/pages/spn/spn.module.ts +++ b/desktop/angular/src/app/pages/spn/spn.module.ts @@ -6,6 +6,7 @@ import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { TranslateModule } from '@ngx-translate/core'; import { SfngToggleSwitchModule, SfngTooltipModule, TabModule } from '@safing/ui'; import { SfngAppIconModule } from 'src/app/shared/app-icon'; import { CountIndicatorModule } from 'src/app/shared/count-indicator'; @@ -45,6 +46,7 @@ import { SPNFeatureCarouselComponent } from './spn-feature-carousel'; CommonPipesModule, DragDropModule, RouterModule, + TranslateModule, ], declarations: [ MapRendererComponent, diff --git a/desktop/angular/src/app/pages/support/form/support-form.html b/desktop/angular/src/app/pages/support/form/support-form.html index 10685d99f..1086de3a0 100644 --- a/desktop/angular/src/app/pages/support/form/support-form.html +++ b/desktop/angular/src/app/pages/support/form/support-form.html @@ -1,6 +1,6 @@
    @@ -25,11 +25,11 @@

    {{ page?.repoHelp }}

    -

    Title

    +

    {{ 'common.title' | translate }}

    - Copy + (ngModelChange)="searchIssues($event)" [placeholder]="'search.summarizeReport' | translate"> + {{ 'common.copy' | translate }}
    @@ -37,7 +37,7 @@

    {{section.title}}

    - Copy + {{ 'common.copy' | translate }}
    -
    +
    - Uploading debug data .... + {{ 'dialogs.progress.uploadingDebug' | translate }} - Creating GitHub issue ... + {{ 'dialogs.progress.creatingIssue' | translate }} - Creating private support ticket ... + {{ 'dialogs.progress.creatingTicket' | translate }}
    @@ -41,30 +41,30 @@ - Ticket prepared successfully + {{ 'dialogs.progress.ticketPrepared' | translate }} - Ticket created successfully! + {{ 'dialogs.progress.ticketCreated' | translate }}
    - Use the following button to open the pre-filled GitHub issue form: + {{ 'dialogs.progress.useButtonToOpen' | translate }}
    - +
    - We successfully create the issue on GitHub for you. + {{ 'dialogs.progress.issueCreatedSuccess' | translate }}
    - Use the following link to check for updates: + {{ 'dialogs.progress.useFollowingLink' | translate }}
    - We will contact you as soon as possbile. + {{ 'dialogs.progress.willContactYou' | translate }} @@ -87,17 +87,17 @@ - Failed to create Support Ticket + {{ 'dialogs.progress.failedToCreate' | translate }} - An error occured while creating your support ticket: + {{ 'dialogs.progress.errorOccurred' | translate }} - {{ error || 'Unknown Error' }} + {{ error || ('errors.unknownError' | translate) }}
    @@ -105,10 +105,10 @@
    + class="bg-blue" (click)="integration.openExternal(url)">{{ 'dialogs.progress.openIssue' | translate }} + (click)="copyUrl()">{{ 'dialogs.progress.copyUrl' | translate }} - +
    diff --git a/desktop/angular/src/app/pages/support/support.html b/desktop/angular/src/app/pages/support/support.html index 2ad9eca2a..47d7ba6f9 100644 --- a/desktop/angular/src/app/pages/support/support.html +++ b/desktop/angular/src/app/pages/support/support.html @@ -1,6 +1,6 @@
    @@ -31,7 +31,7 @@

    {{item.title}}

    FAQ

    -
    diff --git a/desktop/angular/src/app/services/index.ts b/desktop/angular/src/app/services/index.ts index d4b95f1d1..3a6b08e71 100644 --- a/desktop/angular/src/app/services/index.ts +++ b/desktop/angular/src/app/services/index.ts @@ -5,4 +5,5 @@ export { StatusService } from './status.service'; export * from './status.types'; export * from './supporthub.service'; export * from './ui-state.service'; - +export { LocaleService, AVAILABLE_LANGUAGES } from './locale.service'; +export type { Language } from './locale.service'; diff --git a/desktop/angular/src/app/services/locale.service.ts b/desktop/angular/src/app/services/locale.service.ts new file mode 100644 index 000000000..b9cf29880 --- /dev/null +++ b/desktop/angular/src/app/services/locale.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { BehaviorSubject } from 'rxjs'; + +export interface Language { + code: string; + name: string; + nativeName: string; +} + +export const AVAILABLE_LANGUAGES: Language[] = [ + { code: 'en', name: 'English', nativeName: 'English' }, + { code: 'ru', name: 'Russian', nativeName: 'Русский' } +]; + +@Injectable({ + providedIn: 'root' +}) +export class LocaleService { + private readonly currentLang$ = new BehaviorSubject('en'); + + readonly languages = AVAILABLE_LANGUAGES; + readonly currentLanguage$ = this.currentLang$.asObservable(); + + constructor(private readonly translate: TranslateService) { + // Set available languages + this.translate.addLangs(AVAILABLE_LANGUAGES.map(l => l.code)); + this.translate.setDefaultLang('en'); + + // Try to use browser language or stored preference + const storedLang = localStorage.getItem('portmaster-language'); + const browserLang = this.translate.getBrowserLang(); + + let langToUse = 'en'; + if (storedLang && this.isSupported(storedLang)) { + langToUse = storedLang; + } else if (browserLang && this.isSupported(browserLang)) { + langToUse = browserLang; + } + + this.setLanguage(langToUse); + } + + setLanguage(langCode: string): void { + if (this.isSupported(langCode)) { + this.translate.use(langCode); + this.currentLang$.next(langCode); + localStorage.setItem('portmaster-language', langCode); + document.documentElement.lang = langCode; + } + } + + getCurrentLanguage(): string { + return this.currentLang$.value; + } + + getLanguageByCode(code: string): Language | undefined { + return AVAILABLE_LANGUAGES.find(l => l.code === code); + } + + private isSupported(langCode: string): boolean { + return AVAILABLE_LANGUAGES.some(l => l.code === langCode); + } +} diff --git a/desktop/angular/src/app/services/localized-helptexts.service.ts b/desktop/angular/src/app/services/localized-helptexts.service.ts new file mode 100644 index 000000000..afb84cab4 --- /dev/null +++ b/desktop/angular/src/app/services/localized-helptexts.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { HelpTexts } from '@safing/ui'; +import { LocaleService } from '../services/locale.service'; + +// Import both language files +import helptextsEn from '../i18n/helptexts.yaml'; +import helptextsRu from '../i18n/helptexts.ru.yaml'; + +export interface LocalizedHelpTexts { + [lang: string]: HelpTexts; +} + +const HELPTEXTS: LocalizedHelpTexts = { + 'en': helptextsEn, + 'ru': helptextsRu +}; + +@Injectable({ + providedIn: 'root' +}) +export class LocalizedHelpTextsService { + constructor(private readonly localeService: LocaleService) {} + + getHelpTexts(): HelpTexts { + const lang = this.localeService.getCurrentLanguage(); + return HELPTEXTS[lang] || HELPTEXTS['en']; + } + + getHelpText(key: string): any { + const texts = this.getHelpTexts(); + return texts[key]; + } +} + +// Factory function for providing help texts based on current language +export function helpTextsFactory(localeService: LocaleService): HelpTexts { + const lang = localeService.getCurrentLanguage(); + return HELPTEXTS[lang] || HELPTEXTS['en']; +} diff --git a/desktop/angular/src/app/shared/config/config-settings.html b/desktop/angular/src/app/shared/config/config-settings.html index f6c9253eb..7c7c04c64 100644 --- a/desktop/angular/src/app/shared/config/config-settings.html +++ b/desktop/angular/src/app/shared/config/config-settings.html @@ -27,19 +27,19 @@
  • - Other + {{ 'common.other' | translate }}
    • - +
    • - +
    • - +
    diff --git a/desktop/angular/src/app/shared/config/config.module.ts b/desktop/angular/src/app/shared/config/config.module.ts index 127032af0..510261563 100644 --- a/desktop/angular/src/app/shared/config/config.module.ts +++ b/desktop/angular/src/app/shared/config/config.module.ts @@ -11,6 +11,7 @@ import { SfngTooltipModule, } from '@safing/ui'; import { MarkdownModule } from 'ngx-markdown'; +import { TranslateModule } from '@ngx-translate/core'; import { ExpertiseModule } from '../expertise/expertise.module'; import { SfngFocusModule } from '../focus'; import { SfngMenuModule } from '../menu'; @@ -47,7 +48,8 @@ import { SfngAppIconModule } from '../app-icon'; ExpertiseModule, SfngToggleSwitchModule, MarkdownModule, - SfngAppIconModule + SfngAppIconModule, + TranslateModule ], declarations: [ BasicSettingComponent, diff --git a/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.html b/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.html index da8a3cb15..0e6d80e52 100644 --- a/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.html +++ b/desktop/angular/src/app/shared/config/export-dialog/export-dialog.component.html @@ -1,6 +1,6 @@

    - {{ dialogRef.data.type === "setting" ? "Settings" : "Profile" }} Export + {{ dialogRef.data.type === "setting" ? ('dialogs.exportDialog.settingsExport' | translate) : ('dialogs.exportDialog.profileExport' | translate) }}

    - - -
    \ No newline at end of file + + +
    diff --git a/desktop/angular/src/app/shared/config/generic-setting/generic-setting.html b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.html index 7c2f9bd1b..3dc6201d9 100644 --- a/desktop/angular/src/app/shared/config/generic-setting/generic-setting.html +++ b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.html @@ -13,9 +13,9 @@
    -

    - +

    + clip-rule="evenodd" /> - Saved {{ _setting?.RequiresRestart ? ' - Restart required' : (uiReloadRequired ? ' - Reload required' : '') }} + {{ 'common.saved' | translate }} {{ _setting?.RequiresRestart ? (' - ' + ('common.restartRequired' | translate)) : (uiReloadRequired ? (' - ' + ('common.reloadRequired' | translate)) : '') }} - Invalid Value: {{ rejected }} + {{ 'common.invalidValue' | translate }}: {{ rejected }} *appExpertiseLevel="'developer'">{{setting?.Key}} Beta + *ngIf="setting?.ReleaseLevel === releaseLevel.Beta">{{ 'common.beta' | translate }} Experimental + *ngIf="setting?.ReleaseLevel === releaseLevel.Experimental">{{ 'common.experimental' | translate }} Advanced + *ngIf="setting?.ExpertiseLevel === expertise.expert">{{ 'common.advanced' | translate }} Developer + *ngIf="setting?.ExpertiseLevel === expertise.developer">{{ 'common.developer' | translate }}
    @@ -84,7 +84,7 @@

    - Quick Settings + {{ 'settings.quickSettings' | translate }} @@ -103,8 +103,8 @@

    (ngModelChange)="updateValue($event, true)" [symbolMap]="symbolMap">
    -

    This setting stacks on top of the following global setting:

    +

    {{ 'settings.stackedOnGlobal' | translate }} {{ 'settings.globalSetting' | translate }}:

    @@ -123,8 +123,8 @@

    This setting stacks on top of the following @@ -138,8 +138,8 @@

    This setting stacks on top of the following -

    This setting stacks on top of the following global setting:

    +

    {{ 'settings.stackedOnGlobal' | translate }} {{ 'settings.globalSettings' | translate }}:

    @@ -178,11 +178,11 @@

    This setting stacks on top of the following - Inherited from Global Settings + {{ 'settings.inheritedFromGlobal' | translate }} {{ 'settings.globalSettings' | translate }} - App specific configuration + {{ 'settings.appSpecificConfig' | translate }} diff --git a/desktop/angular/src/app/shared/config/generic-setting/generic-setting.ts b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.ts index 4ff5a7f38..02bf10424 100644 --- a/desktop/angular/src/app/shared/config/generic-setting/generic-setting.ts +++ b/desktop/angular/src/app/shared/config/generic-setting/generic-setting.ts @@ -8,6 +8,7 @@ import { SfngDialogRef, SfngDialogService } from '@safing/ui'; import { Button } from 'js-yaml-loader!../../../i18n/helptexts.yaml'; import { Subject } from 'rxjs'; import { debounceTime, tap } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; import { ActionIndicatorService } from '../../action-indicator'; import { fadeInAnimation, fadeOutAnimation } from '../../animations'; import { ExpertiseService } from '../../expertise/expertise.service'; @@ -453,6 +454,27 @@ export class GenericSettingComponent> implements return this.expertiseService.change; } + /** + * Returns translated name for the setting, falling back to original if not found. + */ + get translatedName(): string { + if (!this.setting?.Key) return ''; + const key = `config.${this.setting.Key}.name`; + const translated = this.translate.instant(key); + // If translation returns the key itself, fallback to original + return (translated === key) ? (this.setting?.Name || '') : translated; + } + + /** + * Returns translated description for the setting, falling back to original if not found. + */ + get translatedDescription(): string { + if (!this.setting?.Key) return ''; + const key = `config.${this.setting.Key}.description`; + const translated = this.translate.instant(key); + return (translated === key) ? (this.setting?.Description || '') : translated; + } + constructor( private expertiseService: ExpertiseService, private configService: ConfigService, @@ -463,6 +485,7 @@ export class GenericSettingComponent> implements private spn: SPNService, private viewRef: ViewContainerRef, private destryoRef: DestroyRef, + private translate: TranslateService, ) { } ngOnInit() { diff --git a/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.html b/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.html index c55709d4a..0a04d16fb 100644 --- a/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.html +++ b/desktop/angular/src/app/shared/config/import-dialog/import-dialog.component.html @@ -1,6 +1,6 @@

    - Import {{ dialogRef.data.type === "setting" ? "Settings" : "Profile" }} + {{ dialogRef.data.type === "setting" ? ('dialogs.importDialog.importSettings' | translate) : ('dialogs.importDialog.importProfile' | translate) }}

    -Please paste the "Export Content" or use "Choose File" to select one from - your hard disk. +{{ 'dialogs.importDialog.pasteContent' | translate }}
    
     
     
    - Configuration + {{ 'dialogs.importDialog.configuration' | translate }}
    - +
    - +
    - +
    - +
    @@ -51,7 +50,7 @@

    result.replacesExisting || result.restartRequired)) "> - Warning + {{ 'common.warning' | translate }}
    • - This export contains unknown settings. To import it, you must enable - "Allow unknown settings". + {{ 'dialogs.importDialog.warningUnknown' | translate }}
    • {{ dialogRef.data.type === "setting" - ? "This export will overwrite settings that have been changed by you." - : "This export will overwrite an existing profile." + ? ('dialogs.importDialog.warningOverwriteSettings' | translate) + : ('dialogs.importDialog.warningOverwriteProfile' | translate) }} - And deletes {{ count }} previously merged profile{{ count > 1 ? 's' : '' }} + {{ 'dialogs.importDialog.deleteMergedProfiles' | translate:{ count: count } }}
    • - This export will require a restart of the Portmaster to take effect. + {{ 'dialogs.importDialog.warningRestart' | translate }}
    - \ No newline at end of file + diff --git a/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.html b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.html index fa043cb55..f674c015f 100644 --- a/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.html +++ b/desktop/angular/src/app/shared/config/ordererd-list/ordered-list.html @@ -18,6 +18,6 @@ - Add + {{ 'common.add' | translate }}
    diff --git a/desktop/angular/src/app/shared/config/rule-list/rule-list.html b/desktop/angular/src/app/shared/config/rule-list/rule-list.html index 3f7115c31..0300c37d0 100644 --- a/desktop/angular/src/app/shared/config/rule-list/rule-list.html +++ b/desktop/angular/src/app/shared/config/rule-list/rule-list.html @@ -14,7 +14,7 @@
    - No entries available + {{ 'rules.noEntries' | translate }}
    @@ -40,7 +40,7 @@ - Remove Rules - Cancel + {{ 'rules.removeRules' | translate }} + {{ 'common.cancel' | translate }} diff --git a/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.html b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.html index 7b6306214..417e5ddec 100644 --- a/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.html +++ b/desktop/angular/src/app/shared/edit-profile-dialog/edit-profile-dialog.html @@ -5,16 +5,15 @@

    class="w-8 h-8 rounded-full border border-gray-400" /> - {{ isEditMode ? 'Edit App Profile' : 'Create New App Profile' }} + {{ isEditMode ? ('dialogs.editAppProfile' | translate) : ('dialogs.createNewAppProfile' | translate) }}

    - +
    - Configure basic profile information like the profile name, it's - description and optionally the profile icon. + {{ 'dialogs.configureBasicInfo' | translate }}
    @@ -24,53 +23,52 @@

    name="id" pattern="[a-zA-Z0-9\-\._]*" [(ngModel)]="profile.ID" - placeholder="A unique identifier for profile. Leave empty to generate a random one." + [placeholder]="'dialogs.edit.idPlaceholder' | translate" />

    - +
    - +
    - +
    The icon must be smaller than 10kB and it's dimensions must not - exceed 512x512 px. Only JPG and PNG files are supported.{{ 'dialogs.iconRequirements' | translate }}
    - +
    This profile will be applied to processes that match one of the - following fingerprints:{{ 'dialogs.fingerprintDescription' | translate }}
    - No fingerprints configured. Please press "Add New" to get started. + {{ 'dialogs.noFingerprints' | translate }}
    [ngModelOptions]="{standalone: true}" > - Tag + {{ 'dialogs.edit.tag' | translate }} Command Line + *sfngSelectValue="fingerPrintTypes.Cmdline; label: ('dialogs.edit.commandLine' | translate)" + >{{ 'dialogs.edit.commandLine' | translate }} Environment + *sfngSelectValue="fingerPrintTypes.Env; label: ('dialogs.edit.environment' | translate)" + >{{ 'dialogs.edit.environment' | translate }} Path{{ 'dialogs.edit.path' | translate }} @@ -172,20 +169,20 @@

    [ngModelOptions]="{standalone: true}" > Equals{{ 'dialogs.edit.equals' | translate }} Prefix{{ 'dialogs.edit.prefix' | translate }} Regex{{ 'dialogs.edit.regex' | translate }} @@ -211,15 +208,15 @@

    - +
    - +
    Select a Profile to copy settings from:{{ 'dialogs.edit.selectProfileToCopy' | translate }}
    [ngModelOptions]="{standalone: true}" class="flex-grow" [allowSearch]="true" - searchPlaceholder="Search Profiles" + [searchPlaceholder]="'dialogs.edit.searchProfiles' | translate" > [disabled]="selectedCopyFrom === null" (click)="addCopyFrom()" > - Add + {{ 'dialogs.edit.add' | translate }}
    @@ -297,9 +294,7 @@

    - Settings will be copied from all specified profiles in order with - settings from higher profiles taking precedence.
    - Existing settings may be overwritten. + {{ 'dialogs.edit.copySettingsInfo' | translate }}
    @@ -308,15 +303,15 @@

    - +
    diff --git a/desktop/angular/src/app/shared/exit-screen/exit-screen.html b/desktop/angular/src/app/shared/exit-screen/exit-screen.html index cff2a9605..83e14aabe 100644 --- a/desktop/angular/src/app/shared/exit-screen/exit-screen.html +++ b/desktop/angular/src/app/shared/exit-screen/exit-screen.html @@ -1,19 +1,16 @@
    -

    + -

    Close User Interface

    +

    {{ 'dialogs.closeUI' | translate }}

    - Closing the User Interface does not shut down the Portmaster. You can shut down the Portmaster - in the Settings or the Tray Notifier. + {{ 'dialogs.closeUIMessage' | translate }}
    - + - +
    diff --git a/desktop/angular/src/app/shared/feature-scout/feature-scout.html b/desktop/angular/src/app/shared/feature-scout/feature-scout.html index f0ee7af71..688bf7b10 100644 --- a/desktop/angular/src/app/shared/feature-scout/feature-scout.html +++ b/desktop/angular/src/app/shared/feature-scout/feature-scout.html @@ -32,16 +32,16 @@ - SPN is connecting...
    - Fail-safe blocking enabled + {{ 'spn.connecting' | translate }}...
    + {{ 'spn.failsafeBlocking' | translate }}
    - SPN failed to connect
    - Fail-safe blocking enabled + {{ 'spn.failedToConnect' | translate }}
    + {{ 'spn.failsafeBlocking' | translate }}
    - SPN is connecting...
    - Fail-safe blocking enabled + {{ 'spn.connecting' | translate }}...
    + {{ 'spn.failsafeBlocking' | translate }}
    - Disabled + {{ 'spn.disabled' | translate }} - Failed to connect
    - Fail-safe blocking enabled + {{ 'spn.failedToConnect' | translate }}
    + {{ 'spn.failsafeBlocking' | translate }}
    - Connecting...
    - Fail-safe blocking enabled + {{ 'spn.connecting' | translate }}...
    + {{ 'spn.failsafeBlocking' | translate }}
    - You're protected + {{ 'spn.youreProtected' | translate }}

    @@ -97,10 +97,10 @@

    - SPN Home (Entry) Node + {{ 'spn.homeEntryNode' | translate }}
      -
    • Connected to {{ spnStatus?.ConnectedIP }}
    • -
    • Uplink is always encrypted
    • -
    • Built with transport/decoy {{ spnStatus?.ConnectedTransport }}
    • +
    • {{ 'spn.connectedTo' | translate }} {{ spnStatus?.ConnectedIP }}
    • +
    • {{ 'spn.uplinkEncrypted' | translate }}
    • +
    • {{ 'spn.builtWithTransport' | translate }} {{ spnStatus?.ConnectedTransport }}
    diff --git a/desktop/angular/src/app/shared/language-selector/language-selector.component.ts b/desktop/angular/src/app/shared/language-selector/language-selector.component.ts new file mode 100644 index 000000000..7f312843d --- /dev/null +++ b/desktop/angular/src/app/shared/language-selector/language-selector.component.ts @@ -0,0 +1,57 @@ +import { Component, OnDestroy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Subject, takeUntil } from 'rxjs'; +import { LocaleService, AVAILABLE_LANGUAGES } from '../../services'; + +@Component({ + selector: 'app-language-selector', + standalone: true, + imports: [CommonModule], + template: ` +
    + +
    + `, + styles: [` + .language-selector select { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%239ca3af' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + padding-right: 1.75rem; + } + `] +}) +export class LanguageSelectorComponent implements OnDestroy { + languages = AVAILABLE_LANGUAGES; + currentLang: string; + private readonly destroy$ = new Subject(); + + constructor(private readonly localeService: LocaleService) { + this.currentLang = this.localeService.getCurrentLanguage(); + + this.localeService.currentLanguage$ + .pipe(takeUntil(this.destroy$)) + .subscribe(lang => { + this.currentLang = lang; + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + onLanguageChange(event: Event): void { + const select = event.target as HTMLSelectElement; + this.localeService.setLanguage(select.value); + } +} diff --git a/desktop/angular/src/app/shared/netquery/connection-details/conn-details.html b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.html index 73abd31f8..7fff5977a 100644 --- a/desktop/angular/src/app/shared/netquery/connection-details/conn-details.html +++ b/desktop/angular/src/app/shared/netquery/connection-details/conn-details.html @@ -162,12 +162,12 @@ Scope: - Internet Peer-to-Peer - Internet Multicast - Device-Local - LAN Peer-to-Peer - LAN Multicast - LAN Peer-to-Peer + {{ 'monitor.internetPeerToPeer' | translate }} + {{ 'monitor.internetMulticast' | translate }} + {{ 'monitor.deviceLocal' | translate }} + {{ 'monitor.lanPeerToPeer' | translate }} + {{ 'monitor.lanMulticast' | translate }} + {{ 'monitor.lanPeerToPeer' | translate }} N/A N/A @@ -222,7 +222,7 @@
    -

    SPN Tunnel

    +

    {{ 'monitor.spnTunnel' | translate }}

    This connection has not been routed through the Safing Privacy Network. @@ -256,7 +256,7 @@

    SPN Tunnel

    -

    Data Usage

    +

    {{ 'monitor.dataUsage' | translate }}

    Data Usage

    - +
    diff --git a/desktop/angular/src/app/shared/netquery/netquery.component.html b/desktop/angular/src/app/shared/netquery/netquery.component.html index dac7211c2..83f38ee7c 100644 --- a/desktop/angular/src/app/shared/netquery/netquery.component.html +++ b/desktop/angular/src/app/shared/netquery/netquery.component.html @@ -6,7 +6,7 @@
    + (click)="copyQuery()" [sfng-tooltip]="'monitor.copyQueryToClipboard' | translate" snfgTooltipPosition="left">
    - +
    @@ -38,7 +38,7 @@ (ngModelChange)="performSearch()" (opened)="loadSuggestion(field.key)" [dynamicValueTemplate]="dynamicValueTemplate" [sortBy]="sortByCount"> - Loading ... + {{ 'common.loading' | translate }} @@ -78,7 +78,7 @@
    +
    diff --git a/desktop/angular/src/app/shared/notification/notification.html b/desktop/angular/src/app/shared/notification/notification.html index c3d7bcf65..04fb356ec 100644 --- a/desktop/angular/src/app/shared/notification/notification.html +++ b/desktop/angular/src/app/shared/notification/notification.html @@ -1,8 +1,8 @@
    -
    + diff --git a/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.html b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.html index 85cfa1bb0..8e4d67228 100644 --- a/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.html +++ b/desktop/angular/src/app/shared/process-details-dialog/process-details-dialog.html @@ -1,14 +1,14 @@

    - Process Details + {{ 'dialogs.processDetails.title' | translate }}

    - +
    NameOperatorUsed AsLatencyCapacity{{ 'spn.name' | translate }}{{ 'spn.operator' | translate }}{{ 'spn.usedAs' | translate }}{{ 'spn.latency' | translate }}{{ 'spn.capacity' | translate }} IPv4 IPv6 - Most VPNs + {{ 'spn.carousel.mostVpns' | translate }}
    - Read Comparison Blog + {{ 'spn.carousel.readComparisonBlog' | translate }}
    SPN
    Multiple Identities (simultaneous){{ 'spn.carousel.multipleIdentitiesSimultaneous' | translate }}
    Individual Apps Settings{{ 'spn.carousel.individualAppsSettings' | translate }}
    Easy Setup{{ 'spn.carousel.easySetup' | translate }} Browser Only{{ 'spn.carousel.browserOnly' | translate }}
    Availabilty{{ 'spn.carousel.availability' | translate }}
    @@ -230,7 +222,7 @@

    Bye Bye, VPNs

    Open Source{{ 'spn.carousel.openSource' | translate }}
    Built for Privacy{{ 'spn.carousel.builtForPrivacy' | translate }}
    Status{{ 'dialogs.progress.status' | translate }} Tip{{ 'common.tip' | translate }} Notifications{{ 'notifications.title' | translate }} Notification{{ 'notifications.notification' | translate }} - Broadcast Notification + {{ 'notifications.broadcastNotification' | translate }}
    - + - + - + - + - + - + - + - +
    Name{{ 'dialogs.processDetails.name' | translate }}
    @@ -18,35 +18,35 @@

    User{{ 'dialogs.processDetails.user' | translate }} {{ process.UserName }} ({{ process.UserID }})
    Process ID{{ 'dialogs.processDetails.processId' | translate }} {{ process.Pid }}
    Process Group ID{{ 'dialogs.processDetails.processGroupId' | translate }} {{ process.Pgid }}
    Parent Process ID{{ 'dialogs.processDetails.parentProcessId' | translate }} {{ process.ParentPid }}
    Path{{ 'dialogs.processDetails.path' | translate }} {{ process.Path }} ({{ process.MatchingPath }})
    Executable Name{{ 'dialogs.processDetails.executableName' | translate }} {{ process.ExecName }}
    Command Line{{ 'dialogs.processDetails.commandLine' | translate }} {{ process.CmdLine }} - This process does not have any tags. + {{ 'dialogs.processDetails.noTags' | translate }}
    • @@ -98,10 +98,10 @@

      - +
      - This process does not have any environment variables. + {{ 'dialogs.processDetails.noEnvVars' | translate }}
      @@ -111,7 +111,7 @@

      - + - + - + - + - + - + - + - + - + - + - + - + @@ -76,18 +76,18 @@

      Account Details

      - +
      - Open Account Page + {{ 'common.openAccountPage' | translate }} - +
      - Safing Account Login + {{ 'account.login.title' | translate }} - Unlock powerful features. + {{ 'account.login.unlockFeatures' | translate }}
      - You have been logged out by the account server. + {{ 'account.login.forcedLogout' | translate }}
      - Please check your account. + {{ 'account.login.checkAccount' | translate }} {{ 'account.login.yourAccount' | translate }}.
      - +
      - +
      @@ -58,11 +58,10 @@

      diff --git a/desktop/angular/src/app/shared/spn-network-status/spn-network-status.html b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.html index 83196071c..dce72613d 100644 --- a/desktop/angular/src/app/shared/spn-network-status/spn-network-status.html +++ b/desktop/angular/src/app/shared/spn-network-status/spn-network-status.html @@ -1,17 +1,17 @@
      -

      Network Status

      +

      {{ 'spn.networkStatus.title' | translate }}

      - Loading Network Status ... + {{ 'spn.networkStatus.loading' | translate }}
      • {{ issue.title }} - {{ issue.closed ? 'closed' : 'opened'}} by {{ issue.user }} + {{ issue.closed ? ('spn.networkStatus.closed' | translate) : ('spn.networkStatus.opened' | translate)}} {{ 'spn.networkStatus.by' | translate }} {{ issue.user }} {{ issue.createdAt | timeAgo }} @@ -20,7 +20,7 @@

        Network Status

      • diff --git a/desktop/angular/src/app/shared/spn-status/spn-status.html b/desktop/angular/src/app/shared/spn-status/spn-status.html index 84006d465..33d0d1adf 100644 --- a/desktop/angular/src/app/shared/spn-status/spn-status.html +++ b/desktop/angular/src/app/shared/spn-status/spn-status.html @@ -17,16 +17,16 @@

        - Increase privacy protection + {{ 'spn.increaseProtection' | translate }} - Failed to connect + {{ 'spn.failedToConnect' | translate }} - Connecting to the SPN ... + {{ 'spn.connectingToSPN' | translate }} - You're protected + {{ 'spn.youreProtected' | translate }} @@ -41,13 +41,13 @@

      - Identities + {{ 'spn.identities' | translate }} {{ identities }}
      diff --git a/desktop/angular/src/assets b/desktop/angular/src/assets deleted file mode 120000 index ec2e4be2f..000000000 --- a/desktop/angular/src/assets +++ /dev/null @@ -1 +0,0 @@ -../assets \ No newline at end of file diff --git a/desktop/angular/src/i18n/helptexts.ru.yaml b/desktop/angular/src/i18n/helptexts.ru.yaml new file mode 100644 index 000000000..d551cf232 --- /dev/null +++ b/desktop/angular/src/i18n/helptexts.ru.yaml @@ -0,0 +1,331 @@ +########### +### Пример + +myKey: + title: Пример подсказки + content: | + Это контент в формате Markdown. + + Это супер крутая новая фича, которая вам понравится! + Она даже поддерживает Markdown: + - упорядоченные списки + - с несколькими элементами + + И :rocket: эмодзи + + ### :tada: :facepalm: + url: https://docs.safing.io/?source=Portmaster + urlText: Покажи! + nextKey: navMonitor + +############## +### Навигация + +introTipup: + title: Привет! + content: | + Спасибо за установку Portmaster. + +intro: + title: Подсказки Portmaster + content: | + Откройте подсказки, чтобы узнать как работает Portmaster. + + Такие подсказки можно найти по всему Portmaster. С некоторыми подсказками вы можете пройти тур по элементу или функции: + nextKey: navShield + +navShield: + title: Щит статуса и Панель управления + content: | + Щит даёт общий обзор состояния Portmaster. Если он станет любого цвета кроме зелёного, ищите уведомление, которое объяснит что происходит. + + __Нажмите на щит, чтобы открыть панель управления.__ + nextKey: navMonitor + +navMonitor: + title: Монитор сети + content: | + Наблюдайте и исследуйте всё, что происходит на вашем устройстве. + nextKey: navApps + buttons: + - name: Пройти тур + action: + Type: open-page + Payload: monitor + nextKey: networkMonitor + +navApps: + title: Настройки приложений + content: | + Настройте параметры для каждого приложения, которые переопределяют глобальные настройки. + nextKey: navMap + buttons: + - name: Пройти тур + action: + Type: open-page + Payload: apps + nextKey: appsTitle + +navMap: + title: Карта SPN + content: | + Просматривайте карту SPN и видите как маршрутизируются ваши соединения. + nextKey: navSettings + +navSettings: + title: Глобальные настройки + content: | + Настройте глобальные параметры Portmaster. + nextKey: navSupport + buttons: + - name: Пройти тур + action: + Type: open-page + Payload: settings + nextKey: globalSettings + +navSupport: + title: Помощь + content: | + Сообщите об ошибке, свяжитесь с поддержкой или просмотрите документацию Portmaster. + nextKey: navTools + buttons: + - name: Открыть страницу + action: + Type: open-page + Payload: support + +navTools: + title: Версия и инструменты + content: | + Просмотрите версию Portmaster и используйте специальные действия и инструменты. + nextKey: navPause + +navPause: + title: Пауза и возобновление + content: | + Временно отключите защиту и мониторинг сети Portmaster. + + Выберите приостановку только SPN или отключите всю защиту полностью. + nextKey: navPower + +navPower: + title: Выключение и перезапуск + content: | + Выключите или перезапустите Portmaster. + nextKey: uiMode + +uiMode: + title: Режим интерфейса + content: | + Быстро изменяйте количество отображаемых настроек и информации. + + Скрытые настройки всё ещё действуют. После закрытия интерфейса режим возвращается к значению по умолчанию. + buttons: + - name: Изменить режим по умолчанию + action: + Type: "open-setting" + Payload: + Key: "core/expertiseLevel" + +############ +### Боковая панель + +pilot-widget: + title: Статус Portmaster + content: | + Этот щит показывает текущее состояние Portmaster: + + - 🟢 всё в порядке + - 🟡 что-то не так, требуется внимание + - 🔴 опасное состояние, требуется немедленная реакция + + Этот цветовой код также отображается в иконке в системном трее. + +pilot-widget-NetworkRating: + title: Рейтинг сети + content: | + Контролируйте свою конфиденциальность даже при подключении к новым сетям. + + В Portmaster вы настраиваете параметры, которые активны в одной среде, но не в другой — например, разрешаете конфиденциальные соединения дома, но не в публичной библиотеке. + + Всё, что вам нужно делать — это менять рейтинг сети при подключении к другой сети. + nextKey: pilot-widget-NetworkRating-Trusted + +pilot-widget-NetworkRating-Trusted: + title: "Рейтинг сети: Доверенная" + content: | + Вы доверяете текущей сети и считаете её безопасной. + + Примеры: + - ваша домашняя сеть + - сеть доверенных друзей + nextKey: pilot-widget-NetworkRating-Untrusted + +pilot-widget-NetworkRating-Untrusted: + title: "Рейтинг сети: Недоверенная" + content: | + Вы не доверяете текущей сети и сомневаетесь в её безопасности и конфиденциальности. + + Примеры: + - публичный WiFi в кафе, библиотеке, поезде, отеле... + - сеть родственника, не разбирающегося в технологиях + nextKey: pilot-widget-NetworkRating-Danger + +pilot-widget-NetworkRating-Danger: + title: "Рейтинг сети: Опасная" + content: | + Вы считаете, что текущая сеть взломана или иным образом враждебна к вам. + + Примеры: + - что-то подозрительное происходит в вашей домашней сети + + _Примечание: При рейтинге "Опасная" Portmaster становится очень защитным. Это может нарушить работу приложений или сделать их бесполезными._ + +broadcast-info: + title: Широковещательные уведомления + content: | + Широковещательные уведомления — это публичные сообщения, загружаемые Portmaster при проверке обновлений. + + Затем Portmaster локально решает, какие сообщения следует отображать. + url: https://github.com/safing/portmaster/issues/703 + urlText: Узнать больше + +############# +### Панель управления + +dashboardIntro: + title: Панель управления + content: | + Панель управления даёт первый обзор активных функций Portmaster и того, что происходит на вашем устройстве. + + Если не указано иное, все графики и статистика основаны на данных за последние 10 минут и обновляются каждые 10 секунд. + +######################## +### Страница монитора сети + +networkMonitor: + title: Сетевая активность + content: | + Наблюдайте за всем, что происходит на вашем устройстве. + + Смотрите все сетевые соединения всех приложений и процессов, которые были активны за последние 10 минут. Нажмите на любое приложение или процесс для детального изучения. + +networkMonitor-App-Focus-connection-history: + title: Сетевая активность + content: | + Отслеживайте соединения по мере их появления. Нажмите на любое соединение, чтобы просмотреть детали и предпринять действия. +

      + + + 2k+ + + + + + Сводка статуса +

      + Сгруппированные соединения имеют цветную полосу, показывающую общее количество соединений и процентное соотношение между разрешёнными (зелёный) и заблокированными/неудачными (серый). +

      + Отдельное соединение имеет три состояния:
      + Разрешено
      + Заблокировано
      + Ошибка
      + + Если кружок заполнен, ваши _текущие_ настройки разрешили или заблокировали соединение.
      + Если кружок пустой, _предыдущие_ настройки разрешили или заблокировали соединение. + Ваши текущие настройки могут решить иначе. + +######################## +### Страница глобальных настроек + +globalSettings: + title: Глобальные настройки + content: | + Здесь вы можете установить общесистемные предпочтения и настроить правила по умолчанию для всех приложений и соединений. + + Легко создать более строгий глобальный набор правил, а затем создать исключения в настройках приложения, которые переопределяют глобальные значения по умолчанию. + +######################### +### Страница настроек приложений + +appsTitle: + title: Обзор приложений + content: | + Все приложения или процессы, которые Portmaster видел активными в сети, перечислены здесь и могут быть настроены. + + Приложения категоризированы и появляются только один раз: + + - **Активные:** приложения, которые сейчас активны и видны в Мониторе сети + - **Недавно использованные:** приложения, которые были активны в течение последней недели + - **Недавно изменённые:** приложения, настройки которых изменялись в течение последней недели + - **Другие:** все остальные приложения + +appSettings: + title: Настройки приложения + content: | + Здесь вы можете настроить параметры конкретного приложения, которые переопределяют глобальные настройки. + + Легко создать более строгий глобальный набор правил, а затем создать исключения в настройках приложения. + nextKey: appSettings-Filter + +appSettings-Filter: + title: Режим отображения + content: | + Быстро меняйте, какие настройки отображаются: + + **Показать активные:**
      + Показывать только настройки приложения, которые переопределяют глобальные значения. + + **Показать все:**
      + Показать все настройки. Настройки приложения, которые переопределяют глобальные значения, выделены. + +appSettings-QuickSettings: + title: Быстрое изменение важных настроек + content: | + __Блокировать соединения__ + + Установите действие по умолчанию, когда ничто другое не разрешает и не блокирует исходящее соединение. + + Когда другие настройки могут переопределить это, жёлтая точка рядом с переключателем сообщит о возможных исключениях. + + __SPN__ + + Быстро включите или отключите SPN для этого приложения. + + Когда другие настройки могут переопределить это, жёлтая точка рядом с переключателем сообщит о возможных исключениях. + + __Сохранять историю__ + + Сохраняйте соединения в базе данных (на диске), чтобы просматривать и искать их позже. + + Изменения могут занять несколько минут для применения ко всем соединениям. + +######################### +### Страница поддержки + +support-page-related-issues: + title: Локальный поиск проблем + content: | + Публичные проблемы ищутся только локально, поэтому никакие данные не покидают ваше устройство, пока вы не решите иначе. + + Публичные GitHub issues загружаются через нашу систему поддержки, чтобы предотвратить раскрытие данных GitHub. + +######################### +### Параметры конфигурации + +spn: + title: Сеть конфиденциальности Safing + content: | + Сеть конфиденциальности Safing (SPN) — это дополнение к Portmaster, которое защищает вашу личность + и интернет-трафик от посторонних глаз. Она распределяет ваши соединения по нескольким серверам, + позволяя вам выходить в интернет из множества мест одновременно, чтобы эффективно скрыть + ваши следы. + url: https://safing.io/spn/?source=Portmaster + urlText: Узнать больше + +########################### +# Сопоставление процессов и отпечатки +process-tags: + title: Теги процессов + content: Теги содержат специальные метаданные процессов и собираются Portmaster. Вы можете использовать эти теги в отпечатках для лучшего сопоставления процессов, которые иначе было бы сложно или невозможно правильно идентифицировать. diff --git a/desktop/angular/src/i18n/helptexts.ru.yaml.d.ts b/desktop/angular/src/i18n/helptexts.ru.yaml.d.ts new file mode 100644 index 000000000..81f4fe21b --- /dev/null +++ b/desktop/angular/src/i18n/helptexts.ru.yaml.d.ts @@ -0,0 +1,15 @@ +declare const helptexts: { + [key: string]: { + title: string; + content: string; + url?: string; + urlText?: string; + nextKey?: string; + buttons?: Array<{ + name: string; + action: any; + nextKey?: string; + }>; + }; +}; +export default helptexts; diff --git a/service/firewall/config.go b/service/firewall/config.go index 34cbe4e83..61e3f3a71 100644 --- a/service/firewall/config.go +++ b/service/firewall/config.go @@ -34,15 +34,15 @@ var ( func registerConfig() error { err := config.Register(&config.Option{ - Name: "Enable Privacy Filter", + Name: "Включить Фильтр Приватности", Key: CfgOptionEnableFilterKey, - Description: "Enable the Privacy Filter. If turned off, all privacy filter protections are fully disabled on this device. Not meant to be disabled in production - only turn off for testing.", + Description: "Включить фильтр приватности. При отключении все защитные функции фильтра полностью отключаются на этом устройстве. Не предназначено для отключения в продакшене - отключайте только для тестирования.", OptType: config.OptTypeBool, ExpertiseLevel: config.ExpertiseLevelDeveloper, ReleaseLevel: config.ReleaseLevelExperimental, DefaultValue: true, Annotations: config.Annotations{ - config.CategoryAnnotation: "General", + config.CategoryAnnotation: "Основные", }, }) if err != nil { @@ -51,16 +51,16 @@ func registerConfig() error { filterEnabled = config.Concurrent.GetAsBool(CfgOptionEnableFilterKey, true) err = config.Register(&config.Option{ - Name: "Permanent Verdicts", + Name: "Постоянные Решения", Key: CfgOptionPermanentVerdictsKey, - Description: "The Portmaster's system integration intercepts every single packet. Usually the first packet is enough for the Portmaster to set the verdict for a connection - ie. to allow or deny it. Making these verdicts permanent means that the Portmaster will tell the system integration that is does not want to see any more packets of that single connection. This brings a major performance increase.", + Description: "Системная интеграция Portmaster перехватывает каждый пакет. Обычно первого пакета достаточно для принятия решения о соединении - разрешить или запретить. Постоянные решения означают, что Portmaster сообщит системе, что больше не хочет видеть пакеты этого соединения. Это значительно повышает производительность.", OptType: config.OptTypeBool, ExpertiseLevel: config.ExpertiseLevelDeveloper, ReleaseLevel: config.ReleaseLevelExperimental, DefaultValue: true, Annotations: config.Annotations{ config.DisplayOrderAnnotation: cfgOptionPermanentVerdictsOrder, - config.CategoryAnnotation: "Advanced", + config.CategoryAnnotation: "Расширенные", }, }) if err != nil { @@ -69,16 +69,16 @@ func registerConfig() error { permanentVerdicts = config.Concurrent.GetAsBool(CfgOptionPermanentVerdictsKey, true) err = config.Register(&config.Option{ - Name: "Seamless DNS Integration", + Name: "Бесшовная DNS Интеграция", Key: CfgOptionDNSQueryInterceptionKey, - Description: "Intercept and redirect astray DNS queries to the Portmaster's internal DNS server. This enables seamless DNS integration without having to configure the system or other software. However, this may lead to compatibility issues with other software that attempts the same.", + Description: "Перехватывать и перенаправлять DNS-запросы на внутренний DNS-сервер Portmaster. Это обеспечивает бесшовную интеграцию DNS без необходимости настройки системы или другого ПО. Однако это может вызвать проблемы совместимости с другими программами, которые делают то же самое.", OptType: config.OptTypeBool, ExpertiseLevel: config.ExpertiseLevelDeveloper, ReleaseLevel: config.ReleaseLevelExperimental, DefaultValue: true, Annotations: config.Annotations{ config.DisplayOrderAnnotation: cfgOptionDNSQueryInterceptionOrder, - config.CategoryAnnotation: "Advanced", + config.CategoryAnnotation: "Расширенные", }, }) if err != nil { @@ -87,15 +87,15 @@ func registerConfig() error { dnsQueryInterception = config.Concurrent.GetAsBool(CfgOptionDNSQueryInterceptionKey, true) err = config.Register(&config.Option{ - Name: "Prompt Desktop Notifications", + Name: "Уведомления на Рабочий Стол", Key: CfgOptionAskWithSystemNotificationsKey, - Description: `In addition to showing prompt notifications in the Portmaster App, also send them to the Desktop. This requires the Portmaster Notifier to be running. Requires Desktop Notifications to be enabled.`, + Description: `Помимо показа уведомлений в приложении Portmaster, также отправлять их на рабочий стол. Требуется запущенный Portmaster Notifier. Должны быть включены уведомления на рабочий стол.`, OptType: config.OptTypeBool, ExpertiseLevel: config.ExpertiseLevelUser, DefaultValue: true, Annotations: config.Annotations{ config.DisplayOrderAnnotation: cfgOptionAskWithSystemNotificationsOrder, - config.CategoryAnnotation: "General", + config.CategoryAnnotation: "Основные", config.RequiresAnnotation: config.ValueRequirement{ Key: notifications.CfgUseSystemNotificationsKey, Value: true, @@ -108,16 +108,16 @@ func registerConfig() error { askWithSystemNotifications = config.Concurrent.GetAsBool(CfgOptionAskWithSystemNotificationsKey, true) err = config.Register(&config.Option{ - Name: "Prompt Timeout", + Name: "Таймаут Уведомлений", Key: CfgOptionAskTimeoutKey, - Description: "How long the Portmaster will wait for a reply to a prompt notification. Please note that Desktop Notifications might not respect this or have their own limits.", + Description: "Как долго Portmaster будет ждать ответа на уведомление. Обратите внимание, что уведомления на рабочем столе могут не учитывать это или иметь собственные ограничения.", OptType: config.OptTypeInt, ExpertiseLevel: config.ExpertiseLevelUser, DefaultValue: 60, Annotations: config.Annotations{ config.DisplayOrderAnnotation: cfgOptionAskTimeoutOrder, - config.UnitAnnotation: "seconds", - config.CategoryAnnotation: "General", + config.UnitAnnotation: "секунд", + config.CategoryAnnotation: "Основные", }, ValidationRegex: `^[1-9][0-9]{1,5}$`, })
      {{ env.value }} + \ No newline at end of file diff --git a/desktop/angular/src/app/shared/prompt-list/prompt-list.component.html b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.html index 4d32a21e3..44ab2b4c9 100644 --- a/desktop/angular/src/app/shared/prompt-list/prompt-list.component.html +++ b/desktop/angular/src/app/shared/prompt-list/prompt-list.component.html @@ -6,19 +6,19 @@ {{ profile.prompts.length }} - Per Connection - Allow All - Block All + {{ 'prompts.perConnection' | translate }} + {{ 'prompts.allowAll' | translate }} + {{ 'prompts.blockAll' | translate }} - Default Action - Allow App - Block App + {{ 'prompts.defaultAction' | translate }} + {{ 'prompts.allowApp' | translate }} + {{ 'prompts.blockApp' | translate }} Change Default + [queryParams]="{setting: 'filter/defaultAction', tab: 'settings'}">{{ 'prompts.changeDefault' | translate }} - App Settings + {{ 'apps.appSettings' | translate }} @@ -39,18 +39,18 @@ - - + +
      {{ profile.prompts.length - profile.promptsLimited.length }} - more + {{ 'prompts.more' | translate }}
      - Show less + {{ 'prompts.showLess' | translate }}
      @@ -63,6 +63,6 @@ d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> - No Prompts + {{ 'prompts.noPrompts' | translate }} \ No newline at end of file diff --git a/desktop/angular/src/app/shared/spn-account-details/spn-account-details.html b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.html index 3b5940579..f58395dfa 100644 --- a/desktop/angular/src/app/shared/spn-account-details/spn-account-details.html +++ b/desktop/angular/src/app/shared/spn-account-details/spn-account-details.html @@ -1,5 +1,5 @@ -

      Account Details

      +

      {{ 'account.details' | translate }}

      Your Package{{ 'account.yourPackage' | translate }} {{ currentUser.current_plan?.name }}
      Access Until{{ 'account.accessUntil' | translate }} {{ currentUser.subscription.ends_at | date:'medium' }}
      Your Subscription{{ 'account.yourSubscription' | translate }} {{ currentUser.current_plan?.name }}
      Status{{ 'common.status' | translate }} {{ currentUser.subscription.state }}
      Next Payment Date{{ 'account.nextPaymentDate' | translate }} {{ currentUser.subscription.next_billing_date | date:'medium' }} via @@ -39,36 +39,36 @@

      Account Details

      Access Paid Until{{ 'account.accessPaidUntil' | translate }} {{ currentUser.subscription.ends_at | date:'medium' }}
      Username{{ 'account.username' | translate }} {{ currentUser.username }}
      Device Name{{ 'account.deviceName' | translate }} {{ currentUser.device?.name }}
      Account State{{ 'account.accountState' | translate }} {{ currentUser.state }}
      Features{{ 'account.features' | translate }} {{ currentUser.current_plan?.feature_ids?.join(", ") }}
      Device ID{{ 'account.deviceID' | translate }} {{currentUser.device?.id}}
      Logged in Since{{ 'account.loggedInSince' | translate }} {{ currentUser.LoggedInAt | date:'medium' }}