refactor(i18n): route remaining extension labels through the catalog (T-468)

Migrate the user-facing strings in menubar, output, search, deeplink, editor,
files, terminal, markdown, and vim to ClideSettings.i18n.string/.interpolated;
extend the existing catalogs and create the missing ones (deeplink, output,
search, markdown, menubar, vim). vim mode labels now resolve through
builtin.vim (default still `-- NORMAL --`, now localizable). A dock-status test
seeds builtin.output so the widget's own i18n lookups resolve instead of
warning into the logRing it counts. No en_US behaviour change (D-21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 22:16:20 +02:00
co-authored by Claude Opus 4.8
parent 17d22b9571
commit c5500dc235
25 changed files with 495 additions and 97 deletions
+38 -7
View File
@@ -32,18 +32,43 @@ class AboutDialog extends StatelessWidget {
const SizedBox(height: 2),
const ClideText(clideTagline, muted: true, fontSize: 13),
const SizedBox(height: 16),
_Kv(label: 'Version', value: clideVersion, tokens: tokens),
_Kv(label: 'Commit', value: clideCommit, tokens: tokens),
_Kv(label: 'Built', value: _formatDate(clideDate), tokens: tokens),
_Kv(label: 'Repository', value: clideRepository, tokens: tokens),
_Kv(
label: ClideSettings.i18n.string(context, 'about.version', namespace: 'builtin.menubar', placeholder: 'Version'),
value: clideVersion,
tokens: tokens,
),
_Kv(
label: ClideSettings.i18n.string(context, 'about.commit', namespace: 'builtin.menubar', placeholder: 'Commit'),
value: clideCommit,
tokens: tokens,
),
_Kv(
label: ClideSettings.i18n.string(context, 'about.built', namespace: 'builtin.menubar', placeholder: 'Built'),
value: _formatDate(clideDate),
tokens: tokens,
),
_Kv(
label: ClideSettings.i18n.string(context, 'about.repository', namespace: 'builtin.menubar', placeholder: 'Repository'),
value: clideRepository,
tokens: tokens,
),
const SizedBox(height: 16),
ClideText('Bundled dependencies', fontSize: clideFontCaption, color: tokens.globalTextMuted),
ClideText(
ClideSettings.i18n.string(context, 'licenses.heading', namespace: 'builtin.menubar', placeholder: 'Bundled dependencies'),
fontSize: clideFontCaption,
color: tokens.globalTextMuted,
),
const SizedBox(height: 6),
_Licenses(tokens: tokens),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [ClideButton(label: 'Close', onPressed: onDismiss)],
children: [
ClideButton(
label: ClideSettings.i18n.string(context, 'button.close', namespace: 'builtin.menubar', placeholder: 'Close'),
onPressed: onDismiss,
),
],
),
],
),
@@ -86,7 +111,13 @@ class _Licenses extends StatelessWidget {
future: loadLicenses(),
builder: (context, snap) {
if (!snap.hasData) {
return ClideText(snap.hasError ? 'Licenses unavailable.' : 'Loading…', fontSize: 12, color: tokens.globalTextMuted);
return ClideText(
snap.hasError
? ClideSettings.i18n.string(context, 'licenses.unavailable', namespace: 'builtin.menubar', placeholder: 'Licenses unavailable.')
: ClideSettings.i18n.string(context, 'licenses.loading', namespace: 'builtin.menubar', placeholder: 'Loading…'),
fontSize: 12,
color: tokens.globalTextMuted,
);
}
final deps = snap.data!.dependencies;
return Container(
+49 -8
View File
@@ -123,9 +123,17 @@ class _OpenFolderDialogState extends State<OpenFolderDialog> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ClideText('Open project', fontSize: 16, fontWeight: FontWeight.w600),
ClideText(
ClideSettings.i18n.string(context, 'dialog.openProject.title', namespace: 'builtin.menubar', placeholder: 'Open project'),
fontSize: 16,
fontWeight: FontWeight.w600,
),
const SizedBox(height: 4),
const ClideText('Enter the path to a git repository.', muted: true, fontSize: 13),
ClideText(
ClideSettings.i18n.string(context, 'dialog.openProject.body', namespace: 'builtin.menubar', placeholder: 'Enter the path to a git repository.'),
muted: true,
fontSize: 13,
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
@@ -148,14 +156,29 @@ class _OpenFolderDialogState extends State<OpenFolderDialog> {
onSubmitted: (_) => unawaited(_submit()),
),
),
if (_error != null) ...[const SizedBox(height: 8), ClideText(_error!, color: tokens.statusError, fontSize: 12)],
if (_error != null) ...[
const SizedBox(height: 8),
ClideText(
ClideSettings.i18n.string(context, 'dialog.openProject.error', namespace: 'builtin.menubar', placeholder: 'Not a git repository'),
color: tokens.statusError,
fontSize: 12,
),
],
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ClideButton(label: 'Cancel', onPressed: widget.onCancel),
ClideButton(
label: ClideSettings.i18n.string(context, 'button.cancel', namespace: 'builtin.menubar', placeholder: 'Cancel'),
onPressed: widget.onCancel,
),
const SizedBox(width: 8),
ClideButton(label: _loading ? 'Opening…' : 'Open', onPressed: _loading ? null : _submit),
ClideButton(
label: _loading
? ClideSettings.i18n.string(context, 'button.opening', namespace: 'builtin.menubar', placeholder: 'Opening…')
: ClideSettings.i18n.string(context, 'button.open', namespace: 'builtin.menubar', placeholder: 'Open'),
onPressed: _loading ? null : _submit,
),
],
),
],
@@ -186,15 +209,33 @@ class NotARepoDialog extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ClideText('No git repo found', fontSize: 16, fontWeight: FontWeight.w600),
ClideText(
ClideSettings.i18n.string(context, 'dialog.notRepo.title', namespace: 'builtin.menubar', placeholder: 'No git repo found'),
fontSize: 16,
fontWeight: FontWeight.w600,
),
const SizedBox(height: 8),
ClideText(path, muted: true, fontSize: 13),
const SizedBox(height: 8),
const ClideText('A clide project root requires a git repository.', muted: true, fontSize: 13),
ClideText(
ClideSettings.i18n.string(
context,
'dialog.notRepo.body',
namespace: 'builtin.menubar',
placeholder: 'A clide project root requires a git repository.',
),
muted: true,
fontSize: 13,
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [ClideButton(label: 'OK', onPressed: () => onDismiss())],
children: [
ClideButton(
label: ClideSettings.i18n.string(context, 'button.ok', namespace: 'builtin.menubar', placeholder: 'OK'),
onPressed: () => onDismiss(),
),
],
),
],
),
+15 -1
View File
@@ -187,10 +187,24 @@ class _TopMenuButtonState extends State<_TopMenuButton> {
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: clideInsetStandard),
color: open || hovered ? tokens.listItemHoverBackground : null,
child: ClideText(widget.menu.title, fontSize: 12, color: open || hovered ? tokens.globalForeground : tokens.chromeForeground),
child: ClideText(_menuTitle(ctx, widget.menu.title), fontSize: 12, color: open || hovered ? tokens.globalForeground : tokens.chromeForeground),
),
),
),
);
}
/// Localize a curated top-menu title. The curated tree authors English
/// titles ('File'/'View'/'Help'); resolve each to its catalog entry, falling
/// back to the authored English (which also drives the Alt mnemonic).
String _menuTitle(BuildContext context, String title) {
final key = switch (title) {
'File' => 'menu.file',
'View' => 'menu.view',
'Help' => 'menu.help',
_ => null,
};
if (key == null) return title;
return ClideSettings.i18n.string(context, key, namespace: 'builtin.menubar', placeholder: title);
}
}