refactor(i18n): route planning-pane labels through the catalog (T-467)
Migrate the user-facing strings in builtin.tickets, builtin.pql, builtin.decisions, and builtin.problems (list/detail views, section headers, filters, empty/loading/error states, type chips) to ClideSettings.i18n; extend each extension's catalog. The detail/links tabs that had a bare title now wire titleKey + i18nNamespace so the tab title resolves too. Context threaded into the static label helpers. No en_US behaviour change (D-21). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,10 +42,22 @@ class _BacklinksViewState extends State<BacklinksView> {
|
||||
builder: (context, _) {
|
||||
final tokens = ClideSettings.theme.of(context).surface;
|
||||
if (c.activePath == null) {
|
||||
return const Padding(padding: EdgeInsets.all(12), child: ClideText('Open a file to see its links.', muted: true));
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(context, 'backlinks.empty', namespace: 'builtin.pql', placeholder: 'Open a file to see its links.'),
|
||||
muted: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Semantics(
|
||||
label: 'backlinks for ${c.activePath}',
|
||||
label: ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'backlinks.semantics',
|
||||
namespace: 'builtin.pql',
|
||||
placeholder: 'backlinks for {path}',
|
||||
replacers: [I18nReplacer(from: '{path}', replace: c.activePath!)],
|
||||
),
|
||||
container: true,
|
||||
explicitChildNodes: true,
|
||||
child: SingleChildScrollView(
|
||||
@@ -63,9 +75,21 @@ class _BacklinksViewState extends State<BacklinksView> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ClideText(c.error!, color: tokens.statusError, fontSize: clideFontCaption),
|
||||
),
|
||||
if (c.loading) const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)),
|
||||
_LinkGroup(label: 'Backlinks', links: c.backlinks, pathKey: 'source'),
|
||||
_LinkGroup(label: 'Outlinks', links: c.outlinks, pathKey: 'target'),
|
||||
if (c.loading)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ClideText(ClideSettings.i18n.string(context, 'backlinks.loading', namespace: 'builtin.pql', placeholder: 'Loading…'), muted: true),
|
||||
),
|
||||
_LinkGroup(
|
||||
label: ClideSettings.i18n.string(context, 'group.backlinks', namespace: 'builtin.pql', placeholder: 'Backlinks'),
|
||||
links: c.backlinks,
|
||||
pathKey: 'source',
|
||||
),
|
||||
_LinkGroup(
|
||||
label: ClideSettings.i18n.string(context, 'group.outlinks', namespace: 'builtin.pql', placeholder: 'Outlinks'),
|
||||
links: c.outlinks,
|
||||
pathKey: 'target',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -90,12 +114,29 @@ class _LinkGroup extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 8, top: 8, bottom: 2),
|
||||
child: ClideText('$label (${links.length})', fontSize: clideFontCaption, muted: true),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.interpolated(
|
||||
context,
|
||||
'group.label',
|
||||
namespace: 'builtin.pql',
|
||||
placeholder: '{label} ({count})',
|
||||
replacers: [
|
||||
I18nReplacer(from: '{label}', replace: label),
|
||||
I18nReplacer(from: '{count}', replace: '${links.length}'),
|
||||
],
|
||||
),
|
||||
fontSize: clideFontCaption,
|
||||
muted: true,
|
||||
),
|
||||
),
|
||||
if (links.isEmpty)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 2),
|
||||
child: ClideText('None', fontSize: clideFontCaption, muted: true),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 2),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(context, 'group.none', namespace: 'builtin.pql', placeholder: 'None'),
|
||||
fontSize: clideFontCaption,
|
||||
muted: true,
|
||||
),
|
||||
),
|
||||
for (final link in links) _LinkRow(link: link, pathKey: pathKey),
|
||||
],
|
||||
|
||||
@@ -21,6 +21,8 @@ class PqlExtension extends ClideExtension {
|
||||
id: 'pql.backlinks',
|
||||
slot: Slots.contextPanel,
|
||||
title: 'Links',
|
||||
titleKey: 'tab.links.title',
|
||||
i18nNamespace: id,
|
||||
icon: PhosphorIcons.byName('link'),
|
||||
priority: -80,
|
||||
build: (_) => const BacklinksView(),
|
||||
|
||||
@@ -104,7 +104,7 @@ class _PqlSearchBodyState extends State<PqlSearchBody> {
|
||||
if (widget.mode == PqlPaneMode.markdown)
|
||||
ClideFilterBox(
|
||||
address: 'search.pql.markdown',
|
||||
hint: 'Filter markdown…',
|
||||
hint: ClideSettings.i18n.string(context, 'filter.markdown.hint', namespace: 'builtin.pql', placeholder: 'Filter markdown…'),
|
||||
onChanged: (v) => unawaited(c.loadMarkdownFiles(glob: v.isEmpty ? null : '**/*$v*.md')),
|
||||
)
|
||||
else
|
||||
@@ -118,9 +118,19 @@ class _PqlSearchBodyState extends State<PqlSearchBody> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||
child: ClideText(c.error!, color: tokens.statusError, fontSize: clideFontCaption, maxLines: 3),
|
||||
),
|
||||
if (c.loading && c.results.isEmpty) const Padding(padding: EdgeInsets.all(12), child: ClideText('Loading…', muted: true)),
|
||||
if (c.loading && c.results.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ClideText(ClideSettings.i18n.string(context, 'loading', namespace: 'builtin.pql', placeholder: 'Loading…'), muted: true),
|
||||
),
|
||||
if (!c.loading && c.results.isEmpty && c.error == null && widget.mode == PqlPaneMode.markdown)
|
||||
const Padding(padding: EdgeInsets.all(12), child: ClideText('No markdown files found.', muted: true)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ClideText(
|
||||
ClideSettings.i18n.string(context, 'empty.markdown', namespace: 'builtin.pql', placeholder: 'No markdown files found.'),
|
||||
muted: true,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
@@ -160,7 +170,9 @@ class _PqlSearchInput extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ClideFilterBox(
|
||||
address: address,
|
||||
hint: dsl ? 'PQL query…' : 'Search vault…',
|
||||
hint: dsl
|
||||
? ClideSettings.i18n.string(context, 'search.query.hint', namespace: 'builtin.pql', placeholder: 'PQL query…')
|
||||
: ClideSettings.i18n.string(context, 'search.vault.hint', namespace: 'builtin.pql', placeholder: 'Search vault…'),
|
||||
debounce: dsl ? Duration.zero : const Duration(milliseconds: 300),
|
||||
onChanged: dsl ? (_) {} : (v) => unawaited(controller.search(v)),
|
||||
onSubmitted: dsl ? (v) => unawaited(controller.runQuery(v)) : (v) => unawaited(controller.search(v)),
|
||||
|
||||
Reference in New Issue
Block a user