feat: i18n layer — locales, slug-map contract, typed dictionaries

- locales.ts: locale codes, html lang, hreflang, OG locale
- slugMap.ts: EN/NO route table + section anchors, twin-path resolver (the tested contract)
- typed EN/NO dictionaries implementing one interface (missing key = compile error)
- useDict accessor + interpolate helper; no runtime i18n library

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-04 00:54:02 +02:00
parent 9a75f73550
commit 63db8bf254
6 changed files with 454 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
import type { Dictionary } from './dictionary';
export const en: Dictionary = {
nav: {
home: 'Home',
projects: 'Projects',
experience: 'Experience',
about: 'About',
contact: 'Contact',
},
lang: {
switchTo: 'Switch to Norwegian',
en: 'EN',
no: 'NO',
},
theme: {
toLight: 'Switch to light mode',
toDark: 'Switch to dark mode',
},
cv: {
download: 'Download CV',
english: 'English CV',
norsk: 'Norwegian CV',
fileMeta: 'pdf · {size} kB',
},
project: {
readCaseStudy: 'Read case study',
explore: 'Explore',
all: 'All projects',
caseStudyEyebrow: 'Case study · personal product',
capabilityEyebrow: 'Capability · infrastructure',
onThisPage: 'On this page',
repository: 'Repository',
liveDemo: 'Live demo',
prev: 'Previous',
next: 'Next',
status: {
active: 'Active',
'in-development': 'In development',
archived: 'Archived',
},
tldr: { what: 'What', why: 'Why', stack: 'Stack', role: 'Role' },
},
form: {
name: 'Name',
email: 'E-mail',
message: 'Message',
send: 'Send message',
sending: 'Sending',
successTitle: 'Message sent',
successBody: 'Thanks — I usually reply within a day.',
errorTitle: 'Something went wrong',
errorBody: 'The message could not be sent. Please e-mail me directly instead.',
required: 'This field is required',
invalidEmail: 'Please enter a valid e-mail address',
orEmail: 'Or reach me directly',
copy: 'Copy',
copied: 'Copied',
},
// The first-visit hint appears only on EN pages, inviting a Norwegian-preferring
// visitor to switch — so its text is written in Norwegian (ROUTING_SPEC §3).
hint: {
text: 'Denne siden finnes på norsk',
action: 'Bytt til norsk',
dismiss: 'Lukk',
},
notFound: {
code: 'route not found',
title: 'No such page',
body: 'The page you were looking for does not exist or has moved.',
home: 'Go to homepage',
projects: 'View projects',
},
a11y: {
skipToContent: 'Skip to content',
primaryNav: 'Primary',
openMenu: 'Open menu',
closeMenu: 'Close menu',
breadcrumb: 'Breadcrumb',
backToTop: 'Back to top',
},
footer: {
tagline: 'Systems developer based in Tønsberg, Norway.',
colophon: 'How this site works',
availability: 'Open to remote, hybrid or on-site roles.',
copyright: '© {year} Connor Babbington',
},
};