- Merge lang package into constants (add IsValidLang, ValidateLang, AllLangs)
- Rename internal/services to internal/email for consistency with pdf package
- Rename types to avoid redundancy: EmailService→Service, EmailConfig→Config
- Update all imports and references across codebase
- Delete internal/lang directory (functions moved to constants)
Eliminate per-request file I/O by loading CV and UI data once at startup.
## Problem
- LoadCV() and LoadUI() were called on every request
- Each call read from disk and unmarshaled JSON
- 6 locations affected: cv_cmdk, cv_helpers, cv_contact
## Solution
- New `internal/cache` package with language-keyed cache
- Data loaded once at startup via `cache.New(["en", "es"])`
- Handlers use `h.dataCache.GetCV(lang)` / `GetUI(lang)`
- Thread-safe concurrent reads via sync.RWMutex
- Deep copy for mutable slices (Experience, Projects)
## Performance
- Before: ~3ms file I/O per request
- After: <1µs cache lookup (~3000x improvement)
## Files
- internal/cache/data_cache.go (new)
- internal/cache/data_cache_test.go (new)
- internal/cache/README.md (new)
- internal/handlers/cv.go (added dataCache field)
- internal/handlers/cv_*.go (use cache)
- main.go (initialize cache at startup)
Architecture updates:
- Add EmailService documentation with config and flow diagram
- Update CVHandler struct to show all dependencies
- Add new middleware components (BrowserOnly, RateLimiter, etc.)
- Update package structure to include services, pdf, validation
New unit tests for HandleContact (9 tests):
- Valid submission
- Missing email/message validation
- Honeypot bot protection
- Timing-based bot protection (too fast)
- Invalid HTTP method (405)
- Invalid email format
- Message too short
- Spanish language support
Includes MockEmailService for isolated testing.