feat(i18n): load every extension's own namespace + facade lookup (T-462)

Foundation for routing hardcoded UI labels through the catalog (D-21). Two
enablers:

- ExtensionManager now eagerly loads each activated extension's own-id catalog
  namespace, not just the namespaces of localized tabs. An extension's id IS
  its catalog namespace (ClideExtension.t), so labels resolve through the
  catalog even for extensions that contribute no tab; a missing catalog file
  loads as an empty map, so it's harmless.
- ClideSettings.i18n gains string()/interpolated() lookups so widget call
  sites route through the one D-101 facade (like theme/fonts) instead of each
  re-deriving ClideKernel.of(context).i18n.

No user-visible change yet — placeholders equal the en_US catalog values; the
per-extension label migrations follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-18 09:54:56 +02:00
co-authored by Claude Opus 4.8
parent 2fe5d6d3e8
commit 6d2361cd61
5 changed files with 79 additions and 5 deletions
+7 -4
View File
@@ -162,11 +162,14 @@ class ExtensionManager extends ChangeNotifier {
_applyContribution(c);
applied.add(c);
}
// Eagerly load the i18n catalog for any localized tab this extension
// contributes, so its title resolves without a "namespace not
// registered" warning — and without the namespace having to be listed
// by hand at boot (T-155).
// Eagerly load the i18n catalogs this extension needs so its labels
// resolve through the catalog (D-21) instead of falling back to inline
// English placeholders — without listing namespaces by hand at boot
// (T-155, T-462). An extension's own id IS its catalog namespace (see
// [ClideExtension.t]); a missing catalog file loads as an empty map, so
// this is harmless for extensions that ship none.
for (final ns in {
id,
for (final c in ext.contributions)
if (c is TabContribution && c.i18nNamespace != null) c.i18nNamespace!,
}) {