feat(studio): Doctor + Modules localized + sparse-store federation nudge (v0.25.0)
Two threads.
Translation expansion: every visible string on the Doctor
page (section headers, status text, button labels, daemon-
control card, daemon-files panel, update banner) and the
Modules page (recent-activity strip, capabilities label,
uninstall dialog + toast) flips between DE and EN with the
sidebar toggle. Adds ~50 ARB keys split between
`app_en.arb` / `app_de.arb`. Pluralised + parametrised
strings (`{n} events verified`, `Running on {name}: {endpoint}`)
use the standard ICU placeholder syntax so future locales
slot in without code changes.
Sparse-store federation nudge: the Store page renders an
inline banner above the grid when the operator has zero
federated entries — single biggest store-fullness lever is
configuring an MCP server / n8n endpoint, so the banner
says exactly that and the button opens Settings straight
to the editor. Banner dismisses automatically the next
render after a federated entry appears.
Coverage stand for translation: Doctor + Modules + sidebar
+ page titles + nav + Cmd+K labels are bilingual. Settings
dialog, Approvals cards, Audit drilldown, MCP/n8n editors,
Store search hint + filter chips remain English-literal.
The infrastructure (ARB plumbing, generator hookup,
locale-notifier) means each follow-up surface is a
one-line ARB edit + one call-site swap.
Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
ee83eb851a
commit
349619d68e
11 changed files with 1091 additions and 80 deletions
|
|
@ -10,6 +10,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
|||
|
||||
import '../data/hub.dart';
|
||||
import '../data/system_actions.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../theme/theme.dart';
|
||||
import '../theme/tokens.dart';
|
||||
import '../widgets/widgets.dart';
|
||||
|
|
@ -220,9 +221,25 @@ class _StorePageState extends State<StorePage> {
|
|||
_status.isEmpty &&
|
||||
!_installedOnly &&
|
||||
items.any((e) => e.featured);
|
||||
// Sparse-store nudge: when no filter is set
|
||||
// and the store carries no federated entries,
|
||||
// hint that adding an MCP / n8n endpoint
|
||||
// brings dozens of capabilities at once.
|
||||
// Hides the moment any federated entry shows
|
||||
// up — the nudge has done its job.
|
||||
final showFederationNudge =
|
||||
_queryCtrl.text.trim().isEmpty &&
|
||||
_category.isEmpty &&
|
||||
_status.isEmpty &&
|
||||
!_installedOnly &&
|
||||
!items.any((e) => e.isFederated);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (showFederationNudge) ...[
|
||||
const _FederationNudge(),
|
||||
const SizedBox(height: FaiSpace.md),
|
||||
],
|
||||
if (showFeatured) ...[
|
||||
_FeaturedStrip(
|
||||
items: items.where((e) => e.featured).toList(),
|
||||
|
|
@ -1773,3 +1790,66 @@ class _ScreenshotPlaceholder extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sparse-store nudge: the operator's store has only the
|
||||
/// bundled native + planned modules and no federated entries.
|
||||
/// The single biggest store-fullness lever is configuring an
|
||||
/// MCP server or n8n endpoint, so the banner says exactly
|
||||
/// that. Tap → Settings dialog opens straight to the editor.
|
||||
/// Dismisses automatically the next render after a federated
|
||||
/// entry shows up — no per-user "don't show again".
|
||||
class _FederationNudge extends StatelessWidget {
|
||||
const _FederationNudge();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final l = AppLocalizations.of(context)!;
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(FaiSpace.md),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(FaiRadius.md),
|
||||
border: Border.all(color: theme.colorScheme.outlineVariant),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.hub_outlined,
|
||||
size: 22,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: FaiSpace.md),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
l.storeFederationNudgeTitle,
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
l.storeFederationNudgeBody,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: FaiSpace.md),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: () => FaiSettingsDialog.show(context),
|
||||
icon: const Icon(Icons.settings_outlined, size: 16),
|
||||
label: Text(l.storeFederationNudgeButton),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue