Skip to content

sd-app: i18n/l10n Foundation + Localized Gross-Up

Section titled “sd-app: i18n/l10n Foundation + Localized Gross-Up”

Date: 2026-03-09 Session: Phase A (shadcn-svelte) + Phase B (Paraglide i18n) Status: ✅ Complete Commit: 4909ec9


Established two foundational layers for apps/sd-app:

  1. shadcn-svelte — copy-paste UI components (Button, Input, Label, Card) built on existing Tailwind CSS custom properties
  2. Paraglide i18n — compile-time translations via @inlang/paraglide-sveltekit with shared message files in packages/i18n/

Proved the foundation by localizing the RRSP Gross-Up calculator into /ca/en/gross-up (English) and /ca/fr/gross-up (French), both prerendered at build time.

  • shadcn-svelte: Button, Input, Label, Card components
  • cn() utility (clsx + tailwind-merge)
  • packages/i18n/ with en.json + fr.json (25 keys each)
  • Paraglide Vite plugin configured, src/paraglide/ generated at build
  • Route restructure: /[country]/[lang]/ with static prerender entries
  • Localized dashboard + gross-up calculator (Intl.NumberFormat for en-CA / fr-CA)
  • jurisdiction.ts (RRSP/TFSA vs 401k/Roth IRA config)
  • formatters.ts (locale-aware Intl.NumberFormat)
  • profile.svelte.ts global state store ($state class pattern)
  • TaxCalculatorInterface + CanadaTaxCalculator strategy pattern
  • Cloudflare edge _worker.js (Accept-Language detection, old-path 301s)
  • static/_redirects for reference
  • MiniAppsShowcase.astro updated (iframe src → /ca/en/gross-up?embed)
  • ESLint fixed: TypeScript parser for Svelte script blocks + rest-prop override

packages/i18n/ (new package)

  • package.json — @smart-debt/i18n workspace package
  • messages/en.json — 25 English keys
  • messages/fr.json — 25 French (Canadian) keys

apps/sd-app/

  • package.json — added @inlang/paraglide-sveltekit (dev), clsx, tailwind-merge
  • vite.config.ts — added paraglide() plugin
  • svelte.config.js — added $paraglide alias
  • .gitignore — added src/paraglide/
  • project.inlang/settings.json — Paraglide project config
  • src/paraglide-types.d.ts — TypeScript declarations for generated modules
  • src/app.css — added —card/—card-foreground CSS variables
  • tailwind.config.js — added card color tokens
  • src/lib/utils.ts — cn() helper
  • src/lib/i18n.ts — re-exports from $paraglide/runtime + messages
  • src/lib/jurisdiction.ts — country config (CA/US product names)
  • src/lib/formatters.ts — Intl.NumberFormat currency formatting
  • src/lib/stores/profile.svelte.ts — global $state profile
  • src/lib/calculators/interfaces.ts — TaxCalculatorInterface
  • src/lib/calculators/ca/index.ts — CanadaTaxCalculator
  • src/lib/components/ui/ — Button, Input, Label, Card (shadcn-style)
  • src/routes/+layout.svelte — stripped to bare (css import only)
  • src/routes/+page.svelte — redirect to /ca/en
  • src/routes/[country]/[lang]/ — full route tree (6 pages × 2 locales = 12 pages)
  • static/_worker.js — Cloudflare edge worker
  • static/_redirects — old-path redirects reference

Deleted:

  • src/routes/gross-up/+page.svelte
  • src/routes/int-only/+page.svelte
  • src/routes/term-loan/+page.svelte
  • src/routes/demo/+page.svelte → moved to [country]/[lang]/demo/

sites/Template:

  • MiniAppsShowcase.astro — iframe src updated to /ca/en/gross-up?embed

Root:

  • eslint.config.mjs — TypeScript parser for Svelte + ui component rule override
pnpm check: 0 errors · 0 warnings
pnpm build: ✓ 10 prerendered HTML pages
build/ca/en.html ← English dashboard
build/ca/en/gross-up.html ← "RRSP Gross-Up Calculator"
build/ca/en/demo.html
build/ca/en/int-only.html
build/ca/en/term-loan.html
build/ca/fr.html ← French dashboard
build/ca/fr/gross-up.html ← "Calculateur de majoration REER"
build/ca/fr/demo.html
build/ca/fr/int-only.html
build/ca/fr/term-loan.html
English: grep -c "RRSP Gross-Up Calculator" build/ca/en/gross-up.html → 2 ✓
French: grep -c "Calculateur de majoration REER" build/ca/fr/gross-up.html → 2 ✓
French dashboard: grep -c "Bientôt disponible" build/ca/fr.html → 1 ✓
  • Locale in URL path (/ca/en/, /ca/fr/) — SvelteKit entries() for prerendering
  • Paraglide compile-time — messages baked into prerendered HTML, zero runtime overhead
  • setLocale() in layout load — runs on both server (prerender) and client (navigation)
  • _worker.js — handles all Cloudflare requests; _redirects not processed when worker present
  • shadcn copy-paste model — components live in sd-app (not packages/components — different format)

None. Pre-existing ESLint limitations with Svelte files (TypeScript parser not configured) fixed as part of this session.

  • Add /us/en/ entries when US market is ready (just add to entries() array)
  • Add packages/i18n/messages/fr-CA.json if regional French distinction needed
  • Phase C: deploy to Cloudflare Pages and verify edge worker + Accept-Language detection
  • Write decision logs: i18n-Paraglide.md + Web Styling.md update