feat(studio): Store + Cmd+K palette i18n (v0.27.0)
- Localize Store page: search hint, category and status filter
chips (All / Published / Alpha / Planned / Installed),
pluralized result count, hub-unreachable / no-matches empty
states, featured strip header, store-card pills (installed /
update / version), Install / Update / Details / Read docs /
Uninstall / Not installable buttons, install-progress dialog,
uninstall confirm dialog, install / uninstall toasts,
open-browser failure toast.
- Localize detail-sheet sections: Tags, Required capabilities,
Required host services, Screenshots, Documentation, Source.
- Localize docs panel: Load documentation button, fetching
spinner copy, Open repository in browser button, friendly
error mapping (not_found / no_docs / auth / network / parse).
- Localize Cmd+K palette: search hint, group labels (Pages /
Modules / Store / Flows), keyboard hint footer, indexing /
no-matches states, dynamic-hit copy ("installed module ·
v{version}", "saved flow", "uncategorized"). Static page
hits now flow through the localized "Pages" group; Settings
entry uses the localized label and hint.
Status pill values ("published", "alpha", "planned") stay as
data-derived English strings since they map directly to the
store-index `status:` enum.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
d25b4c87ae
commit
e2b2639a86
9 changed files with 990 additions and 109 deletions
|
|
@ -117,6 +117,66 @@
|
||||||
"@storeNResults": {
|
"@storeNResults": {
|
||||||
"placeholders": { "n": { "type": "int" } }
|
"placeholders": { "n": { "type": "int" } }
|
||||||
},
|
},
|
||||||
|
"storeReloadTooltip": "Aktualisieren",
|
||||||
|
"storePillInstalled": "installiert",
|
||||||
|
"storePillUpdate": "Update",
|
||||||
|
"storeUpdateButton": "Update auf {version}",
|
||||||
|
"@storeUpdateButton": { "placeholders": { "version": { "type": "String" } } },
|
||||||
|
"storeUpdateTooltip": "Installiert {installed} — Store bietet v{best}",
|
||||||
|
"@storeUpdateTooltip": {
|
||||||
|
"placeholders": {
|
||||||
|
"installed": { "type": "String" },
|
||||||
|
"best": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeNoTagline": "(keine Beschreibung)",
|
||||||
|
"storeUninstallTitle": "Modul deinstallieren?",
|
||||||
|
"storeUninstallBody": "Entfernt {name} aus diesem Hub. Flows, die es verwenden, schlagen beim nächsten Lauf fehl.",
|
||||||
|
"@storeUninstallBody": { "placeholders": { "name": { "type": "String" } } },
|
||||||
|
"storeInstalledToast": "{name} v{version} installiert.",
|
||||||
|
"@storeInstalledToast": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" },
|
||||||
|
"version": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeInstallFailedToast": "Installation fehlgeschlagen: {error}",
|
||||||
|
"@storeInstallFailedToast": { "placeholders": { "error": { "type": "String" } } },
|
||||||
|
"storeUninstalledToast": "{name} v{version} deinstalliert.",
|
||||||
|
"@storeUninstalledToast": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" },
|
||||||
|
"version": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeUninstallFailedToast": "Deinstallation fehlgeschlagen: {error}",
|
||||||
|
"@storeUninstallFailedToast": { "placeholders": { "error": { "type": "String" } } },
|
||||||
|
"storeOpenBrowserFailed": "Browser konnte nicht geöffnet werden: {error}",
|
||||||
|
"@storeOpenBrowserFailed": { "placeholders": { "error": { "type": "String" } } },
|
||||||
|
"storeSectionTags": "Tags",
|
||||||
|
"storeSectionRequiredCapabilities": "Erforderliche Capabilities",
|
||||||
|
"storeSectionRequiredHostServices": "Erforderliche Host-Dienste",
|
||||||
|
"storeSectionScreenshots": "Screenshots",
|
||||||
|
"storeSectionDocumentation": "Dokumentation",
|
||||||
|
"storeSectionSource": "Quelle",
|
||||||
|
"storeInstallingButton": "Installiere…",
|
||||||
|
"storeNotInstallable": "Nicht installierbar",
|
||||||
|
"storeNotInstallableTooltip": "Status „{status}\" — Installations-Pfad noch nicht verdrahtet.",
|
||||||
|
"@storeNotInstallableTooltip": { "placeholders": { "status": { "type": "String" } } },
|
||||||
|
"storeInstallProgressTitle": "Installation von {name}",
|
||||||
|
"@storeInstallProgressTitle": { "placeholders": { "name": { "type": "String" } } },
|
||||||
|
"storeInstallProgressBody": "Holen, verifizieren, entpacken…",
|
||||||
|
"storeLoadDocs": "Dokumentation laden",
|
||||||
|
"storeDocsHint": "README inline gerendert; nutzt das Registry-Token des Hubs, falls nötig.",
|
||||||
|
"storeDocsFetching": "README wird geladen…",
|
||||||
|
"storeOpenRepoButton": "Repository im Browser öffnen",
|
||||||
|
"storeDocsErrorNotFound": "Dieses Modul ist nicht im Store-Index des Hubs enthalten.",
|
||||||
|
"storeDocsErrorNoDocs": "Für dieses Modul ist keine Repository-URL hinterlegt.",
|
||||||
|
"storeDocsErrorAuth": "Die Registry erfordert Authentifizierung, um dieses README zu laden. Setze FAI_REGISTRY_TOKEN in der Umgebung des Daemons und starte den Hub neu.",
|
||||||
|
"storeDocsErrorNetwork": "Netzwerkfehler beim Laden des README. {detail}",
|
||||||
|
"@storeDocsErrorNetwork": { "placeholders": { "detail": { "type": "String" } } },
|
||||||
|
"storeDocsErrorParse": "README ist kein gültiges UTF-8.",
|
||||||
|
"storeDocsErrorGeneric": "README konnte nicht geladen werden.",
|
||||||
"storeFederationNudgeTitle": "Mehr Capabilities?",
|
"storeFederationNudgeTitle": "Mehr Capabilities?",
|
||||||
"storeFederationNudgeBody": "MCP-Server oder n8n-Endpunkt unter Einstellungen → MCP-Clients / N8N-Endpunkte konfigurieren. Entdeckte Tools und Workflows tauchen hier als `mcp.<server>.<tool>`- und `n8n.<endpoint>.<workflow>`-Capabilities auf — jeder Anthropic-/Community-MCP-Server bringt mehrere auf einmal.",
|
"storeFederationNudgeBody": "MCP-Server oder n8n-Endpunkt unter Einstellungen → MCP-Clients / N8N-Endpunkte konfigurieren. Entdeckte Tools und Workflows tauchen hier als `mcp.<server>.<tool>`- und `n8n.<endpoint>.<workflow>`-Capabilities auf — jeder Anthropic-/Community-MCP-Server bringt mehrere auf einmal.",
|
||||||
"storeFederationNudgeButton": "Einstellungen öffnen",
|
"storeFederationNudgeButton": "Einstellungen öffnen",
|
||||||
|
|
@ -328,5 +388,28 @@
|
||||||
"hubUnreachableHint": "Hub starten mit `fai serve`.",
|
"hubUnreachableHint": "Hub starten mit `fai serve`.",
|
||||||
|
|
||||||
"languageEnglish": "English",
|
"languageEnglish": "English",
|
||||||
"languageGerman": "Deutsch"
|
"languageGerman": "Deutsch",
|
||||||
|
|
||||||
|
"searchHint": "Springe überall hin — Module, Store, Flows, Seiten…",
|
||||||
|
"searchIndexing": "Indiziere…",
|
||||||
|
"searchNoMatches": "Keine Treffer.",
|
||||||
|
"searchHintNavigate": "↑↓ navigieren",
|
||||||
|
"searchHintOpen": "Enter öffnen",
|
||||||
|
"searchHintClose": "Esc schließen",
|
||||||
|
"searchGroupPages": "Seiten",
|
||||||
|
"searchGroupModules": "Module",
|
||||||
|
"searchGroupStore": "Store",
|
||||||
|
"searchGroupFlows": "Flows",
|
||||||
|
"searchSettingsLabel": "Einstellungen",
|
||||||
|
"searchPageHint": "Seite · ⌘{n}",
|
||||||
|
"@searchPageHint": { "placeholders": { "n": { "type": "int" } } },
|
||||||
|
"searchSettingsHint": "Hub-Endpunkt, Kanäle, System-AI · ⌘;",
|
||||||
|
"searchInstalledModuleHint": "installiertes Modul · v{version}",
|
||||||
|
"@searchInstalledModuleHint": { "placeholders": { "version": { "type": "String" } } },
|
||||||
|
"searchStoreUncategorized": "ohne Kategorie",
|
||||||
|
"searchStoreHintWithTagline": "Store · {tagline}",
|
||||||
|
"@searchStoreHintWithTagline": { "placeholders": { "tagline": { "type": "String" } } },
|
||||||
|
"searchStoreHintWithCategory": "Store · {category}",
|
||||||
|
"@searchStoreHintWithCategory": { "placeholders": { "category": { "type": "String" } } },
|
||||||
|
"searchSavedFlowHint": "gespeicherter Flow"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,66 @@
|
||||||
"@storeNResults": {
|
"@storeNResults": {
|
||||||
"placeholders": { "n": { "type": "int" } }
|
"placeholders": { "n": { "type": "int" } }
|
||||||
},
|
},
|
||||||
|
"storeReloadTooltip": "Reload",
|
||||||
|
"storePillInstalled": "installed",
|
||||||
|
"storePillUpdate": "update",
|
||||||
|
"storeUpdateButton": "Update to {version}",
|
||||||
|
"@storeUpdateButton": { "placeholders": { "version": { "type": "String" } } },
|
||||||
|
"storeUpdateTooltip": "Installed {installed} — store has v{best}",
|
||||||
|
"@storeUpdateTooltip": {
|
||||||
|
"placeholders": {
|
||||||
|
"installed": { "type": "String" },
|
||||||
|
"best": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeNoTagline": "(no tagline)",
|
||||||
|
"storeUninstallTitle": "Uninstall module?",
|
||||||
|
"storeUninstallBody": "Removes {name} from this hub. Flows that reference it will fail at the next run.",
|
||||||
|
"@storeUninstallBody": { "placeholders": { "name": { "type": "String" } } },
|
||||||
|
"storeInstalledToast": "{name} v{version} installed.",
|
||||||
|
"@storeInstalledToast": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" },
|
||||||
|
"version": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeInstallFailedToast": "Install failed: {error}",
|
||||||
|
"@storeInstallFailedToast": { "placeholders": { "error": { "type": "String" } } },
|
||||||
|
"storeUninstalledToast": "{name} v{version} uninstalled.",
|
||||||
|
"@storeUninstalledToast": {
|
||||||
|
"placeholders": {
|
||||||
|
"name": { "type": "String" },
|
||||||
|
"version": { "type": "String" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storeUninstallFailedToast": "Uninstall failed: {error}",
|
||||||
|
"@storeUninstallFailedToast": { "placeholders": { "error": { "type": "String" } } },
|
||||||
|
"storeOpenBrowserFailed": "Could not open browser: {error}",
|
||||||
|
"@storeOpenBrowserFailed": { "placeholders": { "error": { "type": "String" } } },
|
||||||
|
"storeSectionTags": "Tags",
|
||||||
|
"storeSectionRequiredCapabilities": "Required capabilities",
|
||||||
|
"storeSectionRequiredHostServices": "Required host services",
|
||||||
|
"storeSectionScreenshots": "Screenshots",
|
||||||
|
"storeSectionDocumentation": "Documentation",
|
||||||
|
"storeSectionSource": "Source",
|
||||||
|
"storeInstallingButton": "Installing…",
|
||||||
|
"storeNotInstallable": "Not installable",
|
||||||
|
"storeNotInstallableTooltip": "Status \"{status}\" — install path not yet wired.",
|
||||||
|
"@storeNotInstallableTooltip": { "placeholders": { "status": { "type": "String" } } },
|
||||||
|
"storeInstallProgressTitle": "Installing {name}",
|
||||||
|
"@storeInstallProgressTitle": { "placeholders": { "name": { "type": "String" } } },
|
||||||
|
"storeInstallProgressBody": "Fetching, verifying, unpacking…",
|
||||||
|
"storeLoadDocs": "Load documentation",
|
||||||
|
"storeDocsHint": "README rendered inline; uses the hub's registry token when needed.",
|
||||||
|
"storeDocsFetching": "Fetching README…",
|
||||||
|
"storeOpenRepoButton": "Open repository in browser",
|
||||||
|
"storeDocsErrorNotFound": "This module is not in the hub's store index.",
|
||||||
|
"storeDocsErrorNoDocs": "No repository URL is recorded for this module.",
|
||||||
|
"storeDocsErrorAuth": "The registry requires authentication to fetch this README. Set FAI_REGISTRY_TOKEN in the daemon's environment and restart the hub.",
|
||||||
|
"storeDocsErrorNetwork": "Network error while fetching the README. {detail}",
|
||||||
|
"@storeDocsErrorNetwork": { "placeholders": { "detail": { "type": "String" } } },
|
||||||
|
"storeDocsErrorParse": "README is not valid UTF-8.",
|
||||||
|
"storeDocsErrorGeneric": "Could not load README.",
|
||||||
"storeFederationNudgeTitle": "Want more capabilities?",
|
"storeFederationNudgeTitle": "Want more capabilities?",
|
||||||
"storeFederationNudgeBody": "Configure an MCP server or n8n endpoint in Settings → MCP Clients / N8N Endpoints. Discovered tools and workflows surface here as `mcp.<server>.<tool>` and `n8n.<endpoint>.<workflow>` capabilities — every Anthropic / community MCP server adds a handful at once.",
|
"storeFederationNudgeBody": "Configure an MCP server or n8n endpoint in Settings → MCP Clients / N8N Endpoints. Discovered tools and workflows surface here as `mcp.<server>.<tool>` and `n8n.<endpoint>.<workflow>` capabilities — every Anthropic / community MCP server adds a handful at once.",
|
||||||
"storeFederationNudgeButton": "Open Settings",
|
"storeFederationNudgeButton": "Open Settings",
|
||||||
|
|
@ -329,5 +389,28 @@
|
||||||
"hubUnreachableHint": "Start the hub with `fai serve`.",
|
"hubUnreachableHint": "Start the hub with `fai serve`.",
|
||||||
|
|
||||||
"languageEnglish": "English",
|
"languageEnglish": "English",
|
||||||
"languageGerman": "Deutsch"
|
"languageGerman": "Deutsch",
|
||||||
|
|
||||||
|
"searchHint": "Jump anywhere — modules, store, flows, pages…",
|
||||||
|
"searchIndexing": "Indexing…",
|
||||||
|
"searchNoMatches": "No matches.",
|
||||||
|
"searchHintNavigate": "↑↓ navigate",
|
||||||
|
"searchHintOpen": "enter open",
|
||||||
|
"searchHintClose": "esc close",
|
||||||
|
"searchGroupPages": "Pages",
|
||||||
|
"searchGroupModules": "Modules",
|
||||||
|
"searchGroupStore": "Store",
|
||||||
|
"searchGroupFlows": "Flows",
|
||||||
|
"searchSettingsLabel": "Settings",
|
||||||
|
"searchPageHint": "page · ⌘{n}",
|
||||||
|
"@searchPageHint": { "placeholders": { "n": { "type": "int" } } },
|
||||||
|
"searchSettingsHint": "hub endpoint, channels, system AI · ⌘;",
|
||||||
|
"searchInstalledModuleHint": "installed module · v{version}",
|
||||||
|
"@searchInstalledModuleHint": { "placeholders": { "version": { "type": "String" } } },
|
||||||
|
"searchStoreUncategorized": "uncategorized",
|
||||||
|
"searchStoreHintWithTagline": "store · {tagline}",
|
||||||
|
"@searchStoreHintWithTagline": { "placeholders": { "tagline": { "type": "String" } } },
|
||||||
|
"searchStoreHintWithCategory": "store · {category}",
|
||||||
|
"@searchStoreHintWithCategory": { "placeholders": { "category": { "type": "String" } } },
|
||||||
|
"searchSavedFlowHint": "saved flow"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -686,6 +686,210 @@ abstract class AppLocalizations {
|
||||||
/// **'{n} result{n, plural, =1{} other{s}}'**
|
/// **'{n} result{n, plural, =1{} other{s}}'**
|
||||||
String storeNResults(int n);
|
String storeNResults(int n);
|
||||||
|
|
||||||
|
/// No description provided for @storeReloadTooltip.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Reload'**
|
||||||
|
String get storeReloadTooltip;
|
||||||
|
|
||||||
|
/// No description provided for @storePillInstalled.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'installed'**
|
||||||
|
String get storePillInstalled;
|
||||||
|
|
||||||
|
/// No description provided for @storePillUpdate.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'update'**
|
||||||
|
String get storePillUpdate;
|
||||||
|
|
||||||
|
/// No description provided for @storeUpdateButton.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Update to {version}'**
|
||||||
|
String storeUpdateButton(String version);
|
||||||
|
|
||||||
|
/// No description provided for @storeUpdateTooltip.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Installed {installed} — store has v{best}'**
|
||||||
|
String storeUpdateTooltip(String installed, String best);
|
||||||
|
|
||||||
|
/// No description provided for @storeNoTagline.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'(no tagline)'**
|
||||||
|
String get storeNoTagline;
|
||||||
|
|
||||||
|
/// No description provided for @storeUninstallTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Uninstall module?'**
|
||||||
|
String get storeUninstallTitle;
|
||||||
|
|
||||||
|
/// No description provided for @storeUninstallBody.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Removes {name} from this hub. Flows that reference it will fail at the next run.'**
|
||||||
|
String storeUninstallBody(String name);
|
||||||
|
|
||||||
|
/// No description provided for @storeInstalledToast.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{name} v{version} installed.'**
|
||||||
|
String storeInstalledToast(String name, String version);
|
||||||
|
|
||||||
|
/// No description provided for @storeInstallFailedToast.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Install failed: {error}'**
|
||||||
|
String storeInstallFailedToast(String error);
|
||||||
|
|
||||||
|
/// No description provided for @storeUninstalledToast.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{name} v{version} uninstalled.'**
|
||||||
|
String storeUninstalledToast(String name, String version);
|
||||||
|
|
||||||
|
/// No description provided for @storeUninstallFailedToast.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Uninstall failed: {error}'**
|
||||||
|
String storeUninstallFailedToast(String error);
|
||||||
|
|
||||||
|
/// No description provided for @storeOpenBrowserFailed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Could not open browser: {error}'**
|
||||||
|
String storeOpenBrowserFailed(String error);
|
||||||
|
|
||||||
|
/// No description provided for @storeSectionTags.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Tags'**
|
||||||
|
String get storeSectionTags;
|
||||||
|
|
||||||
|
/// No description provided for @storeSectionRequiredCapabilities.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Required capabilities'**
|
||||||
|
String get storeSectionRequiredCapabilities;
|
||||||
|
|
||||||
|
/// No description provided for @storeSectionRequiredHostServices.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Required host services'**
|
||||||
|
String get storeSectionRequiredHostServices;
|
||||||
|
|
||||||
|
/// No description provided for @storeSectionScreenshots.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Screenshots'**
|
||||||
|
String get storeSectionScreenshots;
|
||||||
|
|
||||||
|
/// No description provided for @storeSectionDocumentation.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Documentation'**
|
||||||
|
String get storeSectionDocumentation;
|
||||||
|
|
||||||
|
/// No description provided for @storeSectionSource.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Source'**
|
||||||
|
String get storeSectionSource;
|
||||||
|
|
||||||
|
/// No description provided for @storeInstallingButton.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Installing…'**
|
||||||
|
String get storeInstallingButton;
|
||||||
|
|
||||||
|
/// No description provided for @storeNotInstallable.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Not installable'**
|
||||||
|
String get storeNotInstallable;
|
||||||
|
|
||||||
|
/// No description provided for @storeNotInstallableTooltip.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Status \"{status}\" — install path not yet wired.'**
|
||||||
|
String storeNotInstallableTooltip(String status);
|
||||||
|
|
||||||
|
/// No description provided for @storeInstallProgressTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Installing {name}'**
|
||||||
|
String storeInstallProgressTitle(String name);
|
||||||
|
|
||||||
|
/// No description provided for @storeInstallProgressBody.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Fetching, verifying, unpacking…'**
|
||||||
|
String get storeInstallProgressBody;
|
||||||
|
|
||||||
|
/// No description provided for @storeLoadDocs.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Load documentation'**
|
||||||
|
String get storeLoadDocs;
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'README rendered inline; uses the hub\'s registry token when needed.'**
|
||||||
|
String get storeDocsHint;
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsFetching.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Fetching README…'**
|
||||||
|
String get storeDocsFetching;
|
||||||
|
|
||||||
|
/// No description provided for @storeOpenRepoButton.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Open repository in browser'**
|
||||||
|
String get storeOpenRepoButton;
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsErrorNotFound.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This module is not in the hub\'s store index.'**
|
||||||
|
String get storeDocsErrorNotFound;
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsErrorNoDocs.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No repository URL is recorded for this module.'**
|
||||||
|
String get storeDocsErrorNoDocs;
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsErrorAuth.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'The registry requires authentication to fetch this README. Set FAI_REGISTRY_TOKEN in the daemon\'s environment and restart the hub.'**
|
||||||
|
String get storeDocsErrorAuth;
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsErrorNetwork.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Network error while fetching the README. {detail}'**
|
||||||
|
String storeDocsErrorNetwork(String detail);
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsErrorParse.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'README is not valid UTF-8.'**
|
||||||
|
String get storeDocsErrorParse;
|
||||||
|
|
||||||
|
/// No description provided for @storeDocsErrorGeneric.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Could not load README.'**
|
||||||
|
String get storeDocsErrorGeneric;
|
||||||
|
|
||||||
/// No description provided for @storeFederationNudgeTitle.
|
/// No description provided for @storeFederationNudgeTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -1471,6 +1675,114 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Deutsch'**
|
/// **'Deutsch'**
|
||||||
String get languageGerman;
|
String get languageGerman;
|
||||||
|
|
||||||
|
/// No description provided for @searchHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Jump anywhere — modules, store, flows, pages…'**
|
||||||
|
String get searchHint;
|
||||||
|
|
||||||
|
/// No description provided for @searchIndexing.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Indexing…'**
|
||||||
|
String get searchIndexing;
|
||||||
|
|
||||||
|
/// No description provided for @searchNoMatches.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No matches.'**
|
||||||
|
String get searchNoMatches;
|
||||||
|
|
||||||
|
/// No description provided for @searchHintNavigate.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'↑↓ navigate'**
|
||||||
|
String get searchHintNavigate;
|
||||||
|
|
||||||
|
/// No description provided for @searchHintOpen.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'enter open'**
|
||||||
|
String get searchHintOpen;
|
||||||
|
|
||||||
|
/// No description provided for @searchHintClose.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'esc close'**
|
||||||
|
String get searchHintClose;
|
||||||
|
|
||||||
|
/// No description provided for @searchGroupPages.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Pages'**
|
||||||
|
String get searchGroupPages;
|
||||||
|
|
||||||
|
/// No description provided for @searchGroupModules.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Modules'**
|
||||||
|
String get searchGroupModules;
|
||||||
|
|
||||||
|
/// No description provided for @searchGroupStore.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Store'**
|
||||||
|
String get searchGroupStore;
|
||||||
|
|
||||||
|
/// No description provided for @searchGroupFlows.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Flows'**
|
||||||
|
String get searchGroupFlows;
|
||||||
|
|
||||||
|
/// No description provided for @searchSettingsLabel.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Settings'**
|
||||||
|
String get searchSettingsLabel;
|
||||||
|
|
||||||
|
/// No description provided for @searchPageHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'page · ⌘{n}'**
|
||||||
|
String searchPageHint(int n);
|
||||||
|
|
||||||
|
/// No description provided for @searchSettingsHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'hub endpoint, channels, system AI · ⌘;'**
|
||||||
|
String get searchSettingsHint;
|
||||||
|
|
||||||
|
/// No description provided for @searchInstalledModuleHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'installed module · v{version}'**
|
||||||
|
String searchInstalledModuleHint(String version);
|
||||||
|
|
||||||
|
/// No description provided for @searchStoreUncategorized.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'uncategorized'**
|
||||||
|
String get searchStoreUncategorized;
|
||||||
|
|
||||||
|
/// No description provided for @searchStoreHintWithTagline.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'store · {tagline}'**
|
||||||
|
String searchStoreHintWithTagline(String tagline);
|
||||||
|
|
||||||
|
/// No description provided for @searchStoreHintWithCategory.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'store · {category}'**
|
||||||
|
String searchStoreHintWithCategory(String category);
|
||||||
|
|
||||||
|
/// No description provided for @searchSavedFlowHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'saved flow'**
|
||||||
|
String get searchSavedFlowHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,134 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
return '$n Treffer';
|
return '$n Treffer';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeReloadTooltip => 'Aktualisieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storePillInstalled => 'installiert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storePillUpdate => 'Update';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeUpdateButton(String version) {
|
||||||
|
return 'Update auf $version';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeUpdateTooltip(String installed, String best) {
|
||||||
|
return 'Installiert $installed — Store bietet v$best';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeNoTagline => '(keine Beschreibung)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeUninstallTitle => 'Modul deinstallieren?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeUninstallBody(String name) {
|
||||||
|
return 'Entfernt $name aus diesem Hub. Flows, die es verwenden, schlagen beim nächsten Lauf fehl.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeInstalledToast(String name, String version) {
|
||||||
|
return '$name v$version installiert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeInstallFailedToast(String error) {
|
||||||
|
return 'Installation fehlgeschlagen: $error';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeUninstalledToast(String name, String version) {
|
||||||
|
return '$name v$version deinstalliert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeUninstallFailedToast(String error) {
|
||||||
|
return 'Deinstallation fehlgeschlagen: $error';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeOpenBrowserFailed(String error) {
|
||||||
|
return 'Browser konnte nicht geöffnet werden: $error';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeSectionTags => 'Tags';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeSectionRequiredCapabilities => 'Erforderliche Capabilities';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeSectionRequiredHostServices => 'Erforderliche Host-Dienste';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeSectionScreenshots => 'Screenshots';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeSectionDocumentation => 'Dokumentation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeSectionSource => 'Quelle';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeInstallingButton => 'Installiere…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeNotInstallable => 'Nicht installierbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeNotInstallableTooltip(String status) {
|
||||||
|
return 'Status „$status\" — Installations-Pfad noch nicht verdrahtet.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeInstallProgressTitle(String name) {
|
||||||
|
return 'Installation von $name';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeInstallProgressBody => 'Holen, verifizieren, entpacken…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeLoadDocs => 'Dokumentation laden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeDocsHint =>
|
||||||
|
'README inline gerendert; nutzt das Registry-Token des Hubs, falls nötig.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeDocsFetching => 'README wird geladen…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeOpenRepoButton => 'Repository im Browser öffnen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeDocsErrorNotFound =>
|
||||||
|
'Dieses Modul ist nicht im Store-Index des Hubs enthalten.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeDocsErrorNoDocs =>
|
||||||
|
'Für dieses Modul ist keine Repository-URL hinterlegt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeDocsErrorAuth =>
|
||||||
|
'Die Registry erfordert Authentifizierung, um dieses README zu laden. Setze FAI_REGISTRY_TOKEN in der Umgebung des Daemons und starte den Hub neu.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String storeDocsErrorNetwork(String detail) {
|
||||||
|
return 'Netzwerkfehler beim Laden des README. $detail';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeDocsErrorParse => 'README ist kein gültiges UTF-8.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeDocsErrorGeneric => 'README konnte nicht geladen werden.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get storeFederationNudgeTitle => 'Mehr Capabilities?';
|
String get storeFederationNudgeTitle => 'Mehr Capabilities?';
|
||||||
|
|
||||||
|
|
@ -814,4 +942,67 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get languageGerman => 'Deutsch';
|
String get languageGerman => 'Deutsch';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchHint =>
|
||||||
|
'Springe überall hin — Module, Store, Flows, Seiten…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchIndexing => 'Indiziere…';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchNoMatches => 'Keine Treffer.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchHintNavigate => '↑↓ navigieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchHintOpen => 'Enter öffnen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchHintClose => 'Esc schließen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchGroupPages => 'Seiten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchGroupModules => 'Module';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchGroupStore => 'Store';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchGroupFlows => 'Flows';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchSettingsLabel => 'Einstellungen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String searchPageHint(int n) {
|
||||||
|
return 'Seite · ⌘$n';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchSettingsHint => 'Hub-Endpunkt, Kanäle, System-AI · ⌘;';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String searchInstalledModuleHint(String version) {
|
||||||
|
return 'installiertes Modul · v$version';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchStoreUncategorized => 'ohne Kategorie';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String searchStoreHintWithTagline(String tagline) {
|
||||||
|
return 'Store · $tagline';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String searchStoreHintWithCategory(String category) {
|
||||||
|
return 'Store · $category';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get searchSavedFlowHint => 'gespeicherter Flow';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,134 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
return '$n result$_temp0';
|
return '$n result$_temp0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get storeReloadTooltip => 'Reload';
|
||||||
|
|
||||||
|
@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 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
|
@override
|
||||||
String get storeFederationNudgeTitle => 'Want more capabilities?';
|
String get storeFederationNudgeTitle => 'Want more capabilities?';
|
||||||
|
|
||||||
|
|
@ -827,4 +955,66 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get languageGerman => 'Deutsch';
|
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';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import 'widgets/widgets.dart';
|
||||||
/// Studio's own build version. Bump on every UI commit so the
|
/// Studio's own build version. Bump on every UI commit so the
|
||||||
/// running app self-identifies — visible in the sidebar header
|
/// running app self-identifies — visible in the sidebar header
|
||||||
/// and quick-glance proof that you're seeing the current build.
|
/// and quick-glance proof that you're seeing the current build.
|
||||||
const String kStudioVersion = '0.25.0';
|
const String kStudioVersion = '0.27.0';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
@ -215,23 +215,25 @@ class _StudioShellState extends State<StudioShell> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openSearchPalette() {
|
void _openSearchPalette() {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
|
final pagesGroup = l.searchGroupPages;
|
||||||
final hits = <FaiSearchHit>[
|
final hits = <FaiSearchHit>[
|
||||||
for (var i = 0; i < _pages.length; i++)
|
for (var i = 0; i < _pages.length; i++)
|
||||||
FaiSearchHit(
|
FaiSearchHit(
|
||||||
label: _pages[i].labelOf(context),
|
label: _pages[i].labelOf(context),
|
||||||
hint: 'page · ⌘${i + 1}',
|
hint: l.searchPageHint(i + 1),
|
||||||
icon: _pages[i].icon,
|
icon: _pages[i].icon,
|
||||||
group: 'Pages',
|
group: pagesGroup,
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
setState(() => _selectedIndex = i);
|
setState(() => _selectedIndex = i);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
FaiSearchHit(
|
FaiSearchHit(
|
||||||
label: 'Settings',
|
label: l.searchSettingsLabel,
|
||||||
hint: 'hub endpoint, channels, system AI · ⌘;',
|
hint: l.searchSettingsHint,
|
||||||
icon: Icons.settings_outlined,
|
icon: Icons.settings_outlined,
|
||||||
group: 'Pages',
|
group: pagesGroup,
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
FaiSettingsDialog.show(context);
|
FaiSettingsDialog.show(context);
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,11 @@ class _StorePageState extends State<StorePage> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Store'),
|
title: Text(l.searchGroupStore),
|
||||||
actions: [
|
actions: [
|
||||||
// The DE/EN toggle now lives in the sidebar footer
|
// The DE/EN toggle now lives in the sidebar footer
|
||||||
// and flips the entire app's locale. The Store
|
// and flips the entire app's locale. The Store
|
||||||
|
|
@ -110,7 +111,7 @@ class _StorePageState extends State<StorePage> {
|
||||||
// _locale reads from `Localizations.localeOf`.
|
// _locale reads from `Localizations.localeOf`.
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.refresh, size: 18),
|
icon: const Icon(Icons.refresh, size: 18),
|
||||||
tooltip: 'Reload',
|
tooltip: l.storeReloadTooltip,
|
||||||
onPressed: _runSearch,
|
onPressed: _runSearch,
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: FaiSpace.sm),
|
||||||
|
|
@ -181,11 +182,11 @@ class _StorePageState extends State<StorePage> {
|
||||||
return FaiEmptyState(
|
return FaiEmptyState(
|
||||||
icon: Icons.cloud_off_outlined,
|
icon: Icons.cloud_off_outlined,
|
||||||
iconColor: theme.colorScheme.error,
|
iconColor: theme.colorScheme.error,
|
||||||
title: 'Hub unreachable',
|
title: l.hubUnreachable,
|
||||||
hint: 'Start the hub with `fai daemon start`.',
|
hint: l.hubUnreachableHint,
|
||||||
action: FilledButton.tonal(
|
action: FilledButton.tonal(
|
||||||
onPressed: _runSearch,
|
onPressed: _runSearch,
|
||||||
child: const Text('Retry'),
|
child: Text(l.buttonRetry),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -193,9 +194,8 @@ class _StorePageState extends State<StorePage> {
|
||||||
if (items.isEmpty) {
|
if (items.isEmpty) {
|
||||||
return FaiEmptyState(
|
return FaiEmptyState(
|
||||||
icon: Icons.search_off,
|
icon: Icons.search_off,
|
||||||
title: 'No matches',
|
title: l.storeNoMatches,
|
||||||
hint:
|
hint: l.storeNoMatchesHint,
|
||||||
'Adjust the filters or clear the search to see all entries.',
|
|
||||||
action: FilledButton.tonal(
|
action: FilledButton.tonal(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -206,7 +206,7 @@ class _StorePageState extends State<StorePage> {
|
||||||
});
|
});
|
||||||
_runSearch();
|
_runSearch();
|
||||||
},
|
},
|
||||||
child: const Text('Clear filters'),
|
child: Text(l.storeClearFilters),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -293,6 +293,7 @@ class _SearchField extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
return ValueListenableBuilder<TextEditingValue>(
|
return ValueListenableBuilder<TextEditingValue>(
|
||||||
valueListenable: controller,
|
valueListenable: controller,
|
||||||
builder: (_, value, _) {
|
builder: (_, value, _) {
|
||||||
|
|
@ -300,7 +301,7 @@ class _SearchField extends StatelessWidget {
|
||||||
controller: controller,
|
controller: controller,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: const Icon(Icons.search, size: 20),
|
prefixIcon: const Icon(Icons.search, size: 20),
|
||||||
hintText: 'Search modules — name, tagline, tag, capability…',
|
hintText: l.storeSearchHint,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
suffixIcon: value.text.isEmpty
|
suffixIcon: value.text.isEmpty
|
||||||
|
|
@ -334,6 +335,7 @@ class _CategoryStrip extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
if (categories.isEmpty) return const SizedBox.shrink();
|
if (categories.isEmpty) return const SizedBox.shrink();
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 36,
|
height: 36,
|
||||||
|
|
@ -341,7 +343,7 @@ class _CategoryStrip extends StatelessWidget {
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children: [
|
children: [
|
||||||
_ChoiceChip(
|
_ChoiceChip(
|
||||||
label: 'All',
|
label: l.storeStatusAll,
|
||||||
selected: selected.isEmpty,
|
selected: selected.isEmpty,
|
||||||
onSelected: () => onSelected(''),
|
onSelected: () => onSelected(''),
|
||||||
),
|
),
|
||||||
|
|
@ -375,38 +377,39 @@ class _FilterRow extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
_ChoiceChip(
|
_ChoiceChip(
|
||||||
label: 'All',
|
label: l.storeStatusAll,
|
||||||
selected: status.isEmpty,
|
selected: status.isEmpty,
|
||||||
onSelected: () => onStatus(''),
|
onSelected: () => onStatus(''),
|
||||||
),
|
),
|
||||||
_ChoiceChip(
|
_ChoiceChip(
|
||||||
label: 'Published',
|
label: l.storeStatusPublished,
|
||||||
selected: status == 'published',
|
selected: status == 'published',
|
||||||
onSelected: () => onStatus('published'),
|
onSelected: () => onStatus('published'),
|
||||||
),
|
),
|
||||||
_ChoiceChip(
|
_ChoiceChip(
|
||||||
label: 'Alpha',
|
label: l.storeStatusAlpha,
|
||||||
selected: status == 'alpha',
|
selected: status == 'alpha',
|
||||||
onSelected: () => onStatus('alpha'),
|
onSelected: () => onStatus('alpha'),
|
||||||
),
|
),
|
||||||
_ChoiceChip(
|
_ChoiceChip(
|
||||||
label: 'Planned',
|
label: l.storeStatusPlanned,
|
||||||
selected: status == 'planned',
|
selected: status == 'planned',
|
||||||
onSelected: () => onStatus('planned'),
|
onSelected: () => onStatus('planned'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.md),
|
const SizedBox(width: FaiSpace.md),
|
||||||
FilterChip(
|
FilterChip(
|
||||||
label: const Text('Installed'),
|
label: Text(l.storeInstalledOnly),
|
||||||
selected: installedOnly,
|
selected: installedOnly,
|
||||||
onSelected: onInstalledOnly,
|
onSelected: onInstalledOnly,
|
||||||
showCheckmark: true,
|
showCheckmark: true,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
'$resultCount result${resultCount == 1 ? "" : "s"}',
|
l.storeNResults(resultCount),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -512,6 +515,7 @@ class _FeaturedStrip extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -520,7 +524,7 @@ class _FeaturedStrip extends StatelessWidget {
|
||||||
Icon(Icons.star, size: 14, color: FaiColors.success),
|
Icon(Icons.star, size: 14, color: FaiColors.success),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'FEATURED',
|
l.storeFeatured,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
letterSpacing: 0.6,
|
letterSpacing: 0.6,
|
||||||
|
|
@ -529,7 +533,7 @@ class _FeaturedStrip extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: FaiSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'curated picks · click for details',
|
l.storeFeaturedHint,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -582,6 +586,7 @@ class _FeaturedTile extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
final tagline = locale == 'de' && item.taglineDe.isNotEmpty
|
final tagline = locale == 'de' && item.taglineDe.isNotEmpty
|
||||||
? item.taglineDe
|
? item.taglineDe
|
||||||
: item.taglineEn;
|
: item.taglineEn;
|
||||||
|
|
@ -649,7 +654,7 @@ class _FeaturedTile extends StatelessWidget {
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: FaiSpace.md),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
tagline.isEmpty ? '(no tagline)' : tagline,
|
tagline.isEmpty ? l.storeNoTagline : tagline,
|
||||||
style: theme.textTheme.bodyLarge,
|
style: theme.textTheme.bodyLarge,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
|
@ -672,8 +677,8 @@ class _FeaturedTile extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (item.installed)
|
if (item.installed)
|
||||||
const FaiPill(
|
FaiPill(
|
||||||
label: 'installed',
|
label: l.storePillInstalled,
|
||||||
tone: FaiPillTone.success,
|
tone: FaiPillTone.success,
|
||||||
icon: Icons.check,
|
icon: Icons.check,
|
||||||
)
|
)
|
||||||
|
|
@ -681,7 +686,7 @@ class _FeaturedTile extends StatelessWidget {
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: onInstall,
|
onPressed: onInstall,
|
||||||
icon: const Icon(Icons.download, size: 16),
|
icon: const Icon(Icons.download, size: 16),
|
||||||
label: const Text('Install'),
|
label: Text(l.buttonInstall),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -724,6 +729,7 @@ class _StoreCard extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
final tagline = locale == 'de' && item.taglineDe.isNotEmpty
|
final tagline = locale == 'de' && item.taglineDe.isNotEmpty
|
||||||
? item.taglineDe
|
? item.taglineDe
|
||||||
: item.taglineEn;
|
: item.taglineEn;
|
||||||
|
|
@ -775,17 +781,19 @@ class _StoreCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (_hasUpdate)
|
if (_hasUpdate)
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message:
|
message: l.storeUpdateTooltip(
|
||||||
'Installed ${installedVersion ?? "?"} — store has v${item.bestVersion}',
|
installedVersion ?? '?',
|
||||||
child: const FaiPill(
|
item.bestVersion,
|
||||||
label: 'update',
|
),
|
||||||
|
child: FaiPill(
|
||||||
|
label: l.storePillUpdate,
|
||||||
tone: FaiPillTone.warning,
|
tone: FaiPillTone.warning,
|
||||||
icon: Icons.system_update_alt,
|
icon: Icons.system_update_alt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (item.installed)
|
else if (item.installed)
|
||||||
const FaiPill(
|
FaiPill(
|
||||||
label: 'installed',
|
label: l.storePillInstalled,
|
||||||
tone: FaiPillTone.success,
|
tone: FaiPillTone.success,
|
||||||
icon: Icons.check,
|
icon: Icons.check,
|
||||||
)
|
)
|
||||||
|
|
@ -799,7 +807,7 @@ class _StoreCard extends StatelessWidget {
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: FaiSpace.sm),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
tagline.isEmpty ? '(no tagline)' : tagline,
|
tagline.isEmpty ? l.storeNoTagline : tagline,
|
||||||
style: theme.textTheme.bodySmall,
|
style: theme.textTheme.bodySmall,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
|
@ -819,7 +827,7 @@ class _StoreCard extends StatelessWidget {
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: onInstall,
|
onPressed: onInstall,
|
||||||
icon: const Icon(Icons.system_update_alt, size: 14),
|
icon: const Icon(Icons.system_update_alt, size: 14),
|
||||||
label: Text('Update to ${item.bestVersion}'),
|
label: Text(l.storeUpdateButton(item.bestVersion)),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
|
|
@ -828,13 +836,13 @@ class _StoreCard extends StatelessWidget {
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: onTap,
|
onPressed: onTap,
|
||||||
icon: const Icon(Icons.info_outline, size: 14),
|
icon: const Icon(Icons.info_outline, size: 14),
|
||||||
label: const Text('Details'),
|
label: Text(l.buttonDetails),
|
||||||
)
|
)
|
||||||
else if (installable)
|
else if (installable)
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: onInstall,
|
onPressed: onInstall,
|
||||||
icon: const Icon(Icons.download, size: 14),
|
icon: const Icon(Icons.download, size: 14),
|
||||||
label: const Text('Install'),
|
label: Text(l.buttonInstall),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
|
|
@ -843,7 +851,7 @@ class _StoreCard extends StatelessWidget {
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: onTap,
|
onPressed: onTap,
|
||||||
icon: const Icon(Icons.info_outline, size: 14),
|
icon: const Icon(Icons.info_outline, size: 14),
|
||||||
label: const Text('Details'),
|
label: Text(l.buttonDetails),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
|
|
@ -906,33 +914,33 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
try {
|
try {
|
||||||
final r = await HubService.instance.installModule(source: widget.item.name);
|
final r = await HubService.instance.installModule(source: widget.item.name);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
setState(() {
|
setState(() {
|
||||||
_busy = false;
|
_busy = false;
|
||||||
_toast = '${r.name} v${r.version} installed.';
|
_toast = l.storeInstalledToast(r.name, r.version);
|
||||||
});
|
});
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
setState(() {
|
setState(() {
|
||||||
_busy = false;
|
_busy = false;
|
||||||
_toast = 'Install failed: $e';
|
_toast = l.storeInstallFailedToast(e.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _uninstall() async {
|
Future<void> _uninstall() async {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
final ok = await showDialog<bool>(
|
final ok = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
title: const Text('Uninstall module?'),
|
title: Text(l.storeUninstallTitle),
|
||||||
content: Text(
|
content: Text(l.storeUninstallBody(widget.item.name)),
|
||||||
'Removes ${widget.item.name} from this hub. Flows that '
|
|
||||||
'reference it will fail at the next run.',
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(ctx, false),
|
onPressed: () => Navigator.pop(ctx, false),
|
||||||
child: const Text('Cancel'),
|
child: Text(l.buttonCancel),
|
||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () => Navigator.pop(ctx, true),
|
onPressed: () => Navigator.pop(ctx, true),
|
||||||
|
|
@ -940,7 +948,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
backgroundColor: Theme.of(ctx).colorScheme.error,
|
backgroundColor: Theme.of(ctx).colorScheme.error,
|
||||||
foregroundColor: Theme.of(ctx).colorScheme.onError,
|
foregroundColor: Theme.of(ctx).colorScheme.onError,
|
||||||
),
|
),
|
||||||
child: const Text('Uninstall'),
|
child: Text(l.buttonUninstall),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -953,16 +961,18 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
try {
|
try {
|
||||||
final r = await HubService.instance.uninstallModule(widget.item.name);
|
final r = await HubService.instance.uninstallModule(widget.item.name);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
final l2 = AppLocalizations.of(context)!;
|
||||||
setState(() {
|
setState(() {
|
||||||
_busy = false;
|
_busy = false;
|
||||||
_toast = '${r.name} v${r.version} uninstalled.';
|
_toast = l2.storeUninstalledToast(r.name, r.version);
|
||||||
});
|
});
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
final l2 = AppLocalizations.of(context)!;
|
||||||
setState(() {
|
setState(() {
|
||||||
_busy = false;
|
_busy = false;
|
||||||
_toast = 'Uninstall failed: $e';
|
_toast = l2.storeUninstallFailedToast(e.toString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -972,8 +982,9 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
final r = await SystemActions.openInOs(widget.item.repository);
|
final r = await SystemActions.openInOs(widget.item.repository);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Could not open browser: ${r.stderr}')),
|
SnackBar(content: Text(l.storeOpenBrowserFailed(r.stderr))),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -990,6 +1001,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
final item = widget.item;
|
final item = widget.item;
|
||||||
final tagline = _locale == 'de' && item.taglineDe.isNotEmpty
|
final tagline = _locale == 'de' && item.taglineDe.isNotEmpty
|
||||||
? item.taglineDe
|
? item.taglineDe
|
||||||
|
|
@ -1069,8 +1081,8 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
tone: FaiPillTone.neutral,
|
tone: FaiPillTone.neutral,
|
||||||
),
|
),
|
||||||
if (item.installed)
|
if (item.installed)
|
||||||
const FaiPill(
|
FaiPill(
|
||||||
label: 'installed',
|
label: l.storePillInstalled,
|
||||||
tone: FaiPillTone.success,
|
tone: FaiPillTone.success,
|
||||||
icon: Icons.check,
|
icon: Icons.check,
|
||||||
),
|
),
|
||||||
|
|
@ -1107,7 +1119,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
const SizedBox(height: FaiSpace.xl),
|
const SizedBox(height: FaiSpace.xl),
|
||||||
],
|
],
|
||||||
if (item.tags.isNotEmpty) ...[
|
if (item.tags.isNotEmpty) ...[
|
||||||
_SectionHeader('Tags'),
|
_SectionHeader(l.storeSectionTags),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: FaiSpace.sm),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: FaiSpace.xs,
|
||||||
|
|
@ -1120,7 +1132,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: FaiSpace.lg),
|
||||||
],
|
],
|
||||||
if (item.requiresCapabilities.isNotEmpty) ...[
|
if (item.requiresCapabilities.isNotEmpty) ...[
|
||||||
_SectionHeader('Required capabilities'),
|
_SectionHeader(l.storeSectionRequiredCapabilities),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: FaiSpace.sm),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: FaiSpace.xs,
|
||||||
|
|
@ -1137,7 +1149,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: FaiSpace.lg),
|
||||||
],
|
],
|
||||||
if (item.requiresServices.isNotEmpty) ...[
|
if (item.requiresServices.isNotEmpty) ...[
|
||||||
_SectionHeader('Required host services'),
|
_SectionHeader(l.storeSectionRequiredHostServices),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: FaiSpace.sm),
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: FaiSpace.xs,
|
spacing: FaiSpace.xs,
|
||||||
|
|
@ -1154,7 +1166,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: FaiSpace.lg),
|
||||||
],
|
],
|
||||||
if (item.screenshotUrls.isNotEmpty) ...[
|
if (item.screenshotUrls.isNotEmpty) ...[
|
||||||
_SectionHeader('Screenshots'),
|
_SectionHeader(l.storeSectionScreenshots),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: FaiSpace.sm),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 220,
|
height: 220,
|
||||||
|
|
@ -1171,7 +1183,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: FaiSpace.lg),
|
||||||
],
|
],
|
||||||
if (item.repository.isNotEmpty || item.docsUrl.isNotEmpty) ...[
|
if (item.repository.isNotEmpty || item.docsUrl.isNotEmpty) ...[
|
||||||
_SectionHeader('Documentation'),
|
_SectionHeader(l.storeSectionDocumentation),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: FaiSpace.sm),
|
||||||
_DocsPanel(
|
_DocsPanel(
|
||||||
future: _docsFuture,
|
future: _docsFuture,
|
||||||
|
|
@ -1179,7 +1191,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
onOpenRepo: _openRepository,
|
onOpenRepo: _openRepository,
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.lg),
|
const SizedBox(height: FaiSpace.lg),
|
||||||
_SectionHeader('Source'),
|
_SectionHeader(l.storeSectionSource),
|
||||||
const SizedBox(height: FaiSpace.sm),
|
const SizedBox(height: FaiSpace.sm),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: _openRepository,
|
onTap: _openRepository,
|
||||||
|
|
@ -1225,7 +1237,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: _openRepository,
|
onPressed: _openRepository,
|
||||||
icon: const Icon(Icons.menu_book_outlined, size: 16),
|
icon: const Icon(Icons.menu_book_outlined, size: 16),
|
||||||
label: const Text('Read docs'),
|
label: Text(l.buttonReadDocs),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (item.installed) ...[
|
if (item.installed) ...[
|
||||||
|
|
@ -1239,7 +1251,7 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
CircularProgressIndicator(strokeWidth: 2),
|
CircularProgressIndicator(strokeWidth: 2),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.delete_outline, size: 16),
|
: const Icon(Icons.delete_outline, size: 16),
|
||||||
label: const Text('Uninstall'),
|
label: Text(l.buttonUninstall),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
foregroundColor: theme.colorScheme.error,
|
foregroundColor: theme.colorScheme.error,
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
|
|
@ -1259,15 +1271,16 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||||
CircularProgressIndicator(strokeWidth: 2),
|
CircularProgressIndicator(strokeWidth: 2),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.download, size: 16),
|
: const Icon(Icons.download, size: 16),
|
||||||
label: Text(_busy ? 'Installing…' : 'Install'),
|
label: Text(
|
||||||
|
_busy ? l.storeInstallingButton : l.buttonInstall,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message:
|
message: l.storeNotInstallableTooltip(item.status),
|
||||||
'Status "${item.status}" — install path not yet wired.',
|
child: FilledButton(
|
||||||
child: const FilledButton(
|
|
||||||
onPressed: null,
|
onPressed: null,
|
||||||
child: Text('Not installable'),
|
child: Text(l.storeNotInstallable),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -1324,8 +1337,9 @@ class _InstallProgressDialogState extends State<_InstallProgressDialog> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('Installing ${widget.item.name}'),
|
title: Text(l.storeInstallProgressTitle(widget.item.name)),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
borderRadius: BorderRadius.circular(FaiRadius.md),
|
||||||
),
|
),
|
||||||
|
|
@ -1342,7 +1356,7 @@ class _InstallProgressDialogState extends State<_InstallProgressDialog> {
|
||||||
const CircularProgressIndicator(),
|
const CircularProgressIndicator(),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: FaiSpace.md),
|
||||||
Text(
|
Text(
|
||||||
'Fetching, verifying, unpacking…',
|
l.storeInstallProgressBody,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -1384,7 +1398,7 @@ class _InstallProgressDialogState extends State<_InstallProgressDialog> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: FaiSpace.md),
|
const SizedBox(height: FaiSpace.md),
|
||||||
Text(
|
Text(
|
||||||
'${r.name} v${r.version} installed.',
|
l.storeInstalledToast(r.name, r.version),
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -1395,7 +1409,7 @@ class _InstallProgressDialogState extends State<_InstallProgressDialog> {
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(context, true),
|
||||||
child: const Text('Close'),
|
child: Text(l.buttonClose),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -1458,17 +1472,18 @@ class _DocsPanel extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
if (future == null) {
|
if (future == null) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: onLoad,
|
onPressed: onLoad,
|
||||||
icon: const Icon(Icons.menu_book_outlined, size: 16),
|
icon: const Icon(Icons.menu_book_outlined, size: 16),
|
||||||
label: const Text('Load documentation'),
|
label: Text(l.storeLoadDocs),
|
||||||
),
|
),
|
||||||
const SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: FaiSpace.sm),
|
||||||
Text(
|
Text(
|
||||||
'README rendered inline; uses the hub\'s registry token when needed.',
|
l.storeDocsHint,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -1480,17 +1495,17 @@ class _DocsPanel extends StatelessWidget {
|
||||||
future: future,
|
future: future,
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.connectionState == ConnectionState.waiting) {
|
if (snap.connectionState == ConnectionState.waiting) {
|
||||||
return const Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: FaiSpace.md),
|
padding: const EdgeInsets.symmetric(vertical: FaiSpace.md),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
const SizedBox(
|
||||||
width: 14,
|
width: 14,
|
||||||
height: 14,
|
height: 14,
|
||||||
child: CircularProgressIndicator(strokeWidth: 2),
|
child: CircularProgressIndicator(strokeWidth: 2),
|
||||||
),
|
),
|
||||||
SizedBox(width: FaiSpace.sm),
|
const SizedBox(width: FaiSpace.sm),
|
||||||
Text('Fetching README…'),
|
Text(l.storeDocsFetching),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -1504,7 +1519,7 @@ class _DocsPanel extends StatelessWidget {
|
||||||
final r = snap.data!;
|
final r = snap.data!;
|
||||||
if (r.errorKind.isNotEmpty) {
|
if (r.errorKind.isNotEmpty) {
|
||||||
return _DocsErrorPanel(
|
return _DocsErrorPanel(
|
||||||
message: _friendlyDocsError(r.errorKind, r.text),
|
message: _friendlyDocsError(context, r.errorKind, r.text),
|
||||||
onOpenRepo: onOpenRepo,
|
onOpenRepo: onOpenRepo,
|
||||||
sourceUrl: r.sourceUrl,
|
sourceUrl: r.sourceUrl,
|
||||||
);
|
);
|
||||||
|
|
@ -1599,7 +1614,7 @@ class _DocsErrorPanel extends StatelessWidget {
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
onPressed: onOpenRepo,
|
onPressed: onOpenRepo,
|
||||||
icon: const Icon(Icons.open_in_new, size: 14),
|
icon: const Icon(Icons.open_in_new, size: 14),
|
||||||
label: const Text('Open repository in browser'),
|
label: Text(AppLocalizations.of(context)!.storeOpenRepoButton),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
),
|
),
|
||||||
|
|
@ -1636,22 +1651,21 @@ int _versionCmp(String a, String b) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _friendlyDocsError(String kind, String detail) {
|
String _friendlyDocsError(BuildContext context, String kind, String detail) {
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case 'not_found':
|
case 'not_found':
|
||||||
return 'This module is not in the hub\'s store index.';
|
return l.storeDocsErrorNotFound;
|
||||||
case 'no_docs':
|
case 'no_docs':
|
||||||
return 'No repository URL is recorded for this module.';
|
return l.storeDocsErrorNoDocs;
|
||||||
case 'auth':
|
case 'auth':
|
||||||
return 'The registry requires authentication to fetch this README. '
|
return l.storeDocsErrorAuth;
|
||||||
'Set FAI_REGISTRY_TOKEN in the daemon\'s environment '
|
|
||||||
'and restart the hub.';
|
|
||||||
case 'network':
|
case 'network':
|
||||||
return 'Network error while fetching the README. $detail';
|
return l.storeDocsErrorNetwork(detail);
|
||||||
case 'parse':
|
case 'parse':
|
||||||
return 'README is not valid UTF-8.';
|
return l.storeDocsErrorParse;
|
||||||
default:
|
default:
|
||||||
return detail.isEmpty ? 'Could not load README.' : detail;
|
return detail.isEmpty ? l.storeDocsErrorGeneric : detail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../data/hub.dart';
|
import '../data/hub.dart';
|
||||||
|
import '../l10n/app_localizations.dart';
|
||||||
import 'fai_module_sheet.dart';
|
import 'fai_module_sheet.dart';
|
||||||
|
|
||||||
/// One row in the palette. Carries everything needed to render
|
/// One row in the palette. Carries everything needed to render
|
||||||
|
|
@ -90,6 +91,7 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
/// first character.
|
/// first character.
|
||||||
Future<List<FaiSearchHit>> _loadDynamic() async {
|
Future<List<FaiSearchHit>> _loadDynamic() async {
|
||||||
final svc = HubService.instance;
|
final svc = HubService.instance;
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
final results = await Future.wait([
|
final results = await Future.wait([
|
||||||
svc.listModules().catchError((_) => <ModuleSummary>[]),
|
svc.listModules().catchError((_) => <ModuleSummary>[]),
|
||||||
svc.searchStore(limit: 200).catchError((_) => <StoreItem>[]),
|
svc.searchStore(limit: 200).catchError((_) => <StoreItem>[]),
|
||||||
|
|
@ -103,23 +105,26 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
for (final m in modules) {
|
for (final m in modules) {
|
||||||
hits.add(FaiSearchHit(
|
hits.add(FaiSearchHit(
|
||||||
label: m.name,
|
label: m.name,
|
||||||
hint: 'installed module · v${m.version}',
|
hint: l.searchInstalledModuleHint(m.version),
|
||||||
icon: Icons.extension,
|
icon: Icons.extension,
|
||||||
group: 'Modules',
|
group: l.searchGroupModules,
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
FaiModuleSheet.show(context, m.name);
|
FaiModuleSheet.show(context, m.name);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
final storeGroup = l.searchGroupStore;
|
||||||
|
final pagesGroup = l.searchGroupPages;
|
||||||
for (final s in store) {
|
for (final s in store) {
|
||||||
hits.add(FaiSearchHit(
|
hits.add(FaiSearchHit(
|
||||||
label: s.name,
|
label: s.name,
|
||||||
hint: s.taglineEn.isEmpty
|
hint: s.taglineEn.isEmpty
|
||||||
? 'store · ${s.category.isEmpty ? "uncategorized" : s.category}'
|
? l.searchStoreHintWithCategory(
|
||||||
: 'store · ${s.taglineEn}',
|
s.category.isEmpty ? l.searchStoreUncategorized : s.category)
|
||||||
|
: l.searchStoreHintWithTagline(s.taglineEn),
|
||||||
icon: Icons.storefront_outlined,
|
icon: Icons.storefront_outlined,
|
||||||
group: 'Store',
|
group: storeGroup,
|
||||||
// Selecting a store hit just closes the palette and
|
// Selecting a store hit just closes the palette and
|
||||||
// focuses the Store tab; deep-linking to the detail
|
// focuses the Store tab; deep-linking to the detail
|
||||||
// sheet would require thread-through plumbing we don't
|
// sheet would require thread-through plumbing we don't
|
||||||
|
|
@ -127,7 +132,7 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
for (final h in widget.staticHits) {
|
for (final h in widget.staticHits) {
|
||||||
if (h.label == 'Store' && h.group == 'Pages') {
|
if (h.label == storeGroup && h.group == pagesGroup) {
|
||||||
h.onSelect();
|
h.onSelect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -135,16 +140,17 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
final flowsGroup = l.searchGroupFlows;
|
||||||
for (final f in flows) {
|
for (final f in flows) {
|
||||||
hits.add(FaiSearchHit(
|
hits.add(FaiSearchHit(
|
||||||
label: f.name,
|
label: f.name,
|
||||||
hint: 'saved flow',
|
hint: l.searchSavedFlowHint,
|
||||||
icon: Icons.account_tree_outlined,
|
icon: Icons.account_tree_outlined,
|
||||||
group: 'Flows',
|
group: flowsGroup,
|
||||||
onSelect: () {
|
onSelect: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
for (final h in widget.staticHits) {
|
for (final h in widget.staticHits) {
|
||||||
if (h.label == 'Flows' && h.group == 'Pages') {
|
if (h.label == flowsGroup && h.group == pagesGroup) {
|
||||||
h.onSelect();
|
h.onSelect();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -201,6 +207,7 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final l = AppLocalizations.of(context)!;
|
||||||
return Dialog(
|
return Dialog(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
insetPadding: const EdgeInsets.only(top: 80, left: 24, right: 24),
|
insetPadding: const EdgeInsets.only(top: 80, left: 24, right: 24),
|
||||||
|
|
@ -235,8 +242,7 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon:
|
prefixIcon:
|
||||||
const Icon(Icons.search, size: 20),
|
const Icon(Icons.search, size: 20),
|
||||||
hintText:
|
hintText: l.searchHint,
|
||||||
'Jump anywhere — modules, store, flows, pages…',
|
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
suffixIcon: snap.connectionState ==
|
suffixIcon: snap.connectionState ==
|
||||||
ConnectionState.waiting
|
ConnectionState.waiting
|
||||||
|
|
@ -264,8 +270,8 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Text(
|
child: Text(
|
||||||
snap.connectionState == ConnectionState.waiting
|
snap.connectionState == ConnectionState.waiting
|
||||||
? 'Indexing…'
|
? l.searchIndexing
|
||||||
: 'No matches.',
|
: l.searchNoMatches,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
@ -380,14 +386,14 @@ class _FaiSearchPaletteState extends State<FaiSearchPalette> {
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_Hint(text: '↑↓ navigate'),
|
_Hint(text: l.searchHintNavigate),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
_Hint(text: 'enter open'),
|
_Hint(text: l.searchHintOpen),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
_Hint(text: 'esc close'),
|
_Hint(text: l.searchHintClose),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
'${hits.length} result${hits.length == 1 ? "" : "s"}',
|
l.storeNResults(hits.length),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: fai_studio
|
name: fai_studio
|
||||||
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
description: "F∆I Studio — desktop GUI for the F∆I hub"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.26.0
|
version: 0.27.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.0-200.1.beta
|
sdk: ^3.11.0-200.1.beta
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue