Localization Boundary & Source Generator

PowerPortalsPro pre-fetches every localization key a route will need before the route's first paint, so users never see the brief flash of fallback key-text that on-demand localization libraries normally produce. The mechanism has two halves — a build-time source generator that emits a per-component manifest of the keys each component needs, and a runtime LocalizationBoundary component that wraps the route view and uses that manifest to issue a single batched HTTP fetch for the keys before letting the route render.

The LocalizationBoundary Component

LocalizationBoundary wraps AuthorizeRouteView (or RouteView) inside Routes.razor. On every navigation it derives a key-prefix from RouteData.PageType, unions it with any BaselinePrefixes the consumer configures, calls IAsyncStringLocalizer.EnsurePrefixesLoadedAsync for the combined token list, and holds children off from rendering until the fetch completes. While the fetch is in flight a translucent overlay with a spinner covers the viewport so the previous page's stale text isn't visible.

Baseline Prefixes

Some keys are needed on every page — layout strings, common buttons, framework component labels. Pass these as BaselinePrefixes so they're included in every per-route fetch (the localizer dedupes already-loaded prefixes, so the cost is paid once per session). LocalizationBaselines.Default ships a curated set covering the framework's components.PowerPortalsPro.Web.Blazor.* namespaces; concatenate it with your app's own cross-cutting prefixes (e.g. app) when you wire the boundary up.

Wiring It Up

The starter template configures LocalizationBoundary for you. If you're integrating it manually, place it inside Routes.razor wrapping the route view, like this:

@using PowerPortalsPro.Web.Blazor.FluentUI.Components

<Router AppAssembly="typeof(MyApp.RouteUrls).Assembly">
    <Found Context="routeData">
        <LocalizationBoundary RouteData="routeData"
                              BaselinePrefixes="_baselinePrefixes">
            <AuthorizeRouteView RouteData="routeData"
                                DefaultLayout="typeof(Layout.MainLayout)" />
        </LocalizationBoundary>
    </Found>
</Router>

@code {
    private readonly IReadOnlyList<string> _baselinePrefixes =
        LocalizationBaselines.Default.Concat(new[] { "app" }).ToArray();
}

The Source Generator and Manifest

The PowerPortalsPro.Localization NuGet package bundles a Roslyn source generator that scans every *.razor and code-behind in the consuming project at build time. For each component (anything inheriting ComponentBase) it emits a partial-class extension carrying a public static readonly LocalizationKeyManifest LocalizationManifest field. The manifest contains the transitive set of localization keys the component (and every statically-declared child it renders) requests at runtime — so when LocalizationBoundary issues its pre-fetch, the server has the complete list of strings the page is about to need.

What the Generator Detects

The scanner picks up the common patterns automatically — no annotations required:

Inspecting a Manifest

The generated LocalizationManifest field is an ordinary public static — you can inspect it from any C# code or expand it in your debugger. The keys are sorted deterministically so incremental builds don't churn the generated file.

// Generated by PowerPortalsPro.Localization.SourceGenerators
public partial class Dashboard
{
    public static readonly LocalizationKeyManifest LocalizationManifest =
        new LocalizationKeyManifest(
            "components.MyApp.Pages.Demos.Dashboard",
            "components.MyApp.Pages.Demos.DashboardMainDemo",
            "tables.[*].views.a1b2c3d4-e5f6-4789-abcd-112233445566",
            "tables.opportunity"
        );
}

On-Demand Fallback

Anything the generator can't resolve at compile time — keys built from runtime expressions, new Guid(SomeQualifiedConst) references, etc. — falls through to the localizer's on-demand path. Cache misses queue the missing key, debounce briefly, then issue a batched fetch in the background; LocalizationBoundary remounts the descendant subtree once the fetch completes so stale fallback text gets corrected automatically.

Note

Reference PowerPortalsPro.Localization from any project that hosts Blazor components. The package ships both the runtime types (IStringLocalizer, IAsyncStringLocalizer, LocalizationKeyManifest, LocalizationBoundary) and the source generator as an analyzer asset — consumers get manifest emission automatically on the next build with no additional configuration.