Skip to content

Edge-layer locale resolution and per-locale response caching for multilingual portals #391

@MajorTal

Description

@MajorTal

Context

Kychon's upcoming admin-content-management change adds per-locale block translations (a section_translations table that stores partial config overrides per language). The render path becomes:

  1. Detect the active locale (from URL prefix, cookie, or Accept-Language).
  2. Fetch sections + LEFT JOIN section_translations on the active locale.
  3. Deep-merge translation over base config.
  4. Render HTML.

Every request, every page, every visitor pays for the JOIN. Cache layering today (build-time bake + localStorage + network fetch) does not key on locale, so a returning Spanish visitor still triggers a fresh network fetch even when the page hasn't changed.

Current friction

  • prefixDefaultLocale: false Astro routing works for the default locale, but other locales rely on app-side localStorage preference + runtime t() lookups — there is no edge-level locale routing.
  • Run402's CDN cache key is path-based; serving /about to an English visitor and a Spanish visitor returns the same cached HTML (then the app swaps content via runtime hydrate). This works but causes a noticeable cold-paint flash of English content for non-English visitors.
  • Apps that want a fully locale-aware response (HTML pre-rendered with translated content) have to bypass the CDN or route through their own edge function.

Desired outcome

Run402 same-origin web routes could support locale-aware response caching natively:

# run402.deploy.json route entry
routes:
  - path: /about
    cache:
      vary: ['accept-language']   # cache per Accept-Language
      ttl: 300

Or a path-prefix locale model that Run402 normalizes:

i18n:
  defaultLocale: en
  locales: [en, es, fr, pt]
  routing:
    prefixDefaultLocale: false
    detect: ['cookie:wl_locale', 'accept-language']

Then any /about request from a Spanish visitor is served from the /es/about cache entry (or origin-rendered with locale=es available in the function context), with the CDN keying per locale automatically.

Bonus: expose locale in the routed HTTP function context so app render code reads it without sniffing headers:

export default async (req, ctx) => {
  const locale = ctx.locale;  // resolved per request
  // render with locale-aware data
};

Why it matters

  • Multilingual portals are a Kychon differentiator vs Wild Apricot (which has limited localization). Making them fast at the edge is a big quality bump.
  • Removes the cold-paint English-flash problem for non-English visitors.
  • Halves origin load on multilingual portals (cache hits per locale instead of always-cold renders).
  • The pattern generalizes to any Run402 app with localized content.

Workaround until shipped

Kychon's admin-content-management change implements the LEFT JOIN + deep-merge at the app render layer (Astro page-render.ts). Every request pays for the JOIN. Visitors continue to see a brief English flash before the runtime hydrate replaces content with their preferred locale.

Related Kychon change

/Users/talweiss/Developer/kychon/openspec/changes/admin-content-management/ — specifically specs/i18n/spec.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature-requestFlagged by /bugs as a feature request, not an auto-fixable bug

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions