chain-studio/lib/l10n/app_localizations_en.dart
flemming-it c11461b0f9 feat(studio): trust pass — friendly errors + store clarity + MCP i18n
Second half of the May-2026 trust pass. Drops the wall of
gRPC trailers from every error surface and makes the Store
honest about what is and isn't installable.

Friendly errors:

- New `friendlyError(Object, AppLocalizations)` mapper turns
  GrpcError + arbitrary throwables into a localised headline,
  optional recovery hint, and a verbatim detail string kept
  behind a "Show details" expander. Duck-typed on `.code` /
  `.message` so Studio doesn't have to depend on package:grpc
  directly.
- `FaiErrorBox` gains an `error:` constructor that runs the
  mapper. Every call site that used to render
  `snap.error.toString()` (flows, welcome, store) switches to
  it.
- 9 .arb entries per locale cover the gRPC codes we actually
  emit (INVALID_ARGUMENT, NOT_FOUND, ALREADY_EXISTS,
  PERMISSION_DENIED, FAILED_PRECONDITION, INTERNAL,
  UNAVAILABLE, UNAUTHENTICATED) plus copy/details affordances.
- `test/friendly_error_test.dart` — 6 unit tests for the
  mapper. Covers the mapping table, locale-switching, and the
  non-gRPC fallback so future regressions show up in CI.

Capability discovery:

- New `HubService.allCapabilities()` reads the kind-aware
  capability list (wasm + builtin + federated) and returns a
  Dart-side `CapabilityInfo` value type. The flow page's
  missing-dependency check uses it so `system.approval` and
  federated MCP/n8n tools count as "available" — fixes the
  Run button staying disabled forever.
- `HubService.listModules()` filters to kind=wasm so the
  Modules page doesn't sprout synthetic "system" entries that
  the operator can't uninstall.

Store clarity:

- New "Installable only" filter, on by default. Roughly 2/3
  of seed entries currently carry `status: planned`; the
  default view stops being noise.
- Featured-strip cards for planned modules now show a
  "Coming soon" pill instead of an empty action area.
- Main-grid cards for non-installable modules dim to 60%
  opacity so the eye lands on actionable cards first.
- Detail-sheet "Nicht installierbar" tooltip → inline hint
  box. The reason is visible without hovering.

MCP localisation:

- `_kMcpSuggestions` no longer holds 11 hardcoded English
  description strings. The `description` field is replaced
  with a `resolveDescription(AppLocalizations)` lookup that
  switches on the suggestion `name` to read the matching
  `mcpSuggestion*Desc` .arb key. EN + DE shipped.
- New `FaiEnBadge` widget renders a small `[EN]` pill when
  the active locale isn't English. Used next to MCP /
  federated store entries' tagline + description because
  the server supplies them in English and we can't translate
  on the fly yet — the badge is the honest signal until the
  planned `studio.translate` plugin lands.

Plus housekeeping: removed the unused `_keepImport` lint
escape in the test and the dangling library doc-comment in
`format.dart`.

Signed-off-by: flemming-it <sf@flemming.it>
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
2026-05-25 12:36:14 +02:00

2079 lines
50 KiB
Dart
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.

// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appTitle => 'F∆I Studio';
@override
String get navWelcome => 'Welcome';
@override
String get welcomeHeroTitle => 'F∆I Platform';
@override
String get welcomeHeroSubtitle =>
'Deterministic workflow engine for AI-assisted document processing in regulated environments.';
@override
String get welcomePillarHubTitle => 'Hub';
@override
String get welcomePillarHubBody =>
'One Rust binary that loads modules and runs flows. SQLite for state. No Docker, no message broker, no background services to babysit.';
@override
String get welcomePillarModuleTitle => 'Module';
@override
String get welcomePillarModuleBody =>
'A small WebAssembly component that does one thing. Declares the network endpoints, files, and environment variables it touches; the hub enforces the list.';
@override
String get welcomePillarFlowTitle => 'Flow';
@override
String get welcomePillarFlowBody =>
'A YAML file that chains modules together. Inputs go in, modules run in declared order, outputs come out — every step recorded in a hash-chained audit log.';
@override
String get welcomeTrustHeader => 'TRUST POSTURE';
@override
String get welcomeTrustSandboxTitle => 'Sandbox by default';
@override
String get welcomeTrustSandboxBody =>
'Every module ships with an explicit permission list — network endpoints, files, environment variables. The hub enforces it; nothing reaches outside the sandbox without operator approval.';
@override
String get welcomeTrustAuditTitle => 'Tamper-evident audit log';
@override
String get welcomeTrustAuditBody =>
'Flow runs, install / uninstall actions, approval decisions — all of them write into a hash-chained audit log. The Doctor page verifies the chain end-to-end on every load.';
@override
String get welcomeTrustAirgapTitle => 'Air-gap ready';
@override
String get welcomeTrustAirgapBody =>
'The whole hub fits in a single binary that runs on Linux, macOS and Windows. Once a module is installed, the flow it powers runs without further network access — perfect for regulated environments.';
@override
String get welcomeDocsHeader => 'DOCUMENTATION';
@override
String get welcomeDocsBody =>
'Operator-facing explainers, rendered inline. No browser, no external link — air-gap-friendly.';
@override
String get welcomeDocArchitectureTitle => 'Architecture in one paragraph';
@override
String get welcomeDocArchitectureBlurb =>
'Hub, Module, Flow — and how the three fit together.';
@override
String get welcomeDocSecurityTitle => 'Sandbox model';
@override
String get welcomeDocSecurityBlurb =>
'What a module declares, what the operator caps, what the hub enforces.';
@override
String get welcomeDocAuditTitle => 'Tamper-evident audit log';
@override
String get welcomeDocAuditBlurb =>
'How the hash chain works and why WORM-1 is enough for KRITIS.';
@override
String get welcomeDocFlowsTitle => 'Flow composition';
@override
String get welcomeDocFlowsBlurb =>
'YAML basics, templating reference, the extract→summarize example.';
@override
String get welcomeDocClose => 'Close';
@override
String welcomeDocFailedToLoad(String error) {
return 'Could not load documentation: $error';
}
@override
String get welcomeChecklistHeader => 'GETTING STARTED';
@override
String get welcomeChecklistBody =>
'Four steps to a working hub. Live state — refresh after changing anything.';
@override
String get welcomeChecklistAi => 'Configure System AI';
@override
String get welcomeChecklistAiHint =>
'Settings → System AI. Required for the AI search and inline failure explanations.';
@override
String get welcomeChecklistMcp => 'Add a public capability source';
@override
String get welcomeChecklistMcpHint =>
'Store → Today hero, or Settings → MCP Clients. DeepWiki and Semgrep are one click each.';
@override
String get welcomeChecklistModule => 'Install a text module';
@override
String get welcomeChecklistModuleHint =>
'Store → search \"text\" → Install. Try text.extract for PDFs and DOCX.';
@override
String get welcomeChecklistFlow => 'Run a saved flow';
@override
String get welcomeChecklistFlowHint =>
'Flows → pick one → Run. The bundled extract-summarize.yaml is a good first run.';
@override
String get welcomeChecklistAllDone => 'All four steps complete.';
@override
String get welcomeChecklistDismiss => 'Hide checklist';
@override
String get welcomeChecklistRefresh => 'Refresh';
@override
String get welcomeChecklistRefreshing => 'Checking…';
@override
String get welcomeChecklistDone => 'Done';
@override
String get welcomeChecklistTodo => 'Pending';
@override
String get navDoctor => 'Doctor';
@override
String get navModules => 'Modules';
@override
String get navStore => 'Store';
@override
String get navFlows => 'Flows';
@override
String get navAudit => 'Audit';
@override
String get navApprovals => 'Approvals';
@override
String get connectionConnected => 'connected';
@override
String get connectionUnreachable => 'unreachable';
@override
String get connectionConnecting => 'connecting…';
@override
String get connectionStartHub => 'Start hub';
@override
String get buttonCancel => 'Cancel';
@override
String get buttonSave => 'Save';
@override
String get buttonClose => 'Close';
@override
String get buttonRetry => 'Retry';
@override
String get buttonRefresh => 'Refresh';
@override
String get buttonInstall => 'Install';
@override
String get buttonUninstall => 'Uninstall';
@override
String get buttonDetails => 'Details';
@override
String get buttonAdd => 'Add';
@override
String get buttonRemove => 'Remove';
@override
String get buttonOpen => 'Open';
@override
String get buttonExplain => 'Explain';
@override
String get buttonReadDocs => 'Read docs';
@override
String get buttonCopy => 'Copy to clipboard';
@override
String get buttonCopied => 'Copied';
@override
String get buttonShowDetails => 'Show details';
@override
String get buttonHideDetails => 'Hide details';
@override
String get storeInstallableOnly => 'Installable only';
@override
String get storePillComingSoon => 'Coming soon';
@override
String storeNotInstallableInline(String status) {
return 'This module is on the roadmap (status: $status). It has no installable bundle yet — follow the repository for updates.';
}
@override
String get mcpSuggestionDeepwikiDesc =>
'Public HTTPS — GitHub repo documentation search (AI-powered). No Node, no API key.';
@override
String get mcpSuggestionSemgrepDesc =>
'Public HTTPS — security scanning for code vulnerabilities. No Node, no API key.';
@override
String get mcpSuggestionFilesystemDesc =>
'Anthropic — read / write files in /tmp. Edit the path before saving.';
@override
String get mcpSuggestionFetchDesc =>
'Anthropic — fetch arbitrary HTTP(S) URLs as markdown.';
@override
String get mcpSuggestionGithubDesc =>
'Anthropic — issues, PRs, repo files. Needs a GitHub PAT in the env var.';
@override
String get mcpSuggestionPuppeteerDesc =>
'Anthropic — headless browser automation for screenshots / scraping.';
@override
String get mcpSuggestionPostgresDesc =>
'Anthropic — read-only SQL queries against a Postgres database. Edit the URL.';
@override
String get mcpSuggestionSqliteDesc =>
'Anthropic — query a local SQLite file. Edit the path.';
@override
String get mcpSuggestionBraveSearchDesc =>
'Anthropic — web search via Brave. API key required.';
@override
String get mcpSuggestionMemoryDesc =>
'Anthropic — persistent knowledge graph for an agent\'s memory.';
@override
String get mcpSuggestionTimeDesc =>
'Anthropic — current time + timezone conversion.';
@override
String get mcpServerEnLanguageBadge =>
'Server text in English — Studio shows it as supplied.';
@override
String get settingsTitle => 'Hub endpoint';
@override
String get settingsHost => 'Host';
@override
String get settingsPort => 'Port';
@override
String get settingsTls => 'TLS';
@override
String get settingsSaveAndConnect => 'Save & connect';
@override
String get settingsLanguage => 'Language';
@override
String get settingsLanguageHint =>
'App language. Affects every page; bilingual store entries also flip via this toggle.';
@override
String get settingsHubEndpointHint =>
'Where should Studio connect? Default is the local hub at 127.0.0.1:50051.';
@override
String get settingsTlsSubtitle => 'https/grpc-secure';
@override
String get settingsPortError => 'port must be 165535';
@override
String get channelsBlurb =>
'Switch hub channel (writes ~/.fai/current-channel and restarts the daemon). Connect just changes Studio\'s wire.';
@override
String get channelsActionsTooltip => 'Channel actions';
@override
String get systemAiHeader => 'SYSTEM AI';
@override
String get systemAiEnabled => 'enabled';
@override
String get systemAiOff => 'off';
@override
String get systemAiEdit => 'Edit…';
@override
String get systemAiConfigure => 'Configure…';
@override
String get systemAiOffBlurb =>
'Off — failure explanations on the audit page are disabled. Click Configure… to pick a provider (Ollama / OpenAI / LM Studio / vLLM / Custom). Operator config is rewritten in place; no daemon restart needed.';
@override
String mcpServersCount(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'servers',
one: 'server',
);
return '$n $_temp0';
}
@override
String get mcpRediscoverTooltip => 'Re-run discovery';
@override
String get mcpAddTooltip => 'Add MCP server';
@override
String get mcpRemoveTooltip => 'Remove server';
@override
String mcpToolsCount(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'tools',
one: 'tool',
);
return '$n $_temp0';
}
@override
String n8nEndpointsCount(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'endpoints',
one: 'endpoint',
);
return '$n $_temp0';
}
@override
String n8nWorkflowsCount(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'workflows',
one: 'workflow',
);
return '$n $_temp0';
}
@override
String get n8nRediscoverTooltip => 'Re-run discovery';
@override
String get n8nAddTooltip => 'Add n8n endpoint';
@override
String get n8nRemoveTooltip => 'Remove endpoint';
@override
String get addMcpTitle => 'Add MCP server';
@override
String get addMcpIntro =>
'Tools the server advertises via `tools/list` appear in the Store as `mcp.<name>.<tool>` capabilities.';
@override
String get addMcpSuggestionsLabel => 'Suggested servers — click to pre-fill';
@override
String get addMcpNameLabel => 'Name (no dots)';
@override
String get addMcpNameHint => 'filesystem';
@override
String get addMcpEndpointLabel => 'Endpoint';
@override
String get addMcpEndpointHint => 'http://127.0.0.1:3001/mcp';
@override
String get addMcpApiKeyLabel => 'API key env var (optional)';
@override
String get addMcpApiKeyHint => 'MCP_FILESYSTEM_TOKEN';
@override
String get addMcpNotesLabel => 'Notes (optional)';
@override
String get addMcpAddButton => 'Add + discover';
@override
String get addN8nTitle => 'Add n8n endpoint';
@override
String get addN8nIntro =>
'Active workflows surface in the Store as `n8n.<name>.<workflow_slug>` capabilities.';
@override
String get addN8nNameLabel => 'Name (no dots)';
@override
String get addN8nNameHint => 'ops';
@override
String get addN8nBaseUrlLabel => 'Base URL (without /api/v1)';
@override
String get addN8nBaseUrlHint => 'https://n8n.example.com';
@override
String get addN8nApiKeyLabel => 'API key env var (optional)';
@override
String get addN8nApiKeyHint => 'N8N_OPS_KEY';
@override
String get addN8nNotesLabel => 'Notes (optional)';
@override
String get addN8nAddButton => 'Add + discover';
@override
String addedMcpToast(String name) {
return 'Added MCP server \"$name\".';
}
@override
String addedN8nToast(String name) {
return 'Added n8n endpoint \"$name\".';
}
@override
String addFailedToast(String error) {
return 'Add failed: $error';
}
@override
String removeFailedToast(String error) {
return 'Remove failed: $error';
}
@override
String refreshFailedToast(String error) {
return 'Refresh failed: $error';
}
@override
String get channelsHeader => 'CHANNELS';
@override
String get channelsActive => 'active';
@override
String get channelsRunning => 'running';
@override
String get channelsStopped => 'stopped';
@override
String get channelsConnect => 'Connect Studio to this channel';
@override
String get channelsSwitch => 'Make this the active channel';
@override
String get channelsEnableAutostart => 'Enable autostart at login';
@override
String get channelsDisableAutostart => 'Disable autostart';
@override
String get storeSearchHint =>
'Search modules — name, tagline, tag, capability…';
@override
String get storeAskHint =>
'What do you need? Try \"a module that reads tabular data\" or just type a keyword.';
@override
String get storeAskSubmit => 'Search';
@override
String get storeAskAi => 'Ask the AI';
@override
String get storeAskClear => 'Clear question';
@override
String get storeAskAiThinking => 'Looking through modules…';
@override
String get storeAskAiUnavailable =>
'Configure a System AI in Settings to ask questions in plain language.';
@override
String get storeAskNoMatches =>
'No matches yet — refine the question or browse the grid below.';
@override
String get storeAskAnswerLabel => 'AI suggestion';
@override
String get storeAskAnswerWhy => 'Why this matches';
@override
String get storeAskMatchPill => 'match';
@override
String get storeFilterLabel => 'Filter';
@override
String storeFilterActiveCount(int n) {
return '$n active';
}
@override
String get storeFilterReset => 'Reset filters';
@override
String get storeFilterApply => 'Done';
@override
String get storeFilterStatusGroup => 'Status';
@override
String get storeFilterSourceGroup => 'Source';
@override
String get storeFilterInstalledGroup => 'Show only installed';
@override
String get storeFeatured => 'FEATURED';
@override
String get storeFeaturedHint => 'curated picks · click for details';
@override
String get storeNoMatches => 'No matches';
@override
String get storeNoMatchesHint =>
'Adjust the filters or clear the search to see all entries.';
@override
String get storeClearFilters => 'Clear filters';
@override
String get storeStatusAll => 'All';
@override
String get storeStatusPublished => 'stable';
@override
String get storeStatusAlpha => 'experimental';
@override
String get storeStatusPlanned => 'coming soon';
@override
String get storeInstalledOnly => 'Installed';
@override
String get storeCategoryLabel => 'Category';
@override
String get storeCategoryAll => 'All categories';
@override
String get storeCategoryBridge => 'Connectors';
@override
String get storeCategoryDebug => 'Sample modules';
@override
String get storeCategoryText => 'Text';
@override
String get storeCategoryLlm => 'AI models';
@override
String get storeCategorySystem => 'System';
@override
String get storeCategoryAuth => 'Authentication';
@override
String get storeCategoryStorage => 'Storage';
@override
String get storeCategoryChannel => 'Channels';
@override
String get storeCategoryWeb => 'Web';
@override
String get storeCategoryOrchestrator => 'Orchestration';
@override
String storeNResults(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 's',
one: '',
);
return '$n result$_temp0';
}
@override
String get storeReloadTooltip => 'Reload';
@override
String get storeTodayBadge => 'TODAY';
@override
String get storeTodayDeck => 'From the F∆I editor';
@override
String get storeTodayDismissTooltip => 'Hide for this session';
@override
String get storePillInstalled => 'installed';
@override
String get storePillUpdate => 'update';
@override
String storeUpdateButton(String version) {
return 'Update to $version';
}
@override
String storeUpdateTooltip(String installed, String best) {
return 'Installed $installed — store has v$best';
}
@override
String get storeNoTagline => '(no tagline)';
@override
String get storeUninstallTitle => 'Uninstall module?';
@override
String storeUninstallBody(String name) {
return 'Removes $name from this hub. Flows that reference it will fail at the next run.';
}
@override
String storeInstalledToast(String name, String version) {
return '$name v$version installed.';
}
@override
String storeInstallFailedToast(String error) {
return 'Install failed: $error';
}
@override
String storeUninstalledToast(String name, String version) {
return '$name v$version uninstalled.';
}
@override
String storeUninstallFailedToast(String error) {
return 'Uninstall failed: $error';
}
@override
String storeOpenBrowserFailed(String error) {
return 'Could not open browser: $error';
}
@override
String get storeSectionTags => 'Tags';
@override
String get storeSectionRequiredCapabilities => 'Required capabilities';
@override
String get storeSectionRequiredHostServices => 'Required host services';
@override
String get storeSectionScreenshots => 'Screenshots';
@override
String get storeSectionDocumentation => 'Documentation';
@override
String get storeSectionSource => 'Source';
@override
String get storeSectionPermissions => 'Declared permissions';
@override
String get storeSectionDirectory => 'Module directory';
@override
String get storeSectionPermissionsNone => '(none — pure-computation module)';
@override
String get storeInstallingButton => 'Installing…';
@override
String get storeNotInstallable => 'Not installable';
@override
String storeNotInstallableTooltip(String status) {
return 'Status \"$status\" — install path not yet wired.';
}
@override
String storeInstallProgressTitle(String name) {
return 'Installing $name';
}
@override
String get storeInstallProgressBody => 'Fetching, verifying, unpacking…';
@override
String get storeLoadDocs => 'Load documentation';
@override
String get storeDocsHint =>
'README rendered inline; uses the hub\'s registry token when needed.';
@override
String get storeDocsFetching => 'Fetching README…';
@override
String get storeOpenRepoButton => 'Open repository in browser';
@override
String get storeDocsErrorNotFound =>
'This module is not in the hub\'s store index.';
@override
String get storeDocsErrorNoDocs =>
'No repository URL is recorded for this module.';
@override
String get storeDocsErrorAuth =>
'The registry requires authentication to fetch this README. Set FAI_REGISTRY_TOKEN in the daemon\'s environment and restart the hub.';
@override
String storeDocsErrorNetwork(String detail) {
return 'Network error while fetching the README. $detail';
}
@override
String get storeDocsErrorParse => 'README is not valid UTF-8.';
@override
String get storeDocsErrorGeneric => 'Could not load README.';
@override
String get storeSourceLabel => 'Source';
@override
String get storeSourceAll => 'All sources';
@override
String get storeSourceNative => 'Native';
@override
String get storeSourceMcp => 'MCP';
@override
String get storeSourceN8n => 'n8n';
@override
String get storeProvenanceNative => 'native';
@override
String storeProvenanceMcp(String provider) {
return 'mcp · $provider';
}
@override
String storeProvenanceN8n(String provider) {
return 'n8n · $provider';
}
@override
String get storeRecommendedTitle => 'Add a public source — one click';
@override
String get storeRecommendedBody =>
'Curated MCP servers reachable over HTTPS. No subprocess, no API key, no Node. Federated capabilities appear in the store immediately.';
@override
String get storeRecommendedAddTooltip => 'Add and discover';
@override
String get storeRecommendedAdding => 'Adding…';
@override
String get storeRecommendedDeepWikiTagline =>
'GitHub repo documentation search (AI-powered)';
@override
String get storeRecommendedSemgrepTagline =>
'Security scanning for code vulnerabilities';
@override
String storeProviderAddedToast(String name, int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'capabilities',
one: 'capability',
);
return 'Added $name$n federated $_temp0.';
}
@override
String storeProviderAddedZeroToast(String name) {
return '$name registered, but the server returned no capabilities yet. Open Settings → MCP Clients to retry discovery or check the server.';
}
@override
String storeProviderAddFailed(String name, String error) {
return 'Could not add $name: $error';
}
@override
String get auditTitle => 'Audit';
@override
String get auditNoEvents => 'No events yet';
@override
String get auditNoEventsHint =>
'Run a flow to populate the audit stream.\nEvents appear here within seconds.';
@override
String get auditFilterAll => 'all';
@override
String get auditFilterFlow => 'flow';
@override
String get auditFilterStep => 'step';
@override
String get auditFilterModule => 'module';
@override
String get auditClearLogTooltip => 'Clear log (local/dev channels only)';
@override
String auditClearedToast(int n, String channel) {
return 'Cleared $n events on channel \"$channel\". Chain reseeded with marker.';
}
@override
String auditClearFailed(String detail) {
return 'Clear failed: $detail';
}
@override
String get auditExplain => 'Explain';
@override
String get auditReask => 'Re-ask';
@override
String get auditAsking => 'Asking…';
@override
String get auditSystemAi => 'SYSTEM AI';
@override
String get auditCachedPill => 'cached';
@override
String auditOriginalLatency(int ms) {
return 'orig $ms ms';
}
@override
String auditLatency(int ms) {
return '$ms ms';
}
@override
String get auditRegenerateTooltip =>
'Regenerate — skip cache, ask the model again';
@override
String auditLiveStatus(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'events',
one: 'event',
);
return 'live · polling 2s · $n $_temp0';
}
@override
String auditDisconnected(String error) {
return 'disconnected · $error';
}
@override
String get auditHashChainVerified => 'hash chain verified';
@override
String get auditDetailHeader => 'DETAIL';
@override
String get auditAskingFull => 'Asking the configured System AI…';
@override
String get auditFixLabel => 'FIX';
@override
String auditCachedTooltipKnown(String at, String hits) {
return 'Served from the local cache. Originally generated on $at$hits.';
}
@override
String auditCachedTooltipUnknown(String hits) {
return 'Served from the local cache. Originally generated at unknown time$hits.';
}
@override
String auditCachedTooltipHits(int n) {
return ' · $n hits';
}
@override
String get auditConfigureSystemAi =>
'Configure System AI in Settings (Cmd+,)';
@override
String get auditClearDialogTitle => 'Clear audit log?';
@override
String get auditClearDialogBody =>
'Wipes every audit event on the active channel and seeds a fresh chain.reset marker carrying reviewer + reason. Refused on beta / production. Irreversible.';
@override
String get auditClearReviewerLabel => 'Reviewer';
@override
String get auditClearReasonLabel => 'Reason (recorded in chain.reset marker)';
@override
String get auditClearReasonHelper => 'Required — auditors will read this.';
@override
String get auditClearLogButton => 'Clear log';
@override
String get modulesTitle => 'Modules';
@override
String get modulesNoneTitle => 'No modules yet';
@override
String get modulesNoneHint =>
'Run `fai install <capability-name>` or check ~/.fai/modules/.';
@override
String get modulesReloadTooltip => 'Reload';
@override
String get modulesRecentActivity => 'RECENT ACTIVITY';
@override
String modulesRecentActivityHint(int n) {
return 'last $n install/uninstall events from the audit log';
}
@override
String get modulesActivityInstalled => 'installed';
@override
String get modulesActivityUninstalled => 'uninstalled';
@override
String get modulesCapabilitiesLabel => 'Capabilities';
@override
String get modulesUninstallTitle => 'Uninstall module?';
@override
String modulesUninstallBody(String name, String version) {
return 'Removes $name v$version from this hub. Flows that reference it will fail at the next run. A `module.uninstalled` audit event is recorded.';
}
@override
String modulesUninstalledToast(String name, String version) {
return 'Uninstalled $name v$version.';
}
@override
String modulesUninstallFailed(String error) {
return 'Uninstall failed: $error';
}
@override
String get modulesUninstalling => 'Uninstalling…';
@override
String moduleSheetFailedToLoad(String error) {
return 'Failed to load: $error';
}
@override
String get moduleSheetCapabilities => 'Capabilities';
@override
String get moduleSheetPermissions => 'Declared permissions';
@override
String get moduleSheetNoPermissions => '(none — pure-computation module)';
@override
String get flowsTitle => 'Flows';
@override
String get flowsReloadTooltip => 'Reload';
@override
String get flowsNoneTitle => 'No saved flows';
@override
String get flowsNoneHint =>
'Save a flow with `fai admin flows save <name> <flow.yaml>` and it shows up here.';
@override
String flowsRunDialogTitle(String name) {
return 'Run $name';
}
@override
String get flowsRunDialogBody =>
'One field per declared input. Pick a file for bytes-shaped inputs; type plain text for the rest. The Run button enables when every required input is set.';
@override
String get flowsLoadingDefinition => 'Loading inputs…';
@override
String get flowsDefinitionFailed => 'Could not load flow definition';
@override
String get flowsNoInputs =>
'This flow declares no inputs. Click Run to start.';
@override
String get flowsTypeBytesPick => 'Choose file…';
@override
String get flowsTypeBytesChange => 'Change file';
@override
String get flowsTypeBytesNoFile => 'No file selected';
@override
String flowsTypeBytesSize(int n) {
return '$n bytes';
}
@override
String get flowsRunErrorTitle => 'Flow run failed';
@override
String flowsFileReadFailed(String path, String error) {
return 'Could not read \"$path\": $error';
}
@override
String get flowsMissingModulesLabel => 'Needs:';
@override
String get flowsRunDisabledTooltip => 'Install the missing modules first.';
@override
String flowsInstallAllButton(int n) {
return 'Install all ($n)';
}
@override
String get flowsInstallPillTooltip => 'Click to install';
@override
String get flowsInstallDepsTitle => 'Install dependencies';
@override
String get flowsInstallDepsBody =>
'Walking through the missing modules in order. Existing installs are left alone.';
@override
String get flowsInstallStatusPending => 'Pending';
@override
String get flowsInstallStatusInstalling => 'Installing…';
@override
String flowsInstallStatusDone(String version) {
return 'Installed v$version';
}
@override
String get flowsInstallStatusFailed => 'Failed';
@override
String flowsInstallStatusFailedDetail(String error) {
return '$error';
}
@override
String get flowsInstallDepsAllDone => 'All dependencies installed.';
@override
String flowsInstallDepsAnyFailed(int ok, int failed) {
return '$ok installed, $failed failed.';
}
@override
String get flowsInstallAuthWallButton => 'Add registry token';
@override
String get flowsOpenInEditorTooltip =>
'Open the YAML file in your default editor';
@override
String flowsOpenInEditorFailed(String error) {
return 'Could not open file: $error';
}
@override
String get registryCredentialsHeader => 'REGISTRY CREDENTIALS';
@override
String get registryCredentialsBlurb =>
'Token used to download .fai modules from a registry behind a signin wall (Forgejo, GitHub-private). Stored at ~/.fai/registry-token, mode 0600. The FAI_REGISTRY_TOKEN env var still wins when set.';
@override
String registryTokenStatusConfigured(int chars) {
return 'Configured ($chars chars)';
}
@override
String get registryTokenStatusNotSet => 'Not set';
@override
String get registryTokenFieldLabel => 'Token';
@override
String get registryTokenFieldHint =>
'Paste your Forgejo or GitHub personal access token';
@override
String get registryTokenSaveButton => 'Save';
@override
String get registryTokenClearButton => 'Clear';
@override
String get registryTokenStorageHint =>
'Saved locally to ~/.fai/registry-token. Never sent to a remote service.';
@override
String get registryTokenSavedToast => 'Registry token saved.';
@override
String get registryTokenClearedToast => 'Registry token cleared.';
@override
String registryTokenSaveFailedToast(String error) {
return 'Could not save: $error';
}
@override
String get maintenanceHeader => 'HUB MAINTENANCE';
@override
String get maintenanceResetBlurb =>
'Wipe operator state and start over with a clean hub. Stops every running daemon, atomically backs ~/.fai/ up, then recreates it with the binary, channel state, MCP / n8n / system-AI config, and registry token preserved. Restore by moving the backup directory back.';
@override
String get maintenanceKeepModulesTitle => 'Keep installed modules';
@override
String get maintenanceKeepModulesSubtitle =>
'Default behaviour wipes ~/.fai/modules/ so a fresh install can verify the module-fetch path end-to-end.';
@override
String get maintenanceKeepDataTitle => 'Keep audit log + saved flows';
@override
String get maintenanceKeepDataSubtitle =>
'Default behaviour wipes ~/.fai/data/ so the audit chain restarts from a fresh genesis and the bundled sample flows re-import on first daemon start.';
@override
String get maintenanceResetButton => 'Reset hub state';
@override
String get maintenanceResetInProgress => 'Resetting…';
@override
String get maintenanceResetConfirmTitle => 'Reset hub state?';
@override
String get maintenanceResetConfirmBody =>
'This stops every daemon, moves ~/.fai/ to a timestamped backup, and starts fresh. The backup is fully restorable. Continue?';
@override
String get maintenanceResetConfirmButton => 'Reset';
@override
String get welcomeChecklistAllSetTitle => 'You\'re set up.';
@override
String get welcomeChecklistAllSetBody => 'Three threads to pull on next:';
@override
String get welcomeChecklistNextAuditTitle => 'Read the audit log';
@override
String get welcomeChecklistNextAuditBody =>
'Every install, flow run, and approval lives in the hash-chained log. Open the Audit tab to see what your hub has been up to.';
@override
String get welcomeChecklistNextAuditButton => 'Open Audit';
@override
String get welcomeChecklistNextTodayTitle => 'Set up the daily Today story';
@override
String get welcomeChecklistNextTodayBody =>
'Run tools/today/propose.sh on a cron and the editorial card on the store auto-fills with operator-curated narratives.';
@override
String get welcomeChecklistNextTodayButton => 'Open Today docs';
@override
String get welcomeChecklistNextModuleTitle => 'Build your own module';
@override
String get welcomeChecklistNextModuleBody =>
'Generate a Rust+WASM scaffold with `fai new module <name>`. The module-sdk handles the WIT plumbing; you write a typed invoke.';
@override
String get welcomeChecklistNextModuleButton => 'Read the docs';
@override
String get auditGroupToday => 'TODAY';
@override
String get auditGroupYesterday => 'YESTERDAY';
@override
String get auditGroupThisWeek => 'EARLIER THIS WEEK';
@override
String get auditGroupOlder => 'OLDER';
@override
String get auditEventViewFlowRun => 'View flow run';
@override
String auditFlowRunDialogTitle(String flow) {
return 'Flow run · $flow';
}
@override
String auditFlowRunDialogSubtitle(int n, String execution) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'events',
one: 'event',
);
return '$n $_temp0 for execution $execution';
}
@override
String get approvalsSelectAll => 'Select all';
@override
String get approvalsClearSelection => 'Clear';
@override
String approvalsBatchSelected(int n) {
return '$n selected';
}
@override
String get approvalsBatchApprove => 'Approve all';
@override
String get approvalsBatchReject => 'Reject all';
@override
String approvalsBatchApproveDoneToast(int n) {
return '$n approvals approved.';
}
@override
String approvalsBatchRejectDoneToast(int n) {
return '$n approvals rejected.';
}
@override
String approvalsBatchPartialFailure(int ok, int failed) {
return '$ok done, $failed failed.';
}
@override
String get flowsRunButton => 'Run';
@override
String flowsRunningTitle(String name) {
return 'Running $name';
}
@override
String flowsResultTitle(String name) {
return '$name — result';
}
@override
String flowsErrorTitle(String name) {
return '$name — failed';
}
@override
String get flowsRunning => 'Flow running…';
@override
String get flowsNoOutputs => 'Flow completed with no declared outputs.';
@override
String get errGeneric => 'Something went wrong.';
@override
String get errInvalidArgument => 'Invalid input.';
@override
String get errInvalidArgumentHint => 'Check the form values and try again.';
@override
String get errNotFound => 'Not found.';
@override
String get errNotFoundHint =>
'The item you asked for isn\'t here — it may have been removed or never existed.';
@override
String get errAlreadyExists => 'Already exists.';
@override
String get errPermissionDenied => 'Permission denied.';
@override
String get errPermissionDeniedHint =>
'The hub\'s operator policy blocks this action. Check `~/.fai/config.yaml`.';
@override
String get errFailedPrecondition => 'Setup issue blocked this.';
@override
String get errFailedPreconditionHint =>
'The hub couldn\'t proceed because something it needs isn\'t ready. The detail below explains what.';
@override
String get errInternal => 'Unexpected internal error.';
@override
String get errInternalHint =>
'Check the hub logs at `~/.fai/logs/`. If this repeats, please report it.';
@override
String get errUnavailable => 'Hub not reachable.';
@override
String get errUnavailableHint =>
'Make sure `fai serve` (or `fai daemon start`) is running and the daemon endpoint matches Studio\'s settings.';
@override
String get errUnauthenticated => 'Authentication required.';
@override
String get errUnauthenticatedHint =>
'Set `FAI_REGISTRY_TOKEN` or sign in through Settings before retrying.';
@override
String get errCopyDetail => 'Copy detail';
@override
String get errDetailCopied => 'Detail copied to clipboard.';
@override
String get flowsOutputUnknown => 'Output payload variant not recognised.';
@override
String get flowsOutputBytesUnknownMime => 'binary';
@override
String get flowsOutputSaveAs => 'Save as…';
@override
String get flowsOutputSaveAsTitle => 'Save flow output';
@override
String flowsOutputSavedAt(String path) {
return 'Saved to $path';
}
@override
String flowsOutputSaveFailed(String error) {
return 'Save failed: $error';
}
@override
String get flowsOutputOpen => 'Open';
@override
String flowsOutputOpenFailed(String error) {
return 'Open failed: $error';
}
@override
String get approvalsTitle => 'Approvals';
@override
String get approvalsTabPending => 'Pending';
@override
String get approvalsTabHistory => 'History';
@override
String get approvalsInboxZero => 'Inbox zero';
@override
String get approvalsInboxHint =>
'All caught up. New `system.approval@^0` steps land here automatically.';
@override
String get approvalsReloadTooltip => 'Reload';
@override
String get approvalsPillPending => 'pending';
@override
String get approvalsPillApproved => 'approved';
@override
String get approvalsPillRejected => 'rejected';
@override
String get approvalsPillExpired => 'expired';
@override
String get approvalsPayloadPreview => 'PAYLOAD PREVIEW';
@override
String get approvalsApproveButton => 'Approve';
@override
String get approvalsRejectButton => 'Reject';
@override
String get approvalsRejectDialogTitle => 'Reject approval';
@override
String get approvalsRejectReasonLabel => 'Reason (recorded in audit log)';
@override
String approvalsApprovedToast(String flow, String step) {
return 'approved · $flow $step';
}
@override
String approvalsRejectedToast(String flow, String step) {
return 'rejected · $flow $step';
}
@override
String approvalsApproveFailed(String error) {
return 'approve failed: $error';
}
@override
String approvalsRejectFailed(String error) {
return 'reject failed: $error';
}
@override
String get approvalsHistoryEmpty => 'No history yet';
@override
String get approvalsHistoryEmptyHint =>
'Decided approvals (approved / rejected / expired) appear here.\nThe audit log keeps the full hash-chained record.';
@override
String get approvalsDialogDecided => 'decided';
@override
String get approvalsDialogCreated => 'created';
@override
String get approvalsDialogReason => 'reason';
@override
String get approvalsDialogPrompt => 'PROMPT';
@override
String approvalsExpiresIn(int minutes) {
return '${minutes}m';
}
@override
String approvalsExpiresInHours(int hours) {
return '${hours}h';
}
@override
String get doctorTitle => 'Doctor';
@override
String get doctorSummaryModules => 'Modules';
@override
String get doctorSummaryApprovals => 'Approvals';
@override
String get doctorSummaryAudit => 'Audit';
@override
String get doctorSummaryServices => 'Services';
@override
String doctorSummaryCapabilities(int n) {
return '$n capabilities';
}
@override
String get doctorSummaryPending => 'pending';
@override
String doctorSummaryChain(int verified, int total) {
return '$verified/$total chain';
}
@override
String get doctorSummaryDeclared => 'declared';
@override
String doctorModulesPanelSummary(int n, int m) {
return '$n modules · $m capabilities';
}
@override
String get doctorApprovalsNone => 'No pending approvals';
@override
String doctorApprovalsCount(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: '$n approvals awaiting review',
one: '1 approval awaiting review',
);
return '$_temp0';
}
@override
String get doctorApprovalsAttentionPill => 'attention';
@override
String get doctorServicesEmpty => 'No host services declared';
@override
String get doctorServicesEmptyHint =>
'add to ~/.fai/config.yaml under services:';
@override
String get doctorPillLoaded => 'loaded';
@override
String get doctorPillEmpty => 'empty';
@override
String get doctorRecheckTooltip => 'Re-check';
@override
String get doctorEventLogSection => 'Event log';
@override
String get doctorModulesApprovalsSection => 'Modules & approvals';
@override
String get doctorHostServicesSection => 'Host services';
@override
String get doctorDaemonFilesSection => 'Daemon files';
@override
String get doctorDaemonControlSection => 'Daemon control';
@override
String get doctorChainIntact => 'Hash chain intact';
@override
String doctorChainIntactDetail(int n) {
return '$n events · prev_event_sha256 verified end-to-end';
}
@override
String get doctorChainTampered => 'Tampering detected';
@override
String doctorChainTamperedDetail(int verified, int total, String id) {
return '$verified of $total verified before mismatch at $id';
}
@override
String get doctorChainPillOk => 'WORM-1';
@override
String get doctorChainPillTamper => 'TAMPER';
@override
String get doctorVerifyNow => 'Verify now';
@override
String get doctorRestart => 'Restart';
@override
String get doctorStart => 'Start';
@override
String get doctorStop => 'Stop';
@override
String get doctorStatusAction => 'Status';
@override
String doctorRunningOn(String name, String endpoint) {
return 'Running on $name: $endpoint';
}
@override
String doctorStoppedOn(String name) {
return '$name daemon stopped';
}
@override
String get doctorStatusUnknown => 'Daemon status: …';
@override
String get doctorPillRunning => 'running';
@override
String get doctorPillStopped => 'stopped';
@override
String get doctorDaemonControlHint =>
'Studio shells out to the platform binary — mirrors `fai daemon …` exactly so the CLI and UI stay in lockstep.';
@override
String get doctorDaemonControlWorking => 'Working…';
@override
String get doctorPathLog => 'Log';
@override
String get doctorPathConfig => 'Config';
@override
String get doctorPathDb => 'Audit DB';
@override
String get doctorPathModules => 'Modules dir';
@override
String get doctorPathFlows => 'Flows dir';
@override
String get doctorPathPid => 'PID file';
@override
String get doctorPathsEmpty =>
'Daemon did not report file paths. Update the running hub via `fai daemon restart`.';
@override
String doctorOpenError(String detail) {
return 'Could not open: $detail';
}
@override
String doctorUpdateAvailable(String version) {
return 'Update available — $version';
}
@override
String doctorUpdateBody(String channel, String version) {
return 'Channel $channel now offers $version. Apply via the button — Studio shells out to `fai update apply --channel $channel`.';
}
@override
String get doctorUpdateUnreachable => 'Release host unreachable';
@override
String doctorUpdateUnreachableBody(String channel) {
return 'Could not contact the release feed for $channel.';
}
@override
String doctorReleaseNotes(String url) {
return 'notes: $url';
}
@override
String get doctorPillNew => 'new';
@override
String get doctorPillOffline => 'offline';
@override
String get doctorApplyUpdate => 'Apply update';
@override
String get doctorApplying => 'Applying…';
@override
String get doctorApplyDone =>
'Update applied. Restart Studio to see the new daemon version.';
@override
String get mcpHeader => 'MCP CLIENTS';
@override
String get mcpHint =>
'External MCP servers federate their tools as `mcp.<server>.<tool>` capabilities. Configure once, see them in the Store.';
@override
String get mcpEmpty => 'No MCP servers configured. Click + to add one.';
@override
String get n8nHeader => 'N8N ENDPOINTS';
@override
String get n8nHint =>
'Active n8n workflows surface as `n8n.<endpoint>.<slug>` capabilities. Configure once, see them in the Store.';
@override
String get n8nEmpty => 'No n8n endpoints configured. Click + to add one.';
@override
String get hubUnreachable => 'Hub unreachable';
@override
String get hubUnreachableHint => 'Start the hub with `fai serve`.';
@override
String get languageEnglish => 'English';
@override
String get languageGerman => 'Deutsch';
@override
String get searchHint => 'Jump anywhere — modules, store, flows, pages…';
@override
String get searchIndexing => 'Indexing…';
@override
String get searchNoMatches => 'No matches.';
@override
String get searchHintNavigate => '↑↓ navigate';
@override
String get searchHintOpen => 'enter open';
@override
String get searchHintClose => 'esc close';
@override
String get searchGroupPages => 'Pages';
@override
String get searchGroupModules => 'Modules';
@override
String get searchGroupStore => 'Store';
@override
String get searchGroupFlows => 'Flows';
@override
String get searchSettingsLabel => 'Settings';
@override
String searchPageHint(int n) {
return 'page · ⌘$n';
}
@override
String get searchSettingsHint => 'hub endpoint, channels, system AI · ⌘;';
@override
String searchInstalledModuleHint(String version) {
return 'installed module · v$version';
}
@override
String get searchStoreUncategorized => 'uncategorized';
@override
String searchStoreHintWithTagline(String tagline) {
return 'store · $tagline';
}
@override
String searchStoreHintWithCategory(String category) {
return 'store · $category';
}
@override
String get searchSavedFlowHint => 'saved flow';
@override
String get systemAiTitle => 'System AI';
@override
String get systemAiIntro =>
'The hub-internal LLM the platform itself uses for inline failure explanations and operator help. Off by default; configure here. See docs/architecture/system-ai.md for what crosses the wire.';
@override
String get systemAiProviderLabel => 'Provider';
@override
String get systemAiEndpointLabel => 'Endpoint URL (up to /v1)';
@override
String get systemAiApiKeyRequired => 'API key env var (required)';
@override
String get systemAiApiKeyOptional => 'API key env var (optional)';
@override
String systemAiApiKeyDisclaimer(String name) {
return 'Studio never reads or stores the key value — only its env-var name. The hub reads `\$$name` at request time.';
}
@override
String get systemAiTestConnection => 'Test connection';
@override
String get systemAiTesting => 'Testing…';
@override
String get systemAiSave => 'Save';
@override
String get systemAiPrivacyHeader => 'PRIVACY MODE';
@override
String get systemAiPrivacyOffLabel => 'Off';
@override
String get systemAiPrivacyOffDesc =>
'Feature disabled. No requests leave the hub.';
@override
String get systemAiPrivacyRedactedLabel => 'Redacted';
@override
String get systemAiPrivacyRedactedDesc =>
'Only event_type / error / module names. KRITIS-friendly default.';
@override
String get systemAiPrivacyFullLabel => 'Full';
@override
String get systemAiPrivacyFullDesc =>
'Includes the audit detail JSON (hash-only — no raw payloads).';
@override
String get systemAiConnectionOk => 'Connection ok';
@override
String get systemAiConnectionFailed => 'Connection failed';
@override
String systemAiReplyPrefix(String text) {
return 'Reply: $text';
}
@override
String get systemAiModelLabel => 'Model (required)';
@override
String get systemAiModelHintFallback =>
'model identifier the provider expects';
@override
String get systemAiModelHelperBase =>
'Required: pick from the list (Refresh) or type one.';
@override
String get systemAiModelHelperOllama =>
'Required: pick from the list (Refresh) or type one. Use Pull to download from Ollama.';
@override
String get systemAiRefresh => 'Refresh';
@override
String get systemAiPull => 'Pull';
@override
String get systemAiPulling => 'Pulling…';
@override
String systemAiCouldNotListModels(String error) {
return 'Could not list models: $error';
}
@override
String get systemAiNoModelsOllama =>
'No models on the Ollama server yet. Type one above and click Pull.';
@override
String get systemAiNoModelsGeneric => 'Provider returned no models.';
@override
String get systemAiSuitabilityRecommended => 'recommended';
@override
String get systemAiSuitabilityBalanced => 'balanced';
@override
String get systemAiSuitabilitySmall => 'small — quality may be limited';
@override
String get systemAiSuitabilityLarge => 'large — slower';
@override
String get systemAiSuitabilityHuge => 'huge — likely too slow on a laptop';
@override
String get systemAiSuitabilityUnknown => 'size unknown';
@override
String systemAiHwDetected(String summary) {
return 'Detected: $summary';
}
@override
String systemAiHwReviewed(String date) {
return ' · curation reviewed $date';
}
@override
String systemAiLegendRecommended(String tier) {
return 'recommended for this hardware$tier';
}
@override
String get systemAiLegendBalanced => 'balanced (4-8B)';
@override
String get systemAiLegendSmallLarge =>
'small <3B / large 9-15B / above hardware';
@override
String get systemAiLegendHuge => 'huge >15B';
@override
String get systemAiLegendUnknown => 'size unknown';
@override
String systemAiCacheRow(int n) {
String _temp0 = intl.Intl.pluralLogic(
n,
locale: localeName,
other: 'explanations',
one: 'explanation',
);
return 'Cache: $n cached $_temp0. Identical prompts hit the cache; switching model or privacy mode flushes automatically.';
}
@override
String get systemAiCacheClear => 'Clear';
@override
String systemAiCacheClearedToast(int n) {
return 'Cleared $n cached System-AI explanations.';
}
@override
String systemAiCacheClearFailed(String error) {
return 'Cache clear failed: $error';
}
@override
String get systemAiPullEmptyError =>
'Type a model id (e.g. gemma3:4b) first.';
@override
String systemAiPullFailedError(String error) {
return 'Pull failed: $error';
}
}