Files
tiny-forge/web/src/lib/i18n/en.json
T
alexei.dolgolyov 3071cda512 feat(deploy): commit-status reporting to Git providers
Report deploy status back to the Git provider as a commit status
(pending/success/failure) for git-sourced workloads (static + dockerfile).

- GitProvider.SetCommitStatus on gitea/github/gitlab over the existing
  SSRF-safe client; fixed "tinyforge" context so redeploys update one row.
  postJSON returns status-code-only errors (never echoes the upstream body,
  which a hostile provider could use to reflect the auth token into the
  best-effort log line).
- Best-effort deploy hook: pending on deploy start, success/failure on
  outcome, gated on a per-workload report_commit_status flag. Never fails or
  blocks a deploy; emits nothing on the unchanged-SHA short-circuit.
- UI ToggleSwitch (create + edit) + reportCommitStatus in sourceForms.ts
  + en/ru i18n.
- Tests: per-provider state mapping + request shape; reporter gating
  (enabled/disabled/empty-SHA/nil/error-swallow).

Reviewed via go-reviewer + security-reviewer (0 CRITICAL/HIGH; one MEDIUM
body-echo log-leak fixed).
2026-05-29 11:37:56 +03:00

1741 lines
77 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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": "Event Triggers",
"logScanRules": "Log Rules",
"triggers": "Triggers",
"proxies": "Proxies",
"events": "Events",
"settings": "Settings",
"logout": "Log out",
"dns": "DNS Records",
"containers": "Containers",
"sectionObserve": "Observe",
"sectionSystem": "System",
"closeSidebar": "Close sidebar",
"openSidebar": "Open sidebar",
"quickNavTitle": "Press 'g' then a letter to jump between sections",
"quickNavLabel": "quick-nav"
},
"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",
"statSubWorkloads": "workloads →",
"statSubRunning": "running",
"statSubNeedAttention": "need attention",
"statSubStale": "stale →"
},
"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: 5300s.",
"retentionLabel": "Stats retention (hours)",
"retentionHelp": "How long resource samples are kept. 0 disables collection. Range: 024h."
},
"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",
"deleteTitle": "Delete registry?",
"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?",
"deleteConfirmMessage": "Delete user \"{username}\"? This cannot be undone.",
"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",
"actions": "Actions",
"viewTriggers": "Triggers",
"viewTriggersTitle": "View trigger bindings for this workload"
},
"common": {
"cancel": "Cancel",
"confirm": "Confirm",
"close": "Close",
"toggle": "Toggle",
"dismissNotification": "Dismiss notification",
"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": {
"eyebrowSuffix": "GLOBAL",
"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",
"eyebrowSuffix": "STALE",
"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": {
"containers": "Containers",
"proxies": "Proxies",
"recentErrors": "Recent Errors"
},
"daemons": {
"title": "Daemons",
"notReachable": "{provider} is not reachable.",
"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",
"lastFired": "Last fired",
"lastFiredNever": "Never fired",
"scheduleStatus": "Schedule status",
"scheduleStatusSub": "Operational state of the internal scheduler for this trigger. Fire-now skips ahead of the next natural window and resets the cadence to start counting from now.",
"fireNow": "Fire now",
"fireNowTitle": "Dispatch this trigger immediately and reset the next-fire window.",
"fireNowDisabledTitle": "Bind at least one workload before firing.",
"firing": "Firing…",
"fireConfirmTitle": "Fire schedule trigger?",
"fireConfirmMessage": "Trigger \"{name}\" will fire immediately and fan out to its {count} bound workload(s). The next natural fire window will be one full interval from now.",
"fireConfirm": "Fire now",
"fireResult": "Fired · deployed {deployed}/{bindings} · errored {errored}"
},
"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.",
"browseImages": "Browse",
"browseImagesHint": "Pick an image from a configured registry instead of typing the reference.",
"browseImagesTitle": "Select an image",
"browseImagesSearch": "Search images…",
"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.",
"branchPattern": "Branch pattern (preview deploys)",
"branchPatternPlaceholder": "feat/* or * for any branch",
"branchPatternHint": "When set, any push to a matching branch spawns a per-branch preview deploy. Leave empty to disable previews.",
"manualNote": "Manual triggers carry no config. They fire only via the workload's Deploy button or POST /workloads/{id}/deploy.",
"scheduleNote": "Fires on a fixed interval driven by Tinyforge's internal scheduler. No external webhook is required — enable the webhook ingress below only if a CI also needs to fire it on demand.",
"intervalPresets": "Quick presets",
"intervalPreset": {
"hourly": "Hourly",
"daily": "Daily",
"weekly": "Weekly"
},
"interval": "Interval",
"intervalHint": "Go duration (e.g. \"30m\", \"6h\", \"24h\", \"168h\"). Minimum 1 minute.",
"scheduleReference": "Pinned reference (optional)",
"scheduleReferencePlaceholder": "stable",
"scheduleReferenceHint": "Optional tag, branch, or revision the source plugin should re-pull each fire. Leave empty to let the source use its default.",
"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",
"wizard": {
"stepBasics": "Basics",
"stepConfigure": "Configure",
"stepTrigger": "Trigger",
"stepReview": "Review",
"next": "Next",
"back": "Back"
},
"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",
"fieldRequired": "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": "Edit as 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",
"imageCpuHint": "Cores, 0 = ∞",
"imageMemory": "Memory limit",
"imageMemoryHint": "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.",
"dockerfileHeader": "dockerfile source · build from a git repo",
"dockerfileBuildEyebrow": "build · dockerfile",
"dockerfileContextPath": "Build context",
"dockerfileContextPathPlaceholder": "(empty = repo root)",
"dockerfilePath": "Dockerfile path",
"dockerfilePort": "Container port",
"dockerfilePortRequired": "Container port is required — pick the port your app listens on (165535).",
"dockerfileFoot": "Tinyforge clones the repo, builds the image from the Dockerfile, and runs one container. Env vars and volumes live in the 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 <code>.md</code> files as HTML pages.",
"sourceReportCommitStatus": "Report commit status",
"sourceReportCommitStatusDesc": "— report deploy status back to the Git provider as a commit status on the deployed commit.",
"staticFoot": "The webhook secret for git push triggers lives on the workload's Webhook panel after creation.",
"staticDetectProvider": "Detect",
"staticDetectedOk": "Detected: {provider}",
"staticDetectedFailed": "Detection failed: {error}",
"staticTestConnection": "Test connection",
"staticConnectionOk": "Connected",
"staticConnectionFailed": "Connection failed: {error}",
"staticBrowseRepos": "Browse",
"staticBrowseBranches": "Browse branches",
"staticBrowseFolders": "Browse folders",
"staticPickerRepoTitle": "Select repository",
"staticPickerRepoPlaceholder": "Filter repositories…",
"staticPickerBranchTitle": "Select branch",
"staticPickerBranchPlaceholder": "Filter branches…",
"staticFolderRoot": "/ (root)",
"staticFolderSelectedPrefix": "Selected folder:",
"staticTreeLoading": "Loading folder tree…",
"staticTreeEmpty": "No folders found in this branch.",
"staticDenoAutoDetected": "Auto-detected an <code>api/</code> folder — switched to Deno mode.",
"imageConflictTag": "IMAGE IN USE",
"imageConflictChecking": "Checking for conflicts…",
"imageConflictHeading": "{count} workload(s) already use this image:",
"imageConflictOpenBtn": "Open",
"imageConflictAcknowledgeNote": "If this is intentional (for example a separate stage), continue to create a new workload.",
"imageConflictBlockedSubmit": "Conflicts detected for this image — review the list above, then click Create again to proceed.",
"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…",
"submitAnyway": "Forge anyway",
"unsavedChanges": "You have unsaved changes to this app. Leave without creating it?",
"unsavedChangesTitle": "Unsaved changes",
"unsavedChangesConfirm": "Leave",
"errors": {
"detectionFailed": "Couldn't detect a Git provider at that URL. Check the base URL is correct and reachable.",
"connectionFailed": "Couldn't reach the repository. Check the provider URL, owner/repo, and access token (for private repos).",
"reposFailed": "Couldn't list repositories. Check the base URL and access token.",
"branchesFailed": "Couldn't list branches. Check the repository and access token.",
"treeFailed": "Couldn't load the folder tree. Check the repository, branch, and access token.",
"sourceConfigInvalid": "Source config is not valid JSON.",
"triggerBindUnknown": "unknown error",
"createFailed": "Workload create failed.",
"inspectFailed": "Couldn't inspect that image — make sure it's pulled locally and the reference is correct."
},
"imageInspect": "Inspect",
"imageInspectHint": "Pulls port + healthcheck from the image so you don't have to type them.",
"imageInspectOk": "Inspected — port + healthcheck filled.",
"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."
},
"manifest": {
"title": "Manifest",
"name": "Name",
"source": "Source",
"trigger": "Trigger",
"publicFace": "Public face",
"unnamed": "(unnamed)",
"registryPublic": "public registry",
"folderRoot": "root",
"triggerManual": "Manual only",
"internalOnly": "Internal only"
}
},
"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",
"runtimeState": {
"title": "Sync status",
"sub": "Last successful sync of the source repo and the current container state.",
"status": "Status",
"lastCommit": "Last commit",
"lastSync": "Last sync",
"container": "Container",
"neverDeployed": "Never deployed. Click Deploy to publish for the first time.",
"loading": "Loading runtime state…"
},
"storage": {
"title": "Persistent storage",
"sub": "Mounted at /app/data inside the container.",
"used": "Used",
"limit": "Limit",
"unlimited": "unlimited",
"unavailable": "Usage probe unavailable (container may be stopped).",
"loading": "Computing usage…"
},
"buildLog": {
"title": "Build log",
"sub": "Live tail of the Docker daemon's build output.",
"clear": "Clear"
},
"notifications": {
"title": "Notification routes",
"sub": "Multi-destination fan-out for deploy/build events. Falls back to the workload's legacy URL when empty.",
"loading": "Loading routes…",
"empty": "No per-workload notification routes configured. Add one to get a per-channel destination.",
"addFirst": "Add first route",
"add": "Add route",
"edit": "Edit route",
"delete": "Delete route",
"name": "Name",
"namePlaceholder": "Slack #alerts",
"url": "Webhook URL",
"secret": "Signing secret",
"secretPlaceholder": "Optional — receiver verifies HMAC if set",
"secretEditPlaceholder": "Leave empty to keep the existing secret",
"secretHint": "HMAC-SHA256 over the request body, sent as X-Hub-Signature-256.",
"eventTypes": "Event types",
"eventTypesPlaceholder": "deploy_failure,build_failure (empty = all)",
"eventTypesHint": "Comma-separated allow-list. Empty means every event fires this route.",
"enabled": "Enabled",
"save": "Save route",
"saving": "Saving…",
"cancel": "Cancel",
"allEvents": "all events",
"signed": "signed",
"disabled": "disabled",
"confirmDeleteTitle": "Delete notification route?",
"confirmDeleteMessage": "This route will stop firing immediately. The workload's legacy notification URL (if set) will resume catching events when no routes match."
},
"toolbar": {
"stop": "Stop",
"start": "Start",
"openSite": "Open",
"more": "More"
},
"liveBadge": {
"running": "RUNNING",
"transitioning": "TRANSITIONING",
"stopped": "STOPPED",
"notDeployed": "NOT DEPLOYED",
"mixed": "MIXED · {running}/{total} RUNNING"
},
"stats": {
"title": "Resource usage",
"sub": "CPU and memory of the running container.",
"subMany": "CPU and memory of each of the {count} containers."
},
"webhooks": {
"title": "Webhook bindings",
"sub": "Triggers wired to this app — manage URLs and signing on the trigger detail page.",
"openTrigger": "Open trigger",
"disabled": "disabled",
"empty": "No triggers bound to this app."
},
"errors": {
"stopFailed": "Stop failed.",
"stopNothing": "Nothing to stop — no running container.",
"stopAllFailed": "Stop failed — all containers refused to stop.",
"startFailed": "Start failed.",
"startNothing": "Nothing to start — deploy first to create a container.",
"startAllFailed": "Start failed — all containers refused to start.",
"runtimeStateFailed": "Failed to load runtime state.",
"storageFailed": "Failed to load storage usage."
},
"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 <code>.md</code> as HTML.",
"editDockerfileHeader": "dockerfile source · build from a git repo",
"editDockerfileBuildEyebrow": "build · dockerfile",
"editDockerfileContextPath": "Build context",
"editDockerfileContextPathPlaceholder": "(empty = repo root)",
"editDockerfilePath": "Dockerfile path",
"editDockerfilePort": "Container port",
"editTestConnection": "Test connection",
"editTestConnectionOk": "Connection OK",
"editTestConnectionFailed": "Connection failed: {error}",
"editTestConnectionUnknownError": "Unknown error",
"overrideKeyUnitSingular": "KEY",
"overrideKeyUnitPlural": "KEYS",
"editTestConnectionIncomplete": "Fill provider, base URL, owner, and name first.",
"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 <code>parent_workload_id</code> on a workload to build a chain. Image-source children can promote the parent's currently-running tag with one click.",
"previews": {
"title": "Preview environments",
"subEmpty": "no active previews",
"subCountOne": "1 active preview",
"subCount": "{count} active previews",
"tag": "Preview",
"tagTitle": "Per-branch preview deploy of this workload",
"armedEmpty": "No active previews — push to a branch matching",
"noneEmpty": "No active previews yet.",
"open": "Open",
"noUrl": "no public URL",
"teardown": "Tear down",
"teardownTitle": "Tear down preview?",
"teardownMessage": "This deletes the preview for branch \"{name}\" and removes its containers and proxy routes. Pushing to the branch again will recreate it.",
"teardownConfirm": "Tear down",
"teardownPending": "Tearing down…",
"teardownFailed": "Teardown failed",
"stateRunning": "Running",
"statePending": "Pending",
"stateStopped": "Stopped",
"stateUnknown": "Unknown",
"hint": "Previews are created automatically when a push lands on a branch matching a git trigger's <code>branch_pattern</code>, and torn down when the branch is deleted. Each gets its own slug-prefixed subdomain."
},
"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"
}
}
}
}
}