{
"app": {
"name": "Tinyforge",
"version": "v0.1"
},
"layout": {
"serviceStatus": "Service status"
},
"health": {
"connected": "connected",
"disconnected": "disconnected",
"rawError": "Technical details",
"retryNow": "Retry now"
},
"nav": {
"dashboard": "Dashboard",
"apps": "Apps",
"eventTriggers": "Triggers",
"logScanRules": "Log Rules",
"triggers": "Triggers",
"proxies": "Proxies",
"events": "Events",
"settings": "Settings",
"logout": "Log out",
"dns": "DNS Records",
"containers": "Containers"
},
"dashboard": {
"title": "Dashboard",
"newApp": "New app",
"totalWorkloads": "Total Workloads",
"runningContainers": "Running Containers",
"failedContainers": "Failed Containers",
"recentWorkloads": "Recent Workloads",
"retry": "Retry",
"noWorkloads": "No workloads yet.",
"noWorkloadsDesc": "Create an app and forge your first workload to get started.",
"loadFailed": "Failed to load dashboard",
"staleContainers": "Stale Containers",
"unusedImagesWarning": "Unused Docker images are taking up disk space",
"unusedImages": "unused images",
"systemHealth": "System health",
"daemons": "Daemons",
"systemResources": "System resources",
"systemResourcesSubtitle": "CPU, memory, disk, and top consumers"
},
"resources": {
"cpuCores": "CPU Cores",
"memory": "Memory",
"running": "Running",
"dockerDisk": "Docker Disk",
"workloadUtilization": "Workload utilization",
"windowMinutes": "{n} minutes",
"windowHours": "{n} hours",
"noSamples": "No samples yet — the collector samples every {interval}s.",
"collectionDisabled": "Stats collection is disabled. Enable it in Settings to populate this chart.",
"diskImages": "Images",
"diskContainers": "Containers",
"diskVolumes": "Volumes",
"diskBuildCache": "Build cache",
"reclaimable": "{size} reclaimable",
"topConsumers": "Top consumers",
"byCpu": "by cpu",
"byMemory": "by memory",
"noRunning": "No running containers.",
"instance": "instance",
"site": "site",
"showHistory": "Show history",
"hideHistory": "Hide history",
"cpuSeries": "CPU %",
"memorySeries": "Memory %",
"loading": "Loading…",
"sectionTitle": "Resources",
"showLogs": "Show logs",
"hideLogs": "Hide logs",
"dockerUnavailable": "Docker is unavailable. Check that the daemon is running."
},
"statsSettings": {
"intervalLabel": "Stats collection interval (s)",
"intervalHelp": "How often resource samples are collected. 0 disables collection. Range: 5–300s.",
"retentionLabel": "Stats retention (hours)",
"retentionHelp": "How long resource samples are kept. 0 disables collection. Range: 0–24h."
},
"tagPicker": {
"registry": "Registry",
"local": "Local"
},
"settings": {
"title": "Settings",
"general": "General",
"integrations": "Integrations",
"dns": "DNS",
"maintenance": "Maintenance",
"registries": "Registries",
"credentials": "Credentials",
"authentication": "Authentication",
"backup": "Backups",
"appearance": "Appearance",
"groupMain": "Overview",
"groupProxy": "Routing",
"groupSystem": "System",
"groupSecurity": "Security",
"staleThreshold": "Stale threshold (days)",
"staleThresholdHelp": "Containers inactive for longer than this will be flagged as stale.",
"dockerCleanup": "Docker Image Cleanup",
"dockerCleanupHelp": "Remove unused Docker images belonging to your projects. Only images not used by active instances are removed.",
"pruneThreshold": "Warning Threshold (MB)",
"pruneThresholdHelp": "Show dashboard warning when unused project images exceed this size. 0 = disabled.",
"pruneImages": "Prune Unused Images",
"pruning": "Pruning...",
"pruneResult": "Removed {count} images, reclaimed {mb} MB",
"pruneConfirmMessage": "This will remove unused Docker images belonging to your projects. Images used by active instances will not be affected.",
"pruneFailed": "Failed to prune images",
"proxyProvider": "Proxy Provider",
"proxyProviderHelp": "Select how reverse proxy routes are managed for deployed containers.",
"proxyNone": "None",
"proxyNoneDesc": "No proxy — containers are accessed directly by port",
"proxyNpm": "Nginx Proxy Manager",
"proxyNpmDesc": "Routes managed via NPM API (configure credentials below)",
"npm": "Nginx Proxy Manager",
"traefik": "Traefik",
"traefikLabelsTitle": "Docker Labels Reference",
"traefikLabelsDesc": "These labels are automatically added to deployed containers. Shown here for reference.",
"proxyTraefik": "Traefik",
"proxyTraefikDesc": "Auto-discovery via Docker labels — no API calls needed",
"proxyNoneWarning": "Switching to None does not remove existing proxy routes. You may need to clean them up manually.",
"traefikEntrypoint": "Entrypoint",
"traefikEntrypointHelp": "Traefik entrypoint name for HTTPS routes",
"traefikCertResolver": "Cert Resolver",
"traefikCertResolverHelp": "TLS certificate resolver name (e.g., letsencrypt)",
"traefikNetwork": "Docker Network",
"traefikNetworkHelp": "Network Traefik listens on (leave empty to use global network)",
"traefikApiUrl": "Traefik API URL",
"traefikApiUrlHelp": "Optional — for health checks (e.g., http://traefik:8080)"
},
"settingsGeneral": {
"title": "General Settings",
"globalConfig": "Global Configuration",
"globalConfigDesc": "Core infrastructure: the base domain, network, and polling cadence Tinyforge uses to orchestrate containers.",
"configureNpm": "Nginx Proxy Manager is selected.",
"configureTraefik": "Traefik is selected.",
"configureLink": "Configure provider",
"domain": "Domain",
"domainHelp": "Base domain for subdomain routing (e.g., example.com → stage-dev-app.example.com)",
"serverIp": "Server IP (Docker Host)",
"serverIpHelp": "IP of the machine running Docker. Used for NPM remote forwarding.",
"publicIp": "Public IP (DNS Target)",
"publicIpHelp": "IP for DNS A records — typically your proxy/load balancer. Falls back to Server IP if empty.",
"dockerNetwork": "Docker Network",
"dockerNetworkHelp": "Docker network that containers and proxy share. Must match your NPM/Traefik network.",
"subdomainPattern": "Subdomain Pattern",
"subdomainPatternHelp": "Pattern for auto-generated subdomains",
"subdomainVarsTitle": "Available variables",
"varProject": "Project name",
"varStage": "Stage name",
"varTag": "Image tag",
"varPort": "Container port",
"pollingInterval": "Polling Interval (seconds)",
"pollingIntervalHelp": "How often to check registries for new tags (60-86400)",
"notificationUrl": "Notification URL",
"notificationUrlHelp": "Webhook URL for deploy notifications",
"saveSettings": "Save Settings",
"saving": "Saving...",
"saved": "Settings saved successfully",
"saveFailed": "Failed to save settings",
"loadFailed": "Failed to load settings",
"webhookUrl": "Webhook URL",
"webhookDesc": "This secret URL receives image push notifications from your CI pipeline.",
"noWebhookUrl": "No webhook URL configured",
"copy": "Copy",
"copied": "Webhook URL copied to clipboard",
"regenerateUrl": "Regenerate URL",
"regenerating": "Regenerating...",
"regenerated": "Webhook URL regenerated",
"regenerateFailed": "Failed to regenerate webhook URL",
"regenerateWarning": "Warning: regenerating will invalidate the current URL. Update your CI pipelines.",
"sslCertificate": "SSL Certificate",
"sslCertificateHelp": "Wildcard certificate from NPM for auto-SSL on proxy hosts",
"selectCertificate": "Select Certificate",
"noCertificate": "None (no SSL)",
"clearCertificate": "Clear",
"loadingCertificates": "Loading certificates...",
"noCertificatesFound": "No wildcard certificates found in NPM",
"dnsConfig": "DNS Configuration",
"wildcardDns": "Wildcard DNS is configured",
"wildcardDnsHelp": "When enabled, all subdomains resolve to your server via a wildcard DNS rule. Disable to manage DNS records per service.",
"dnsProvider": "DNS Provider",
"dnsProviderHelp": "Select a DNS provider for automatic record management",
"cloudflareApiToken": "Cloudflare API Token",
"cloudflareApiTokenHelp": "API token with DNS edit permissions for your zone",
"cloudflareApiTokenPlaceholder": "Enter Cloudflare API token",
"cloudflareApiTokenConfigured": "API token is configured",
"cloudflareZone": "Cloudflare Zone",
"cloudflareZoneHelp": "Select the DNS zone to manage records in",
"selectZone": "Select Zone",
"noZone": "No zone selected",
"loadingZones": "Loading zones...",
"noZonesFound": "No zones found for this token",
"testConnection": "Test Connection",
"testingConnection": "Testing...",
"connectionSuccess": "Connection successful",
"connectionFailed": "Connection failed",
"baseVolumePath": "Base Volume Path",
"baseVolumePathHelp": "Prepended to relative volume sources (e.g., /data + my-app/uploads = /data/my-app/uploads)"
},
"settingsRegistries": {
"title": "Container Registries",
"description": "Manage your container registries for image detection.",
"addRegistry": "Add Registry",
"editRegistry": "Edit Registry",
"addNewRegistry": "Add New Registry",
"name": "Name",
"nameHelp": "A friendly name for this registry",
"url": "URL",
"urlHelp": "Registry base URL",
"type": "Type",
"typeHelp": "Registry type for API compatibility",
"token": "Token",
"tokenHelpNew": "API token for authentication",
"tokenHelpEdit": "Leave empty to keep the existing token",
"owner": "Owner",
"ownerHelp": "Package owners, comma-separated (e.g., alexei,my-org)",
"save": "Save",
"saving": "Saving...",
"update": "Update",
"test": "Test",
"testing": "Testing...",
"edit": "Edit",
"delete": "Delete",
"noRegistries": "No registries configured yet.",
"addFirst": "Add your first registry",
"registryUpdated": "Registry updated",
"registryAdded": "Registry added",
"registryDeleted": "Registry \"{name}\" deleted",
"testSuccess": "Connection to \"{name}\" successful",
"saveFailed": "Failed to save registry",
"deleteFailed": "Failed to delete registry",
"testFailed": "Connection test failed",
"loadFailed": "Failed to load registries",
"deleteConfirm": "Delete registry \"{name}\"? This cannot be undone.",
"healthChecking": "Checking...",
"healthConnected": "Connected",
"healthUnreachable": "Unreachable"
},
"settingsNpm": {
"testConnection": "Test Connection",
"testing": "Testing...",
"testSuccess": "NPM connection successful",
"testFailed": "NPM connection failed",
"saveFailedConnection": "Cannot save — connection test failed",
"remoteMode": "Remote NPM",
"remoteModeHelp": "Enable when NPM runs on a different machine than Docker. Forwards to Server IP with published host ports.",
"remoteModeWarning": "Requires Server IP in General settings. Ports are auto-mapped to random host ports.",
"accessList": "Default Access List",
"accessListHelp": "NPM access list for HTTP authentication on proxy hosts. Can be overridden per project.",
"noAccessList": "Global default",
"selectAccessList": "Select an access list",
"noAccessLists": "No access lists found in NPM",
"accessListLoadFailed": "Failed to load access lists"
},
"settingsCredentials": {
"title": "Credentials",
"description": "Manage credentials for Nginx Proxy Manager and registry tokens. All values are encrypted at rest.",
"npm": "Nginx Proxy Manager",
"npmDesc": "Credentials for managing proxy hosts via NPM API",
"configured": "Configured",
"npmUrl": "NPM URL",
"npmUrlHelp": "Nginx Proxy Manager API URL",
"email": "Email",
"emailHelp": "NPM admin email",
"password": "Password",
"passwordHelpNew": "NPM admin password (will be encrypted)",
"passwordHelpEdit": "Enter the new password to replace the existing one",
"changeCredentials": "Change Credentials",
"save": "Save",
"saving": "Saving...",
"saved": "NPM credentials saved",
"saveFailed": "Failed to save NPM credentials",
"loadFailed": "Failed to load credentials",
"registryTokens": "Registry Tokens",
"registryTokensDesc": "Registry authentication tokens are managed per-registry in the",
"registriesLink": "Registries",
"registryTokensSuffix": "section. Each registry stores its token encrypted in the database.",
"notSet": "Not set"
},
"settingsBackup": {
"title": "Backup Management",
"description": "Manage database backups and configure automatic backup schedules.",
"autoBackup": "Automatic Backups",
"autoBackupHelp": "Automatically create backups at the configured interval.",
"interval": "Backup Interval",
"intervalHelp": "How often to create automatic backups.",
"intervalHours": "{hours} hours",
"retention": "Retention Count",
"retentionHelp": "Maximum number of backups to keep. Oldest are deleted first.",
"backupNow": "Backup Now",
"creatingBackup": "Creating...",
"backupCreated": "Backup created successfully",
"backupFailed": "Failed to create backup",
"backupList": "Backups",
"noBackups": "No backups yet. Create one manually or enable automatic backups.",
"columnFilename": "Filename",
"columnSize": "Size",
"columnType": "Type",
"columnDate": "Created",
"columnActions": "Actions",
"download": "Download",
"delete": "Delete",
"restore": "Restore",
"deleteConfirm": "Are you sure you want to delete this backup?",
"deleted": "Backup deleted",
"deleteFailed": "Failed to delete backup",
"restoreConfirm": "Are you sure you want to restore from this backup? This will replace the current database and restart the server. All current data will be lost.",
"restoreWarning": "This action cannot be undone!",
"restored": "Database restored. The server is restarting...",
"restoreFailed": "Failed to restore backup",
"typeManual": "Manual",
"typeAuto": "Auto",
"typePreDeploy": "Pre-deploy",
"preDeploy": "Backup before every deploy",
"preDeployHelp": "Take a Tinyforge DB snapshot at the start of every project deploy. Independent of the periodic schedule above; restorable from this list under the \"Pre-deploy\" type.",
"save": "Save",
"saving": "Saving...",
"saved": "Backup settings saved",
"saveFailed": "Failed to save backup settings"
},
"settingsAuth": {
"title": "Authentication Settings",
"description": "Configure authentication mode and manage users.",
"authMode": "Authentication Mode",
"local": "Local (username/password)",
"oidc": "OIDC (SSO)",
"oidcConfig": "OIDC Provider Configuration",
"issuerUrl": "Issuer URL",
"clientId": "Client ID",
"clientSecret": "Client Secret",
"redirectUrl": "Redirect URL",
"saveSettings": "Save Settings",
"saving": "Saving...",
"saved": "Settings saved",
"saveFailed": "Failed to save",
"loadFailed": "Failed to load settings",
"localUsers": "Local Users",
"username": "Username",
"email": "Email",
"role": "Role",
"created": "Created",
"noUsers": "No users found.",
"addUser": "Add User",
"viewer": "Viewer",
"admin": "Admin",
"userCreated": "User created",
"userDeleted": "User deleted",
"createFailed": "Failed to create user",
"deleteFailed": "Failed to delete user",
"deleteConfirm": "Are you sure you want to delete this user?",
"usernameRequired": "Username and password are required",
"networkError": "Network error",
"password": "Password"
},
"login": {
"title": "Tinyforge",
"subtitle": "Sign in to your account",
"username": "Username",
"password": "Password",
"signIn": "Sign in",
"signingIn": "Signing in...",
"or": "or",
"ssoButton": "Sign in with SSO (OIDC)",
"loginFailed": "Login failed",
"networkError": "Network error"
},
"proxies": {
"title": "Proxy Routes",
"description": "Active proxy routes from deployed containers and static sites.",
"domain": "Domain",
"project": "Project / Site",
"stage": "Stage / Mode",
"tag": "Tag",
"port": "Port",
"status": "Status",
"source": "Source",
"sourceContainer": "Container",
"sourceStatic": "Static Site",
"sourceDeno": "Deno Site",
"filterAll": "All",
"filterContainers": "Containers",
"filterSites": "Sites",
"noRoutes": "No proxy routes",
"noRoutesDesc": "Proxy routes are created automatically when you deploy a container with proxy enabled or publish a static site.",
"searchPlaceholder": "Search by domain, project, or tag...",
"noMatch": "No routes match your search.",
"loadFailed": "Failed to load proxy routes",
"route": "route",
"routes": "routes"
},
"common": {
"cancel": "Cancel",
"confirm": "Confirm",
"delete": "Delete",
"edit": "Edit",
"change": "Change",
"save": "Save",
"retry": "Retry",
"loading": "Loading...",
"noData": "No data",
"project": "Project",
"stack": "Stack",
"site": "Site",
"back": "Back",
"actions": "Actions",
"stop": "Stop",
"start": "Start",
"restart": "Restart",
"remove": "Remove",
"instance": "instance",
"instances": "instances",
"next": "Next",
"yes": "Yes",
"no": "No",
"saving": "Saving...",
"refresh": "Refresh",
"all": "All",
"running": "Running",
"stopped": "Stopped",
"missing": "Missing"
},
"containers": {
"errLoad": "Failed to load containers",
"searchPlaceholder": "Search workload, role, image, subdomain…",
"kindFilterLabel": "Workload kind",
"stateFilterLabel": "Container state",
"emptyTitle": "No containers",
"emptyDesc": "Deploy a project, stack, or site to see containers here.",
"noMatch": "No containers match the current filters.",
"showingN": "Showing {visible} of {total} containers",
"col": {
"workload": "Workload",
"kind": "Kind",
"role": "Role",
"image": "Image",
"state": "State",
"subdomain": "Subdomain",
"lastSeen": "Last seen"
}
},
"empty": {
"noRegistries": "No registries",
"noRegistriesDesc": "Add a container registry to enable image detection.",
"noUsers": "No users",
"noUsersDesc": "Add local users to manage access."
},
"validation": {
"required": "{field} is required",
"invalidUrl": "Invalid URL format",
"invalidDomain": "Invalid domain format",
"invalidIp": "Invalid IP format",
"invalidEmail": "Invalid email format",
"invalidPort": "Port must be between 1 and 65535",
"invalidPollingInterval": "Polling interval must be between 60 and 86400 seconds",
"invalidProjectName": "Only lowercase letters, numbers, and hyphens allowed",
"requiredWhenUpdating": "{field} is required when updating credentials",
"requiredForNew": "{field} is required for new registries"
},
"theme": {
"light": "Light",
"dark": "Dark",
"system": "System"
},
"entityPicker": {
"search": "Search...",
"noResults": "No results found"
},
"stale": {
"title": "Stale Containers",
"noStale": "No stale containers",
"noStaleDesc": "All containers are healthy and running.",
"cleanup": "Clean up",
"cleanupAll": "Clean up all",
"confirmCleanup": "This will stop and remove the container. Continue?",
"confirmBulkCleanup": "This will stop and remove all stale containers. Continue?",
"daysStale": "days stale",
"lastAlive": "Last alive",
"count": "Stale",
"cleanedUp": "Container cleaned up",
"bulkCleanedUp": "{count} containers cleaned up",
"cleanupFailed": "Cleanup failed",
"loadFailed": "Failed to load stale containers"
},
"logs": {
"title": "Container Logs",
"lines": "lines",
"follow": "Follow",
"following": "Following...",
"loading": "Loading logs...",
"noLogs": "No log output"
},
"events": {
"title": "Event Log",
"noEvents": "No events found",
"noEventsDesc": "Events will appear here as they occur.",
"loadMore": "Load more",
"newEvents": "new events",
"totalCount": "{count} total",
"clearAll": "Clear All",
"clearAllTitle": "Clear Event Log",
"clearAllMessage": "This will permanently delete all event log entries. This cannot be undone.",
"cleared": "Cleared {count} events",
"clearFailed": "Failed to clear events",
"filter": {
"severity": "Severity",
"source": "Source",
"dateRange": "Date range",
"search": "Search events...",
"lastHour": "Last hour",
"last24h": "Last 24 hours",
"last7d": "Last 7 days",
"allTime": "All time",
"clear": "Clear filters"
},
"severity": {
"info": "Info",
"warn": "Warning",
"error": "Error"
},
"source": {
"deploy": "Deploy",
"static_site": "Static Site",
"stale_scanner": "Stale Scanner",
"stale_cleanup": "Stale Cleanup",
"admin": "Admin"
},
"metadata": "Details"
},
"stats": {
"cpu": "CPU",
"mem": "MEM",
"unavailable": "Stats unavailable"
},
"systemHealth": {
"title": "System Health",
"containers": "Containers",
"proxies": "Proxies",
"recentErrors": "Recent Errors"
},
"daemons": {
"title": "Daemons",
"refresh": "Refresh",
"refreshing": "Refreshing",
"docker": "Docker Engine",
"npm": "Nginx Proxy Manager",
"traefik": "Traefik",
"proxy": "Proxy",
"online": "Online",
"offline": "Offline",
"notConfigured": "Not configured",
"containers": "Containers",
"running": "Running",
"paused": "Paused",
"stopped": "Stopped",
"version": "Version",
"apiVersion": "API Version",
"platform": "Platform",
"kernel": "Kernel",
"cpu": "CPU",
"memory": "Memory",
"storage": "Storage Driver",
"images": "Images",
"latency": "Latency",
"rootDir": "Root Dir",
"provider": "Provider",
"endpoint": "Endpoint",
"proxyHosts": "Proxy Hosts",
"managed": "managed",
"external": "external",
"accessLists": "Access Lists",
"certificates": "Certificates",
"dockerHint": "Check that the Docker daemon is running and that the socket is reachable.",
"proxyHint": "Verify the proxy URL, credentials, and that the service is listening.",
"noProxyDesc": "No proxy provider is configured. Tinyforge can manage routes via Nginx Proxy Manager or Traefik.",
"configureProxy": "Configure in Settings",
"dockerNotReachable": "Docker daemon is not reachable.",
"dockerUnreachable": "Docker unreachable",
"proxyUnreachable": "Proxy unreachable",
"reachable": "reachable"
},
"dns": {
"title": "DNS Records",
"description": "View and manage DNS records created by Tinyforge.",
"wildcardActive": "Wildcard DNS Mode Active",
"wildcardActiveDesc": "DNS records are managed externally via wildcard DNS. Disable wildcard DNS in Settings to manage records individually.",
"refresh": "Refresh",
"syncNow": "Sync Now",
"syncing": "Syncing...",
"syncComplete": "Sync complete: {created} created, {deleted} deleted, {synced} already synced",
"syncFailed": "DNS sync failed",
"searchPlaceholder": "Search by FQDN...",
"allConsumers": "All consumers",
"managed": "Managed (instances)",
"standalone": "Standalone proxies",
"orphaned": "Orphaned",
"allStatuses": "All statuses",
"statusSynced": "Synced",
"statusMissing": "Missing",
"statusOrphaned": "Orphaned",
"columnFqdn": "FQDN",
"columnType": "Type",
"columnValue": "Value",
"columnConsumer": "Consumer",
"columnStatus": "Status",
"columnActions": "Actions",
"noConsumer": "No consumer",
"noRecords": "No DNS records found. Records will appear here when services are deployed.",
"noMatchingRecords": "No records match the current filters.",
"deleteRecord": "Delete record",
"recordDeleted": "DNS record {fqdn} deleted",
"deleteFailed": "Failed to delete DNS record",
"loadFailed": "Failed to load DNS records",
"totalRecords": "Total: {count}",
"syncedCount": "Synced: {count}",
"missingCount": "Missing: {count}",
"orphanedCount": "Orphaned: {count}"
},
"language": {
"en": "English",
"ru": "Russian"
},
"timezone": {
"eyebrow": "The Forge // Chronograph",
"title": "Display timezone",
"subtitle": "All dates across Tinyforge — event log, deploys, backups, sites — render in this zone.",
"modeLabel": "Detection mode",
"modeAuto": "Auto-detect",
"modeManual": "Manual",
"autoDetect": "Auto-detect from browser",
"autoBadge": "Auto",
"activeZone": "Active zone",
"changeZone": "Change timezone",
"clickToChange": "Click to pick a zone →",
"pickerTitle": "Select timezone",
"pickerPlaceholder": "Search zones — city, region, UTC offset…",
"groupAuto": "Detection",
"groupPopular": "Popular",
"groupAll": "All timezones",
"previewFull": "Full timestamp",
"previewDate": "Date only",
"previewHint": "Timestamps like the event log will look exactly like this."
},
"settingsDns": {
"title": "DNS Configuration",
"description": "Choose whether routes rely on a wildcard record or per-subdomain records managed by a DNS provider."
},
"settingsIntegrations": {
"title": "Integrations",
"outgoing": "Outgoing notifications",
"outgoingDesc": "Where Tinyforge posts deploy and alert events. Paste a webhook URL (Apprise, Discord, Slack, your own handler).",
"incoming": "Incoming webhooks",
"incomingMovedDesc": "Inbound webhooks are now scoped per entity. Open a project or static site to view and rotate its webhook URL."
},
"webhookLog": {
"title": "Recent webhook deliveries",
"description": "The last 14 days of inbound webhook hits — outcome, signature state, and reason. Refreshes every 30 seconds.",
"refresh": "Refresh",
"loadFailed": "Failed to load webhook deliveries",
"empty": "No webhook deliveries yet.",
"colTime": "When",
"colStatus": "Status",
"colOutcome": "Outcome",
"colSignature": "Signature",
"colDetail": "Detail",
"colSource": "Source",
"outcome": {
"deploy": "Deployed",
"skip": "Skipped",
"rejected": "Rejected",
"not_found": "Not found",
"bad_request": "Bad request",
"error": "Error"
},
"sig": {
"valid": "valid",
"invalid": "invalid",
"missing": "missing",
"unconfigured": "off"
}
},
"webhookPanel": {
"copy": "Copy",
"copied": "Webhook URL copied to clipboard",
"copyFailed": "Failed to copy to clipboard",
"noUrl": "No webhook URL configured",
"loadFailed": "Failed to load webhook URL",
"regenerate": "Regenerate URL",
"regenerated": "Webhook URL regenerated",
"regenerateFailed": "Failed to regenerate webhook URL",
"regenerateWarning": "Regenerating invalidates the current URL. Update any CI pipeline or Git webhook that uses it.",
"confirmRegenerate": "Replace the current URL?",
"confirmYes": "Regenerate",
"confirmNo": "Cancel",
"signingTitle": "Inbound HMAC signing",
"signingDesc": "Verify webhook payloads with an HMAC-SHA256 signature so a leaked URL alone cannot be used to forge requests. Compatible with Gitea/GitHub webhook secrets.",
"signingActive": "Signing secret configured.",
"signingInactive": "No signing secret — inbound requests are not authenticated beyond the URL.",
"signingIssue": "Issue signing secret",
"signingRotate": "Rotate signing secret",
"signingDisable": "Disable signing",
"signingDisableConfirm": "Disable signing",
"signingIssued": "New signing secret issued — copy it before leaving this page",
"signingIssueFailed": "Failed to issue signing secret",
"signingDisabled": "Signing disabled",
"signingDisableFailed": "Failed to disable signing",
"signingShownOnce": "Copy this secret now — it will not be shown again.",
"signingDismiss": "Dismiss",
"signingHint": "Set this as the webhook secret in Gitea/GitHub/GitLab. Tinyforge expects {header} on every request.",
"signingCopied": "Signing secret copied to clipboard",
"requireSignature": "Require signature",
"requireSignatureHelp": "Reject any request that lacks a valid signature. Issue a signing secret first.",
"signingRequireFailed": "Failed to update signature requirement"
},
"outgoingWebhook": {
"signingOn": "Signed",
"signingOff": "Unsigned",
"signingSecret": "HMAC signing secret",
"noSecret": "No signing secret — outgoing events are not signed.",
"reveal": "Reveal",
"generate": "Generate",
"copy": "Copy",
"copied": "Signing secret copied to clipboard",
"copyFailed": "Failed to copy to clipboard",
"loadFailed": "Failed to load signing secret",
"regenerate": "Regenerate",
"regenerated": "Signing secret regenerated",
"regenerateFailed": "Failed to regenerate signing secret",
"confirmRegenerateTitle": "Rotate signing secret?",
"confirmRegenerate": "The current secret is invalidated immediately. Every receiver verifying it must be updated in lock-step or it will start rejecting events.",
"confirmDisableTitle": "Disable HMAC signing?",
"confirmDisable": "Future events go out without the X-Hub-Signature-256 header. Receivers that require signatures will reject them.",
"confirmYes": "Confirm",
"confirmNo": "Cancel",
"disable": "Disable signing",
"disabled": "Signing disabled",
"disableFailed": "Failed to disable signing",
"sendTestTitle": "Send a test event",
"sendTestHelp": "Fires a synthetic \"test\" event to the resolved URL using the current secret.",
"sendTest": "Send test",
"sending": "Sending…",
"testFailed": "Failed to send test event",
"tier": "Tier",
"signed": "Signed",
"unsigned": "Unsigned",
"deliveryId": "Delivery",
"responseBody": "Response body",
"networkError": "Network error",
"fallbackTo": "No URL set on this tier — events will fall through to {label}.",
"noUrlConfigured": "No URL set. Configure one above before sending a test."
},
"settingsMaintenance": {
"title": "Maintenance",
"thresholds": "Thresholds",
"thresholdsDesc": "Tune when Tinyforge flags stale containers and warns about unused image disk usage.",
"dangerZone": "Danger zone"
},
"observability": {
"section": "Observability",
"manage": "manage",
"loading": "Loading…",
"anyEvent": "any event",
"noUrlSet": "No URL configured",
"configured": "CONFIGURED",
"clear": "Clear",
"advanced": "Advanced",
"cancel": "Cancel",
"save": "Save changes",
"saving": "Saving…",
"delete": "Delete",
"deleting": "Deleting…",
"refresh": "Refresh",
"open": "Open",
"edit": "Edit",
"back": "Back",
"regex": {
"sampleLabel": "Sample line",
"placeholder": "paste a representative log line here",
"promptType": "type a sample to test the pattern",
"noMatch": "NO MATCH",
"noMatchHint": "pattern did not match this line",
"match": "MATCH",
"invalid": "REGEX",
"captures": "Captures"
}
},
"triggers": {
"title": "Event triggers",
"titleNew": "Forge a new trigger",
"titleSingular": "Trigger",
"lede": "Filter event-log entries (deploy events, log scanner output, future sources) and dispatch a webhook when they match. Filters AND together; empty filters mean \"match anything.\"",
"ledeNew": "Create a filter+action rule. The dispatcher AND-composes all filter fields. Leave a field empty to skip that dimension.",
"stat": {
"total": "TOTAL",
"enabled": "ENABLED",
"disabled": "DISABLED"
},
"toolbar": {
"newButton": "New trigger",
"backToList": "Back to triggers"
},
"empty": {
"heading": "No triggers yet",
"body": "Configure a trigger to forward event-log entries to Slack, a notification bridge, or any HTTP receiver. Tinyforge signs requests with X-Hub-Signature-256 when a secret is set.",
"cta": "Create the first trigger"
},
"list": {
"name": "Name",
"filters": "Filters",
"action": "Action",
"status": "Status",
"open": "Open"
},
"detail": {
"config": "Configuration",
"configSub": "id #{id} · updated {updatedAt}",
"dangerZone": "Danger zone",
"dangerZoneSub": "Trigger deletion is immediate. No soft-delete.",
"sendTest": "Send test",
"sending": "Sending…",
"testHttp": "HTTP {code}",
"testSigned": "signed",
"testOk": "OK",
"testFail": "FAIL",
"deleteButton": "Delete trigger",
"deleteTitle": "Delete trigger?",
"deleteMessage": "Trigger \"{name}\" will be removed immediately. This cannot be undone."
},
"form": {
"name": "Name",
"namePlaceholder": "e.g. Slack #alerts on deploy failure",
"required": "REQUIRED",
"andComposed": "AND-COMPOSED",
"filtersLabel": "Filters",
"actionLabel": "Action",
"actionWebhookBadge": "WEBHOOK",
"severityCsv": "Severity (CSV)",
"severityPlaceholder": "warn,error",
"sourceCsv": "Source (CSV)",
"sourcePlaceholder": "deploy,logscan",
"messageRegex": "Message regex (optional)",
"messageRegexPlaceholder": "(?i)\\bpanic\\b",
"invalidRegex": "Invalid regex — server will reject.",
"urlLabel": "URL",
"urlPlaceholder": "https://hooks.slack.com/services/...",
"secretLabel": "HMAC secret (optional)",
"secretPlaceholder": "leave blank for unsigned delivery",
"secretHint": "Receivers verify X-Hub-Signature-256 against the raw body.",
"secretRotateHint": "Stored encrypted at rest. The value is never returned by the API after creation — leave the placeholder untouched to preserve the existing secret, type a new value to rotate, or clear and save to remove signing.",
"enabled": "Enabled",
"enabledHint": "Disabled triggers stay in the table but never dispatch.",
"submit": "Forge trigger",
"submitting": "Forging…",
"webhookUrl": "Webhook URL"
},
"status": {
"enabled": "enabled",
"disabled": "disabled"
}
},
"logscan": {
"title": "Log scan rules",
"titleNew": "Forge a new rule",
"titleSingular": "Rule",
"lede": "Regex patterns the scanner runs against every running container's log stream. Matched lines land in event_log with the rule's severity, where event triggers pick them up and fan out to operator-configured webhooks. {enabled} of {total} enabled.",
"ledeNew": "Tail container logs against a regex. Leave the workload field empty to create a global rule. To override an existing global for one workload, use the per-workload override action on the workload detail page.",
"stat": {
"total": "TOTAL",
"global": "GLOBAL",
"workload": "WORKLOAD",
"overrides": "OVERRIDES",
"activeTails": "ACTIVE TAILS",
"droppedBucket": "RATE-LIMITED",
"droppedCooldown": "COOLED DOWN",
"compileErrors": "COMPILE ERRORS"
},
"stats": {
"heading": "Scanner stats",
"headingSub": "Engine drop counters and last-snapshot compile errors. Counters reset on server restart.",
"noCompileErrors": "All rules compile cleanly.",
"compileErrorsHeading": "Compile errors (rule dropped from snapshot)",
"tailsExplain": "Per-container tail goroutines currently driven by the scanner manager."
},
"toolbar": {
"newButton": "New rule",
"backToList": "Back to rules"
},
"filter": {
"all": "ALL",
"global": "GLOBAL",
"workload": "WORKLOAD",
"overrides": "OVERRIDES"
},
"empty": {
"heading": "No rules yet",
"body": "Start with a global rule like (?i)\\bpanic\\b at severity error, then narrow per-workload via overrides on the workload detail page.",
"cta": "Create the first rule"
},
"list": {
"name": "Name",
"pattern": "Pattern",
"scope": "Scope",
"severity": "Severity",
"streams": "Streams",
"status": "Status",
"open": "Open"
},
"detail": {
"config": "Configuration",
"configSub": "id #{id} · scope {scope}",
"regexTest": "Regex test",
"regexTestSub": "Live preview uses the browser's JavaScript regex engine. Click \"Run server test\" to evaluate against Go's RE2 — authoritative and the only reliable signal for RE2-only constructs.",
"runServerTest": "Run server test",
"testing": "Testing…",
"serverTestHint": "Enter a sample line above first",
"serverTestSendHint": "Send sample to backend /test endpoint",
"serverMatch": "SERVER MATCH",
"serverNoMatch": "NO MATCH",
"serverNoMatchHint": "server regex did not match the sample",
"serverError": "ERROR",
"dangerZone": "Danger zone",
"dangerZoneSub": "Deleting a global rule cascade-removes its per-workload overrides.",
"deleteButton": "Delete rule",
"deleteTitle": "Delete rule?",
"deleteMessage": "Rule \"{name}\" will be removed immediately. Per-workload overrides referencing it will be cascade-deleted."
},
"form": {
"name": "Name",
"namePlaceholder": "e.g. Panic in worker",
"pattern": "Pattern",
"regex": "REGEX",
"patternPlaceholder": "(?i)\\bpanic\\b",
"invalidRegex": "Invalid regex — server will reject.",
"matchShape": "Match shape",
"matchShapeOpts": "SEVERITY · STREAMS · COOLDOWN",
"severity": "Severity",
"streams": "Streams",
"cooldown": "Cooldown (s)",
"cooldownHint": "Cooldown is per-rule per-container — the same rule firing on two containers stays independent. Token bucket caps per-container emissions at 10 events / 60s to prevent flooding event_log.",
"scope": "Scope",
"scopePlaceholder": "empty for global rule, or paste a workload id",
"scopeHint": "Workload-scoped rules apply only to that workload's containers. Per-workload overrides are easier to create from the workload detail page.",
"scopeGlobal": "Global (applies to every workload)",
"scopePick": "Pick workload…",
"scopePickTitle": "Pick a workload",
"scopeClear": "Make global",
"scopeSelected": "Workload",
"scopeUnknown": "Unknown workload",
"enabled": "Enabled",
"enabledHint": "Disabled rules stay in the table but never fire.",
"required": "REQUIRED",
"optional": "OPTIONAL",
"submit": "Forge rule",
"submitting": "Forging…"
},
"scope": {
"global": "global",
"workload": "workload {id}",
"override": "override of #{id}",
"overrideShort": "override #{id}"
},
"status": {
"enabled": "enabled",
"disabled": "disabled",
"on": "on",
"off": "off"
},
"panel": {
"heading": "Log rules",
"subEmpty": "No effective rules for this workload",
"subCount": "{count} effective rules",
"subCountOne": "1 effective rule",
"emptyHint": "This workload has no log scan rules applied. Create one via New rule — globals apply automatically; this workload can also have its own narrower rules or overrides.",
"newRule": "New rule",
"footerHint": "Global rules apply to every workload. Workload rules apply only here. Override rows substitute for a global on this workload — edit them to disable or change severity per-workload without touching the global.",
"override": "Override",
"overriding": "Overriding…",
"overrideTitle": "Create a per-workload override of this global rule"
}
},
"redeployTriggers": {
"section": "The Forge",
"title": "Redeploy triggers",
"titleNew": "Forge a new trigger",
"titleSingular": "Trigger",
"lede": "Sources of redeploy signals — registry pushes, git events, manual fires, schedules, webhooks, log matches. Each trigger lives once and fans out to every workload bound to it.",
"ledeNew": "Pick a kind, name it, and decide whether external systems may poke it via webhook. Bind it to one or more workloads from the workload page after creation.",
"ledeDetail": "Edit the trigger config, manage its webhook ingress, and review every workload listening to this signal.",
"stat": {
"total": "TOTAL",
"byKind": "{kind}",
"withWebhook": "WEBHOOK ON",
"boundWorkloads": "WORKLOADS"
},
"kind": {
"registry": "Registry",
"git": "Git",
"manual": "Manual",
"schedule": "Schedule",
"webhook": "Webhook",
"logscan": "Log scan",
"unknown": "Unknown"
},
"kindShort": {
"registry": "REG",
"git": "GIT",
"manual": "MAN",
"schedule": "CRN",
"webhook": "HK",
"logscan": "LOG",
"unknown": "?"
},
"kindHint": {
"registry": "Watch a container image; fire when a new tag matching the pattern is pushed.",
"git": "Fire when a configured branch advances or a tag matching the pattern is created.",
"manual": "Fires only via the workload's Deploy button or POST /workloads/{id}/deploy.",
"schedule": "Fires on a fixed cron-style schedule.",
"webhook": "Pure webhook — fires when the ingress URL is hit.",
"logscan": "Fires when an upstream log-scan rule matches a tailed line.",
"unknown": "Unknown trigger kind — fall back to the raw JSON editor."
},
"toolbar": {
"newButton": "New trigger",
"backToList": "Back to triggers"
},
"filter": {
"all": "ALL",
"ariaLabel": "Filter by kind"
},
"empty": {
"heading": "No triggers yet",
"body": "A trigger is the source of a redeploy signal — a registry watcher, git hook, manual button, scheduled fire, or webhook. Create one and bind it to as many workloads as you like.",
"cta": "Forge the first trigger"
},
"list": {
"name": "Name",
"kind": "Kind",
"bindings": "Workloads",
"webhook": "Webhook",
"created": "Created",
"open": "Open",
"webhookOn": "ON",
"webhookOff": "—",
"noBindings": "—",
"bindingsCount": "{count}"
},
"detail": {
"config": "Trigger configuration",
"configSub": "kind {kind} · id {id} · updated {updatedAt}",
"webhook": "Webhook ingress",
"webhookSub": "When enabled, external systems can fire this trigger by posting to the URL below. Each workload bound to it will be redeployed in turn.",
"webhookEnable": "Enable webhook ingress",
"webhookEnableHint": "When off, the trigger fires only via internal sources (its kind config) and the manual deploy button.",
"webhookRequireSig": "Require HMAC signature",
"webhookRequireSigHint": "Reject requests without a valid X-Hub-Signature-256 header. Recommended whenever the URL is reachable from the public internet.",
"webhookUrlLabel": "Ingress URL",
"webhookUrlNote": "Paste this into your CI / registry / GitHub webhook settings. The secret segment is the bearer — treat it like a password.",
"webhookCopy": "Copy",
"webhookCopied": "Copied",
"webhookRotate": "Rotate secret",
"webhookRotating": "Rotating…",
"webhookDisabledNote": "Webhook ingress is disabled. Toggle it on, save, and the URL will appear here.",
"bindings": "Bound workloads",
"bindingsSub": "Every workload listening to this trigger. To bind a new workload, open the workload page and add this trigger from there.",
"bindingsEmpty": "No workloads are bound to this trigger yet. Open a workload and bind this trigger from its Triggers panel.",
"bindingsListItem": {
"openWorkload": "Open workload",
"unbind": "Unbind"
},
"bindingEnabledHint": "Disable to keep the binding but stop this trigger from redeploying that workload.",
"dangerZone": "Danger zone",
"dangerZoneSub": "Trigger deletion is immediate. All bindings to it are cascade-removed.",
"deleteButton": "Delete trigger",
"deleteTitle": "Delete trigger?",
"deleteMessage": "Trigger \"{name}\" will be removed immediately, along with {count} binding(s). This cannot be undone.",
"rotateTitle": "Rotate webhook secret?",
"rotateMessage": "The current ingress URL stops working immediately. Update every external integration with the new URL after rotation.",
"rotateConfirm": "Rotate now",
"unbindTitle": "Unbind workload?",
"unbindMessage": "Workload \"{name}\" will stop redeploying when this trigger fires. The workload itself is not deleted.",
"unbindConfirm": "Unbind"
},
"form": {
"kindLabel": "Kind",
"kindHint": "Pick the source of the redeploy signal. The form below adapts to the kind.",
"name": "Name",
"namePlaceholder": "e.g. ghcr.io/me/api · main",
"required": "REQUIRED",
"configLabel": "Configuration",
"image": "Image reference",
"imagePlaceholder": "registry.example.com/owner/app",
"imageHint": "Full image reference without the tag — Tinyforge matches new tags pushed under it.",
"tagPattern": "Tag pattern",
"tagPatternPlaceholder": "*",
"tagPatternHint": "path.Match glob (e.g. v*, release-*). Use * to match every tag.",
"repo": "Repository",
"repoPlaceholder": "owner/name",
"repoHint": "Provider-agnostic owner/name slug as advertised by the git host.",
"mode": "Mode",
"modePush": "Push to branch",
"modeTag": "Tag created",
"branch": "Branch",
"branchPlaceholder": "main",
"branchHint": "Only push events advancing this branch fire the trigger.",
"manualNote": "Manual triggers carry no config. They fire only via the workload's Deploy button or POST /workloads/{id}/deploy.",
"unknownNote": "This kind has no built-in form yet. Use the JSON editor below; the server validates the shape.",
"advancedToggle": "Advanced JSON",
"advancedHint": "Power-user fallback — replaces the structured form with the raw config payload.",
"configJson": "Config JSON",
"configJsonHint": "Must parse as a valid JSON object. The shape is validated server-side per kind.",
"invalidJson": "Invalid JSON — server will reject.",
"webhookEnabled": "Enable webhook ingress now",
"webhookEnabledHint": "Generates a secret URL that external systems can hit to fire the trigger.",
"webhookRequireSig": "Require HMAC signature",
"webhookRequireSigHint": "Reject unsigned requests. The secret is the same one embedded in the URL — sign the body with HMAC-SHA256 and send it as X-Hub-Signature-256.",
"submit": "Forge trigger",
"submitting": "Forging…",
"cancel": "Cancel"
},
"binding": {
"enabled": "Enabled",
"disabled": "Disabled"
}
},
"apps": {
"list": {
"eyebrowSuffix": "APPS",
"title": "Apps",
"ledePrefix": "Plugin-native deployables —",
"ledeKindImage": "image",
"ledeKindCompose": "compose",
"ledeKindStatic": "static",
"ledeMiddle": ", or",
"ledeSuffix": ", with pluggable redeploy triggers. Legacy projects, stacks, and sites continue to live under their own sections during the cutover.",
"statTotal": "TOTAL",
"statImage": "IMAGE",
"statCompose": "COMPOSE",
"statStatic": "STATIC",
"refresh": "Refresh",
"newApp": "New App",
"filterAriaLabel": "Filter by source plugin",
"filterAll": "ALL",
"loadError": "Failed to load apps",
"alertTag": "ERR",
"emptyTitle": "No apps yet",
"emptyBody": "Apps unify image, compose, and static deployables behind a single plugin-driven surface. Forge your first one to see it light up here.",
"emptyCta": "Forge the first app",
"colName": "Name",
"colSource": "Source",
"colTrigger": "Trigger",
"colCreated": "Created",
"colActions": "Actions",
"rowOpen": "Open"
},
"new": {
"pageTitle": "New App · Tinyforge",
"backLabel": "Back to apps",
"eyebrowSuffix": "NEW APP",
"title": "Forge a new app",
"ledePrefix": "Create a plugin-native workload.",
"ledeSourceLabel": "Source",
"ledeSourceMid": "= how it deploys (image, compose, static). Pick or create a",
"ledeTriggerLabel": "trigger",
"ledeSuffix": "below — when one fires, the source plugin redeploys.",
"loadingKinds": "Loading available plugin kinds…",
"alertTag": "ERR",
"fieldName": "Name",
"fieldNameRequired": "REQUIRED",
"fieldNamePlaceholder": "my-app",
"fieldNameHint": "Lowercase, no spaces. Becomes part of container names and subdomains.",
"fieldSourcePlugin": "Source plugin",
"fieldSourceLabel": "Source",
"fieldSourceHint": "Populated from the running daemon — only plugins compiled in show up. Triggers (registry / git / manual) are configured below as standalone records.",
"fieldSourceConfig": "Source config",
"fieldConfigYaml": "YAML",
"fieldConfigForm": "FORM",
"fieldConfigJson": "JSON",
"advancedJson": "Advanced JSON",
"backToForm": "Back to form",
"resetSample": "Reset sample",
"switchToJsonTitle": "Switch to the raw JSON editor",
"switchToFormTitle": "Switch back to the form",
"jsonOk": "JSON OK",
"jsonInvalid": "JSON INVALID",
"linesUnit": "lines",
"composeHeader": "compose.yaml · compose",
"composeAriaLabel": "Compose YAML",
"composeProjectLabel": "Compose project name (optional)",
"composeProjectPlaceholder": "(defaults to sanitized workload name)",
"composePlaceholder": "services:\n web:\n image: nginx:alpine\n ports:\n - \"80\"",
"imageHeader": "image source · runtime knobs",
"imageRefLabel": "Image (registry path)",
"imageRefPlaceholder": "registry.example.com/owner/app",
"imageRefHint": "Fully-qualified reference; the tag is set per-deploy via the trigger or the Default tag field below.",
"imagePort": "Port",
"imageHealthcheck": "Healthcheck path",
"imageDefaultTag": "Default tag",
"imageRegistryLabel": "Registry (for private pulls)",
"imageRegistryPublic": "(public — no auth)",
"imageRegistryHint": "Match the name from the Registries settings page. Leave empty for public images.",
"imageCpu": "CPU limit (cores, 0 = ∞)",
"imageMemory": "Memory limit (MB, 0 = ∞)",
"imageMax": "Max instances",
"imageMaxHint": "1 = strict blue-green.",
"imageFoot": "Env vars and volume mounts live in their own panels on the workload detail page after creation.",
"staticHeader": "static source · pages from a repo",
"staticProvider": "Provider",
"staticBaseUrl": "Base URL",
"staticBaseUrlPlaceholder": "https://git.example.com",
"staticRepoOwner": "Repo owner",
"staticRepoOwnerPlaceholder": "owner",
"staticRepoName": "Repo name",
"staticRepoNamePlaceholder": "pages",
"staticBranch": "Branch",
"staticBranchPlaceholder": "main",
"staticFolder": "Folder path (optional)",
"staticFolderPlaceholder": "(repo root)",
"staticToken": "Access token (private repos)",
"staticTokenPlaceholder": "(leave blank for public repos)",
"staticTokenHint": "Encrypted at rest. Required only when the repo is private.",
"staticMode": "Mode",
"staticModeStaticDesc": "— serve files via nginx; zero runtime overhead.",
"staticModeDenoDesc": "— Deno runtime container with optional dynamic routing.",
"staticRenderMarkdown": "Render markdown",
"staticRenderMarkdownDesc": "— auto-render .md files as HTML pages.",
"staticFoot": "The webhook secret for git push triggers lives on the workload's Webhook panel after creation.",
"sourceConfigJsonTitle": "source_config.json · {kind}",
"sourceConfigJsonAria": "Source plugin configuration (JSON)",
"triggerNumLabel": "Trigger",
"triggerNumOptional": "OPTIONAL",
"triggerNewTag": "NEW",
"triggerPickTag": "PICK",
"triggerSkipTag": "SKIP",
"noteSkipTag": "SKIP",
"noteEmptyTag": "∅",
"faceLabel": "Public face",
"faceOptional": "OPTIONAL",
"faceSubdomain": "Subdomain",
"faceSubdomainPlaceholder": "myapp",
"faceDomain": "Domain",
"faceDomainPlaceholder": "(inherit from settings)",
"facePort": "Target port",
"faceHint": "Leave blank to skip provisioning a proxy route. Filling any field creates a single face row attached to this workload.",
"cancel": "Cancel",
"submit": "Forge app",
"submitting": "Forging…",
"triggers": {
"section": "Trigger",
"sectionSub": "Optional. Pick how this app gets a redeploy signal — registry watcher, git event, or manual button.",
"modeInline": "Add a trigger",
"modeInlineHint": "Creates a brand-new trigger record bound to this app — fits the common 1:1 case.",
"modePick": "Pick existing trigger",
"modePickHint": "Bind an existing trigger so multiple apps share one signal.",
"modeSkip": "Skip — add later",
"modeSkipHint": "The app is created without any trigger binding. Manual deploys still work.",
"switchToPick": "Pick existing trigger →",
"switchToInline": "← Create a new trigger instead",
"switchToSkip": "Skip for now",
"pickPlaceholder": "Select a trigger…",
"pickEmpty": "No triggers exist yet — create one inline above, or visit /triggers.",
"pickLabel": "Existing trigger",
"pickHint": "The same trigger can be bound to many apps. Manage standalone triggers under /triggers.",
"pickWebhookOn": "WEBHOOK ON",
"skippedNote": "No trigger will be bound. You can add one from the app's Triggers panel after it's created.",
"bindError": "App created, but the trigger binding failed: {error}. Open the app's Triggers panel to retry."
}
},
"detail": {
"pageTitleFallback": "App",
"backLabel": "Back to apps",
"eyebrowSuffix": "APP",
"kickerId": "id: {id}",
"loading": "Loading workload…",
"loadError": "Failed to load app",
"deployError": "Deploy failed",
"saveError": "Save failed",
"deleteError": "Delete failed",
"alertTag": "ERR",
"createdAt": "created",
"refreshLabel": "Refresh",
"editButton": "Edit",
"deleteButton": "Delete",
"editTitle": "Edit configuration",
"editSubPrefix": "Source",
"editSubSuffix": "· triggers managed in the Triggers panel below",
"editFieldName": "Name",
"editFieldParent": "Parent workload",
"editFieldOptional": "OPTIONAL",
"editFieldParentPlaceholder": "workload UUID (blank for root)",
"editSourceConfig": "Source config",
"editConfigYaml": "YAML",
"editConfigForm": "FORM",
"editConfigJson": "JSON",
"advancedJson": "Advanced JSON",
"backToForm": "Back to form",
"switchToJsonTitle": "Switch to the raw JSON editor",
"switchToFormTitle": "Switch back to the form",
"jsonOk": "JSON OK",
"jsonInvalid": "JSON INVALID",
"editComposeProject": "Compose project name (optional)",
"editComposeProjectPlaceholder": "(defaults to sanitized workload name)",
"editComposePlaceholder": "services:\n web:\n image: nginx:alpine",
"editComposeAria": "Compose YAML",
"editComposeHeader": "compose.yaml",
"editImageHeader": "image source · runtime knobs",
"editImageRef": "Image (registry path)",
"editImageRefPlaceholder": "registry.example.com/owner/app",
"editImagePort": "Port",
"editImageHealthcheck": "Healthcheck path",
"editImageDefaultTag": "Default tag",
"editImageRegistry": "Registry (for private pulls)",
"editImageRegistryPublic": "(public — no auth)",
"editImageCpu": "CPU limit (cores, 0 = ∞)",
"editImageMemory": "Memory limit (MB, 0 = ∞)",
"editImageMax": "Max instances",
"editImageFoot": "Env vars and volume mounts use their own panels below — saving here preserves them.",
"editStaticHeader": "static source · pages from a repo",
"editStaticProvider": "Provider",
"editStaticBaseUrl": "Base URL",
"editStaticBaseUrlPlaceholder": "https://git.example.com",
"editStaticRepoOwner": "Repo owner",
"editStaticRepoName": "Repo name",
"editStaticBranch": "Branch",
"editStaticFolder": "Folder path (optional)",
"editStaticFolderPlaceholder": "(repo root)",
"editStaticToken": "Access token (private repos)",
"editStaticTokenPlaceholder": "(leave blank for public repos)",
"editStaticMode": "Mode",
"editStaticModeStaticDesc": "— serve files via nginx.",
"editStaticModeDenoDesc": "— Deno runtime with dynamic routing.",
"editStaticRenderMarkdown": "Render markdown",
"editStaticRenderMarkdownDesc": "— auto-render .md as HTML.",
"editSourceJsonHeader": "source_config.json",
"editSourceJsonAria": "Source plugin configuration (JSON)",
"editPublicFaces": "Public faces",
"editPublicFacesTag": "JSON ARRAY",
"editPublicFacesHeader": "public_faces.json",
"editPublicFacesAria": "Public faces configuration (JSON array)",
"editCancel": "Cancel",
"editSave": "Save changes",
"editSaving": "Saving…",
"manualDeployTitle": "Manual deploy",
"manualDeployOk": "OK",
"manualDeployDispatched": "Dispatched {reference} as {by}",
"manualDeployRefAria": "Deploy reference",
"manualDeployRefPlaceholder": "reference (image tag, git sha, blank for default)",
"manualDeployButton": "Deploy",
"manualDeployDispatching": "Dispatching…",
"manualDeployHint": "Use a specific image tag, git sha, or branch to force a deploy. Leave blank to use the default reference resolved by the source plugin.",
"containersTitle": "Containers",
"containersEmpty": "No containers yet",
"containersCount": "{count} reconciled",
"containersEmptyInline": "No containers yet — deploy to spin one up.",
"containersColRole": "Role",
"containersColState": "State",
"containersColImage": "Image",
"containersColSubdomain": "Subdomain",
"containersColLastSeen": "Last seen",
"containersColActions": "Actions",
"containersLogsAction": "Logs",
"chainTitle": "Chain",
"chainSubFromParent": "promotes from a parent",
"chainSubParentOf": "parent of",
"chainChildSingular": "child",
"chainChildPlural": "children",
"chainParentLabel": "Parent",
"chainSelfLabel": "This",
"chainChildrenLabel": "Children",
"chainPromoteButton": "Promote from parent",
"chainPromoting": "Promoting…",
"chainHint": "Set parent_workload_id on a workload to build a chain. Image-source children can promote the parent's currently-running tag with one click.",
"volumesTitle": "Volumes",
"volumesEmpty": "No mounts",
"volumesCountSingular": "{count} mount",
"volumesCountPlural": "{count} mounts",
"volumesColTarget": "Target",
"volumesColSource": "Source",
"volumesColScope": "Scope",
"volumesColUpdated": "Updated",
"volumesColActions": "Actions",
"volumeSource": "Source (host)",
"volumeSourcePlaceholder": "/srv/data/myapp",
"volumeTarget": "Target (container)",
"volumeTargetPlaceholder": "/data",
"volumeScope": "Scope",
"volumeAddButton": "Add / Replace",
"volumeSaving": "Saving…",
"volumeHint": "Absolute mounts bind a host path into the container. Non-absolute scopes are accepted for future use; only absolute is honoured at deploy time today.",
"volumeTargetError": "Target must be an absolute container path (e.g. /data)",
"volumeSetFailed": "Failed to set volume",
"volumeDeleteFailed": "Failed to delete volume",
"envTitle": "Env",
"envEmpty": "No overrides",
"envCountSingular": "{count} override",
"envCountPlural": "{count} overrides",
"envColKey": "Key",
"envColValue": "Value",
"envColUpdated": "Updated",
"envColActions": "Actions",
"envEncrypted": "ENCRYPTED",
"envKey": "Key",
"envKeyPlaceholder": "DATABASE_URL",
"envValue": "Value",
"envValuePlaceholder": "(empty to unset)",
"envEncryptLabel": "Encrypt at rest",
"envAddButton": "Add / Replace",
"envSaving": "Saving…",
"envHint": "Encrypted values are write-only after store — the API redacts them on read. Rotate by setting a new value.",
"envKeyRequired": "Key is required",
"envSetFailed": "Failed to set env",
"envDeleteFailed": "Failed to delete env",
"sourceConfigTitle": "Source config",
"sourceConfigCopy": "Copy",
"sourceConfigCopied": "Copied",
"sourceConfigCopyAria": "Copy source config",
"publicFacesTitle": "Public faces",
"publicFacesCopyAria": "Copy public faces",
"deleteConfirmTitle": "Delete this app?",
"deleteConfirmMessage": "Tears down all containers and proxy routes owned by \"{name}\", then removes the row. This cannot be undone.",
"deleteConfirmFallbackName": "this workload",
"deleteConfirmYes": "Yes, delete",
"deleteConfirmDeleting": "Deleting…",
"manualDeploySub": "Bypasses configured triggers and dispatches through the source plugin directly.",
"chainTriggersZero": "no triggers",
"chainTriggersOne": "1 trigger",
"chainTriggersMany": "{count} triggers",
"bindings": {
"title": "Triggers",
"subEmpty": "No triggers bound. Manual deploys still work — add a trigger to wire up registry / git / webhook redeploys.",
"subCount": "{count} trigger bound",
"subCountMany": "{count} triggers bound",
"addButton": "Add trigger",
"openTrigger": "View trigger",
"unbindAction": "Unbind",
"rowEnabled": "Enabled",
"rowDisabled": "Disabled",
"rowEnableHint": "Disable to keep the binding but stop this trigger from redeploying the app.",
"loading": "Loading triggers…",
"loadError": "Failed to load trigger bindings",
"unbindTitle": "Unbind trigger?",
"unbindMessage": "Trigger \"{name}\" will stop redeploying this app. The trigger record itself is not deleted — it stays in /triggers and remains bound to any other apps.",
"unbindConfirm": "Unbind",
"modal": {
"title": "Add trigger",
"subtitle": "Bind a trigger to this app — create a new one inline, or pick an existing trigger to share.",
"tabInline": "Create new",
"tabPick": "Bind existing",
"submitInline": "Create & bind",
"submitPick": "Bind",
"submitting": "Binding…",
"cancel": "Cancel",
"error": "Bind failed",
"pickPlaceholder": "Select a trigger…",
"pickEmpty": "No triggers exist yet — switch to \"Create new\" to make one.",
"pickLabel": "Existing trigger",
"pickKind": "Filter by kind",
"pickKindAll": "All kinds"
},
"override": {
"toggle": "Override",
"title": "Per-binding overrides",
"subtitle": "Override fields of the trigger's config for this app only. Top-level keys you set here win; everything else inherits from the trigger.",
"badgeOne": "OVERRIDES 1 FIELD",
"badgeMany": "OVERRIDES {count} FIELDS",
"badgeTitle": "This binding overrides one or more fields of the trigger's config.",
"baseLabel": "Trigger config",
"baseLoading": "Loading trigger config…",
"baseHint": "Read-only view of the parent trigger's config. Edit it from the trigger page if it should change for every binding.",
"editLabel": "Override (JSON object)",
"editHint": "Top-level merge: only keys present here override the trigger. Leave the editor as {} to inherit verbatim.",
"previewLabel": "Effective config",
"previewHint": "Preview of what this binding will see when the trigger fires (trigger config with the override merged on top).",
"invalidJson": "Override must be a JSON object.",
"tooLarge": "Override is {size} B — exceeds the {limit} B server limit.",
"errInvalidJson": "Cannot save: override is not a valid JSON object.",
"errTooLarge": "Cannot save: override exceeds the 8 KiB server limit.",
"saveButton": "Save override",
"saving": "Saving…",
"resetButton": "Reset to inherit",
"closeButton": "Close"
}
}
}
}
}