diff --git a/package.json b/package.json index 00a5415..620bde4 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dep:check": "depcruise src/app --config .dependency-cruiser.js", "dep:graph": "bash scripts/dep-graph.sh", "gen:snippets": "node scripts/gen-snippets.mjs", + "serve:i18n": "ng build --localize && node scripts/serve-i18n.mjs", "ci": "bash scripts/ci-local.sh", "e2e": "playwright test", "extract-i18n": "ng extract-i18n --output-path src/locale" diff --git a/scripts/serve-i18n.mjs b/scripts/serve-i18n.mjs new file mode 100644 index 0000000..ef64761 --- /dev/null +++ b/scripts/serve-i18n.mjs @@ -0,0 +1,53 @@ +#!/usr/bin/env node +// Serve the localized production build so the language switcher actually works locally. +// `ng build --localize` emits dist/.../browser/{nl,en}/ (each with base href /nl/ or /en/). +// Plain `ng serve` (npm start) serves only nl at /, so switching 404s there — this static +// server serves both locale subdirs with per-locale SPA fallback (a miss under //… +// serves that locale's index.html), so deep-link switches resolve. Demo only, not prod infra. +import { createServer } from 'node:http'; +import { readFile } from 'node:fs/promises'; +import { join, extname, normalize } from 'node:path'; + +const ROOT = 'dist/atomic-design-poc/browser'; +const PORT = 4300; +const MIME = { + '.html': 'text/html', + '.js': 'text/javascript', + '.mjs': 'text/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.svg': 'image/svg+xml', + '.ico': 'image/x-icon', + '.woff2': 'font/woff2', + '.png': 'image/png', +}; + +const send = (res, status, body, type) => { + res.writeHead(status, { 'content-type': type }); + res.end(body); +}; + +createServer(async (req, res) => { + const url = decodeURIComponent((req.url ?? '/').split('?')[0]); + // Landing at / has no locale bundle — redirect to Dutch. + if (url === '/') { + res.writeHead(302, { location: '/nl/' }); + return res.end(); + } + const rel = normalize(url).replace(/^(\.\.[/\\])+/, ''); // no path traversal + const locale = url.startsWith('/en/') ? 'en' : 'nl'; + try { + const file = await readFile(join(ROOT, rel)); + send(res, 200, file, MIME[extname(rel)] ?? 'application/octet-stream'); + } catch { + // SPA fallback to the requested locale's index.html. + try { + const index = await readFile(join(ROOT, locale, 'index.html')); + send(res, 200, index, 'text/html'); + } catch { + send(res, 404, 'Not found', 'text/plain'); + } + } +}).listen(PORT, () => { + console.log(`Serving ${ROOT} at http://localhost:${PORT}/ (→ /nl/, /en/)`); +}); diff --git a/src/app/shared/layout/language-switcher/language-switcher.component.ts b/src/app/shared/layout/language-switcher/language-switcher.component.ts new file mode 100644 index 0000000..7c32947 --- /dev/null +++ b/src/app/shared/layout/language-switcher/language-switcher.component.ts @@ -0,0 +1,82 @@ +import { Component, computed, input } from '@angular/core'; +import { Locale, localeLinks } from './locale-links'; + +// CIBG-GAP EXTENSION: "Taal instellen" (designsystem.cibg.nl/componenten/taal-instellen) — no +// vendored Huisstijl class ships for it, so this is a small hand-rolled surface built from the +// token bridge. See cibg-gaps.mdx. +/** + * Organism: CIBG "Taal instellen" language switcher. A `