feat(editor): localised analyzer/strip/run messages + monospace font fallback

Closes operator-reported issues around the text-tab font and
the English-only run + diagnostic surfaces.

  - **Real monospace everywhere**. The package never bundled
    JetBrains Mono as an asset (it relied on Studio's
    google_fonts pre-load), so a host that doesn't ship the
    font saw the YAML editor render in the system proportional
    default. New _monoTextStyle() pins fontFamilyFallback to a
    cross-platform monospace chain (Menlo / Consolas / Courier
    New / monospace) so the editor stays a grid in every host.
    Applied to the code field, gutter, diagnostic strip,
    issue rows, hover card, fix buttons, and the run tab's
    error box.

  - **Locale-aware analyzer messages**. New AnalyzerStrings
    adapter holds every string the analyzer emits, with an
    .english default + an .from(FlowEditorStrings) factory.
    FlowYamlCodeController.setCapabilityProviders takes the
    strings; FlowEditorPage wires them from the active locale.
    The analyzer's 'Unknown capability', 'Did you mean',
    'Not in the store — install locally with fai install
    --link', 'Unknown input/output type', YAML parse errors,
    and every quick-fix label (Install / Add source / Use /
    Change to) now flip to DE when the editor's locale is DE.

  - **Localised run + diagnostic chrome**. The bottom strip's
    'No issues', '2 errors · 1 warning', 'Copy all'; the
    issue row's L-prefix + Copy tooltip; the hover card's
    L-prefix + Copy tooltip; the run-block tooltip + inline
    message ('2 Fehler verhindern den Lauf · siehe
    Diagnose-Leiste unten'); the step-row 'awaiting approval'
    suffix; the CopyableErrorBox's Copy / Copied tooltip —
    all now flow through FlowEditorStrings.

Bumped to 0.20.0.

Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
flemming-it 2026-06-09 01:36:39 +02:00
parent b6bb8741a9
commit 5cd2745db5
6 changed files with 306 additions and 97 deletions

View file

@ -20,6 +20,7 @@
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:yaml/yaml.dart';
import 'l10n.dart';
import 'quick_fix.dart';
import 'wire_colors.dart';
@ -59,9 +60,15 @@ class FlowAnalyzer extends AbstractAnalyzer {
/// [kOutputsNodeId] sentinels.
final Map<String, IssueType> _stepSeverity = {};
/// Localised string builders the analyzer pipes its messages
/// through. Default is English so the analyzer behaves the
/// same in tests + host-less callers.
final AnalyzerStrings strings;
FlowAnalyzer({
required this.availableCapabilities,
this.storeCapabilities,
this.strings = AnalyzerStrings.english,
});
/// Read-only view of the fixes computed during the last
@ -115,14 +122,18 @@ class FlowAnalyzer extends AbstractAnalyzer {
issues.add(
Issue(
line: e.span?.start.line ?? 0,
message: 'YAML: ${e.message}',
message: strings.yamlError(e.message),
type: IssueType.error,
),
);
return AnalysisResult(issues: issues);
} catch (e) {
issues.add(
Issue(line: 0, message: 'YAML: $e', type: IssueType.error),
Issue(
line: 0,
message: strings.yamlError(e.toString()),
type: IssueType.error,
),
);
return AnalysisResult(issues: issues);
}
@ -159,16 +170,12 @@ class FlowAnalyzer extends AbstractAnalyzer {
// Tailor the message: "unknown" reads identical in
// both cases, but the recovery line nudges toward
// the right action.
// the right action. Localised via [strings].
final message = inStore
? 'Unknown capability "$useValue". '
'Install via the Fix button or check the spelling.'
? strings.unknownCapInStore(useValue)
: didYouMean != null
? 'Unknown capability "$useValue". '
'Did you mean "$didYouMean"?'
: 'Unknown capability "$useValue". '
'Not in the store — install locally with '
'`fai install --link <path>` or check the spelling.';
? strings.unknownCapTypo(useValue, didYouMean)
: strings.unknownCapNotInStore(useValue);
final issue = Issue(
line: issueLine,
message: message,
@ -192,7 +199,7 @@ class FlowAnalyzer extends AbstractAnalyzer {
ReplaceLineValueFix(
line: issueLine,
replacement: fullReplacement,
label: 'Use "$fullReplacement"',
label: strings.fixUseInstead(fullReplacement),
),
);
}
@ -200,20 +207,14 @@ class FlowAnalyzer extends AbstractAnalyzer {
fixes.add(
InstallCapabilityFix(
capability: useValue,
label: 'Install $useValue',
label: strings.fixInstallCap(useValue),
),
);
} else if (didYouMean == null) {
// Not in store + no near-miss spelling give the
// operator a path to register the module they
// actually have (local clone, internal URL, ).
// Hidden behind the "Did you mean" suggestion when
// present so the suggested fix stays the primary
// action.
fixes.add(
AddModuleSourceFix(
capability: useValue,
label: 'Add source for $useValue',
label: strings.fixAddSource(useValue),
),
);
}
@ -226,13 +227,13 @@ class FlowAnalyzer extends AbstractAnalyzer {
_checkFieldTypes(
doc['inputs'],
'input',
strings.inputKind(),
issues,
nodeId: kInputsNodeId,
);
_checkFieldTypes(
doc['outputs'],
'output',
strings.outputKind(),
issues,
nodeId: kOutputsNodeId,
);
@ -278,23 +279,22 @@ class FlowAnalyzer extends AbstractAnalyzer {
if (!kKnownTypes.contains(value)) {
final issue = Issue(
line: entry.value.span.start.line,
message:
'Unknown $kind type "$value". '
'Use one of: ${kKnownTypes.join(", ")}.',
message: strings.unknownType(
kind,
value,
kKnownTypes.join(", "),
),
type: IssueType.warning,
);
out.add(issue);
_bumpSeverity(nodeId, IssueType.warning);
// If the typo is within edit-distance two of a known
// type token, offer a one-click replace as the primary
// fix. The strip and tooltip render this as a button.
final closest = _closestKnownType(value);
if (closest != null) {
_fixesByIssue[issue] = [
ReplaceLineValueFix(
line: entry.value.span.start.line,
replacement: closest,
label: 'Change to "$closest"',
label: strings.fixChangeTo(closest),
),
];
}