refactor(i18n): route framework + shared chrome through a 'core' catalog (T-469)
Framework strings outside any extension — widget primitives (collapser, toast, lightbox, multitab, ex-line, spine, pane chrome), the shared reader chrome, the markdown 'Open in editor' tooltip, and the drag-resize handle a11y labels — now resolve under a new 'core' namespace (preloaded at boot). Settles the T-469 namespace question: framework chrome gets one 'core' catalog. Makes ClideSettings.i18n.string/.interpolated null-safe (ClideKernel.maybeOf): primitives render kernel-less in isolated tests, returning the placeholder — matching the D-101 fallback contract for fonts. The markdown tooltip threads via the ClideMarkdownHooks carrier like mono/ui; drag_resize reads the kernel i18n directly to avoid a kernel→widgets layering inversion. No en_US change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -345,6 +345,11 @@ class ClideKernel extends InheritedWidget {
|
||||
return w.services;
|
||||
}
|
||||
|
||||
/// Like [of] but returns null instead of throwing when there is no kernel in
|
||||
/// scope — lets widget-facing facades (e.g. [ClideSettings.i18n]) degrade
|
||||
/// gracefully so a primitive renders in an isolated test without a kernel.
|
||||
static KernelServices? maybeOf(BuildContext context) => context.dependOnInheritedWidgetOfExactType<ClideKernel>()?.services;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ClideKernel oldWidget) => services != oldWidget.services;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"collapser.expand": { "translation": "Expand" },
|
||||
"collapser.collapse": { "translation": "Collapse" },
|
||||
"collapser.expanded": { "translation": "expanded" },
|
||||
"collapser.collapsed": { "translation": "collapsed" },
|
||||
"toast.dismiss": { "translation": "Dismiss notification" },
|
||||
"lightbox.close": { "translation": "close" },
|
||||
"lightbox.hint": { "translation": "scroll to zoom · double-click to reset · Esc to close" },
|
||||
"tab.new": { "translation": "New tab" },
|
||||
"exline.notCommand": { "translation": "Not an editor command" },
|
||||
"spine.expandSuffix": { "translation": "click to expand" },
|
||||
"pane.close": { "translation": "Close pane" },
|
||||
"pane.header": { "translation": "pane header: {title}" },
|
||||
"reader.back": { "translation": "Back" },
|
||||
"reader.forward": { "translation": "Forward" },
|
||||
"reader.jumpToPin": { "translation": "Jump to pin" },
|
||||
"reader.edit": { "translation": "Edit in editor" },
|
||||
"reader.pin": { "translation": "Pin" },
|
||||
"reader.unpin": { "translation": "Unpin" },
|
||||
"link.openInEditor": { "translation": "Open in editor" },
|
||||
"resize.axis.width": { "translation": "width" },
|
||||
"resize.axis.height": { "translation": "height" },
|
||||
"resize.sidebar": { "translation": "Sidebar {axis}" },
|
||||
"resize.contextPanel": { "translation": "Context panel {axis}" },
|
||||
"resize.slot": { "translation": "{slot} {axis}" },
|
||||
"resize.pixels": { "translation": "{n} pixels" }
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:clide/kernel/src/facade.dart' show ClideKernel;
|
||||
import 'package:clide/kernel/src/i18n/i18n.dart' show I18nReplacer;
|
||||
import 'package:clide/kernel/src/panels/arrangement.dart';
|
||||
import 'package:clide/kernel/src/panels/slot_id.dart';
|
||||
import 'package:clide/kernel/src/theme/controller.dart';
|
||||
@@ -43,10 +45,10 @@ class _DragResizeHandleState extends State<DragResizeHandle> {
|
||||
return Semantics(
|
||||
container: true,
|
||||
slider: true,
|
||||
label: _semanticLabel(),
|
||||
value: size == null ? null : '${size.round()} pixels',
|
||||
increasedValue: size == null ? null : '${(size + DragResizeHandle.stepFine).round()} pixels',
|
||||
decreasedValue: size == null ? null : '${(size - DragResizeHandle.stepFine).round()} pixels',
|
||||
label: _semanticLabel(context),
|
||||
value: size == null ? null : _pixels(context, size.round()),
|
||||
increasedValue: size == null ? null : _pixels(context, (size + DragResizeHandle.stepFine).round()),
|
||||
decreasedValue: size == null ? null : _pixels(context, (size - DragResizeHandle.stepFine).round()),
|
||||
onIncrease: () => _bump(DragResizeHandle.stepFine),
|
||||
onDecrease: () => _bump(-DragResizeHandle.stepFine),
|
||||
child: FocusableActionDetector(
|
||||
@@ -87,11 +89,55 @@ class _DragResizeHandleState extends State<DragResizeHandle> {
|
||||
);
|
||||
}
|
||||
|
||||
String _semanticLabel() {
|
||||
final axis = widget.axis == Axis.horizontal ? 'width' : 'height';
|
||||
if (widget.slot == Slots.sidebar) return 'Sidebar $axis';
|
||||
if (widget.slot == Slots.contextPanel) return 'Context panel $axis';
|
||||
return '${widget.slot.value} $axis';
|
||||
/// The slider's accessible name (D-21, `core` namespace). With no kernel in
|
||||
/// scope (isolated tests) the i18n lookups fall back to the English
|
||||
/// placeholders, mirroring the widget-facing facade's degrade behaviour.
|
||||
String _semanticLabel(BuildContext context) {
|
||||
final i18n = ClideKernel.maybeOf(context)?.i18n;
|
||||
final axis = widget.axis == Axis.horizontal
|
||||
? (i18n?.string('resize.axis.width', namespace: 'core', placeholder: 'width') ?? 'width')
|
||||
: (i18n?.string('resize.axis.height', namespace: 'core', placeholder: 'height') ?? 'height');
|
||||
if (widget.slot == Slots.sidebar) {
|
||||
return i18n?.interpolated(
|
||||
'resize.sidebar',
|
||||
namespace: 'core',
|
||||
placeholder: 'Sidebar {axis}',
|
||||
replacers: [I18nReplacer(from: '{axis}', replace: axis)],
|
||||
) ??
|
||||
'Sidebar $axis';
|
||||
}
|
||||
if (widget.slot == Slots.contextPanel) {
|
||||
return i18n?.interpolated(
|
||||
'resize.contextPanel',
|
||||
namespace: 'core',
|
||||
placeholder: 'Context panel {axis}',
|
||||
replacers: [I18nReplacer(from: '{axis}', replace: axis)],
|
||||
) ??
|
||||
'Context panel $axis';
|
||||
}
|
||||
return i18n?.interpolated(
|
||||
'resize.slot',
|
||||
namespace: 'core',
|
||||
placeholder: '{slot} {axis}',
|
||||
replacers: [
|
||||
I18nReplacer(from: '{slot}', replace: widget.slot.value),
|
||||
I18nReplacer(from: '{axis}', replace: axis),
|
||||
],
|
||||
) ??
|
||||
'${widget.slot.value} $axis';
|
||||
}
|
||||
|
||||
/// `'{n} pixels'` via the `core` catalog (falls back to English with no
|
||||
/// kernel in scope).
|
||||
String _pixels(BuildContext context, int n) {
|
||||
final i18n = ClideKernel.maybeOf(context)?.i18n;
|
||||
return i18n?.interpolated(
|
||||
'resize.pixels',
|
||||
namespace: 'core',
|
||||
placeholder: '{n} pixels',
|
||||
replacers: [I18nReplacer(from: '{n}', replace: '$n')],
|
||||
) ??
|
||||
'$n pixels';
|
||||
}
|
||||
|
||||
Map<ShortcutActivator, Intent> _shortcuts() {
|
||||
|
||||
Reference in New Issue
Block a user