feat(i18n): i18n key fields on command + settings contributions (T-462)
Lets manifest labels (command-palette/menu titles, settings labels) localize, not just displayed widget strings. Adds optional titleKey/i18nNamespace to CommandContribution and labelKey/helpKey/titleKey + a category i18nNamespace to the settings schema. The command palette and menu bar now resolve titles via a shared localizedCommandTitle helper — and the palette's fuzzy search matches the localized title too (PaletteController.titleResolver). No behaviour change until the per-extension keys + catalog entries land (placeholder == English). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,12 @@ class PaletteController extends ChangeNotifier {
|
||||
|
||||
final CommandRegistry _registry;
|
||||
|
||||
/// Resolves a command's display title (the widget sets this to an i18n-aware
|
||||
/// resolver so both the rendered title AND fuzzy search use the localized
|
||||
/// string, T-462). Defaults to the English title/id.
|
||||
String Function(CommandContribution cmd)? titleResolver;
|
||||
String _titleOf(CommandContribution c) => titleResolver?.call(c) ?? c.title ?? c.command;
|
||||
|
||||
bool _open = false;
|
||||
String _filter = '';
|
||||
int _selectedIndex = 0;
|
||||
@@ -103,7 +109,7 @@ class PaletteController extends ChangeNotifier {
|
||||
|
||||
final scored = <({CommandContribution cmd, int score})>[];
|
||||
for (final c in all) {
|
||||
final s = fuzzyScore((c.title ?? c.command).toLowerCase(), q);
|
||||
final s = fuzzyScore(_titleOf(c).toLowerCase(), q);
|
||||
if (s != null) scored.add((cmd: c, score: s));
|
||||
}
|
||||
scored.sort((a, b) {
|
||||
@@ -111,7 +117,7 @@ class PaletteController extends ChangeNotifier {
|
||||
if (byScore != 0) return byScore;
|
||||
final byRecent = rank(a.cmd.command).compareTo(rank(b.cmd.command));
|
||||
if (byRecent != 0) return byRecent;
|
||||
return (a.cmd.title ?? a.cmd.command).toLowerCase().compareTo((b.cmd.title ?? b.cmd.command).toLowerCase());
|
||||
return _titleOf(a.cmd).toLowerCase().compareTo(_titleOf(b.cmd).toLowerCase());
|
||||
});
|
||||
return [for (final s in scored) s.cmd];
|
||||
}
|
||||
|
||||
@@ -34,13 +34,17 @@ enum SettingsFieldKind {
|
||||
|
||||
/// One choice in a [SettingsFieldKind.select] field.
|
||||
class SettingsOption {
|
||||
const SettingsOption({required this.value, required this.label});
|
||||
const SettingsOption({required this.value, required this.label, this.labelKey});
|
||||
|
||||
/// Stored value.
|
||||
final String value;
|
||||
|
||||
/// Human label shown in the picker.
|
||||
/// Human label shown in the picker (English fallback).
|
||||
final String label;
|
||||
|
||||
/// Optional i18n key for [label], resolved in the owning category's namespace
|
||||
/// (T-462).
|
||||
final String? labelKey;
|
||||
}
|
||||
|
||||
/// One editable setting. [key] is a `SettingsStore` key — its `app.`/
|
||||
@@ -52,7 +56,9 @@ class SettingsField {
|
||||
required this.key,
|
||||
required this.kind,
|
||||
required this.label,
|
||||
this.labelKey,
|
||||
this.help,
|
||||
this.helpKey,
|
||||
this.defaultValue,
|
||||
this.options = const [],
|
||||
this.min,
|
||||
@@ -66,9 +72,15 @@ class SettingsField {
|
||||
final SettingsFieldKind kind;
|
||||
final String label;
|
||||
|
||||
/// Optional i18n key for [label], resolved in the category's namespace (T-462).
|
||||
final String? labelKey;
|
||||
|
||||
/// Optional one-line help shown under the label.
|
||||
final String? help;
|
||||
|
||||
/// Optional i18n key for [help], resolved in the category's namespace (T-462).
|
||||
final String? helpKey;
|
||||
|
||||
/// Value shown / restored when the key is unset (reset-to-default target).
|
||||
final Object? defaultValue;
|
||||
|
||||
@@ -97,9 +109,12 @@ class SettingsField {
|
||||
/// A carded group of fields (surface.md "sectioned cards"). [label] is the
|
||||
/// small-caps header rendered just above the card.
|
||||
class SettingsSection {
|
||||
const SettingsSection({required this.label, required this.fields});
|
||||
const SettingsSection({required this.label, required this.fields, this.labelKey});
|
||||
|
||||
final String label;
|
||||
|
||||
/// Optional i18n key for [label], resolved in the category's namespace (T-462).
|
||||
final String? labelKey;
|
||||
final List<SettingsField> fields;
|
||||
}
|
||||
|
||||
@@ -107,11 +122,20 @@ class SettingsSection {
|
||||
/// shows. Subsystems register these via `SettingsCategoryContribution`; the
|
||||
/// renderer draws them.
|
||||
class SettingsCategory {
|
||||
const SettingsCategory({required this.id, required this.title, required this.sections, this.iconName, this.priority = 0});
|
||||
const SettingsCategory({required this.id, required this.title, required this.sections, this.iconName, this.priority = 0, this.titleKey, this.i18nNamespace});
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
|
||||
/// Optional i18n key for [title] (the rail label), looked up in
|
||||
/// [i18nNamespace] (T-462).
|
||||
final String? titleKey;
|
||||
|
||||
/// The i18n namespace for this category's [titleKey] and every section/field/
|
||||
/// option key beneath it — usually the contributing extension's `id`. The
|
||||
/// renderer threads it down so the whole category localizes from one catalog.
|
||||
final String? i18nNamespace;
|
||||
|
||||
/// Phosphor glyph name, resolved via `PhosphorIcons.byName` at render (T-314).
|
||||
final String? iconName;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user