|
| 1 | +# Architecture |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Static site built with vanilla HTML, CSS, and JavaScript. No build tools, no frameworks. Just native browser APIs and Bootstrap 5 for layout. |
| 6 | + |
| 7 | +**Philosophy:** Keep it simple. Add complexity only when necessary. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Translation System |
| 12 | + |
| 13 | +### Why Custom? |
| 14 | + |
| 15 | +Could've used i18next, but that's 50KB+ for something I can do in ~100 lines. |
| 16 | + |
| 17 | +### How It Works |
| 18 | + |
| 19 | +**Language detection:** |
| 20 | + |
| 21 | +1. localStorage (`language` key) - remembers user choice |
| 22 | +2. Browser language (`navigator.language`) |
| 23 | +3. Fallback to English |
| 24 | + |
| 25 | +**Translation keys:** |
| 26 | + |
| 27 | +- Flat JSON: `{"title": "...", "role": "..."}` |
| 28 | +- No nesting (keeps it simple) |
| 29 | +- Same keys across all languages (en, es, fr, pt) |
| 30 | + |
| 31 | +**HTML usage:** |
| 32 | + |
| 33 | +```html |
| 34 | +<p data-translate="role">Default text</p> |
| 35 | +<img data-translate-alt="imageAlt" alt="Default" /> |
| 36 | +``` |
| 37 | + |
| 38 | +**SEO:** Updates `<title>`, meta description, and Open Graph tags on language change. |
| 39 | + |
| 40 | +### Module Pattern |
| 41 | + |
| 42 | +Supports both browser and Jest: |
| 43 | + |
| 44 | +```javascript |
| 45 | +// Browser: global functions |
| 46 | +if (typeof window !== "undefined") { |
| 47 | + window.setLanguage = setLanguage; |
| 48 | +} |
| 49 | + |
| 50 | +// Jest: CommonJS exports |
| 51 | +if (typeof module !== "undefined" && module.exports) { |
| 52 | + module.exports = { setLanguage, ... }; |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +No build step needed. Works everywhere. |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +## Custom Language Selector |
| 61 | + |
| 62 | +Standard `<select>` can't do glassmorphism without hacky CSS. Custom dropdown gives full control. |
| 63 | + |
| 64 | +**Features:** |
| 65 | + |
| 66 | +- Click outside to close |
| 67 | +- Keyboard accessible |
| 68 | +- Pure JavaScript (no libs) |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +## CSS |
| 73 | + |
| 74 | +### Mobile-First |
| 75 | + |
| 76 | +Base styles = mobile. Enhance for desktop: |
| 77 | + |
| 78 | +```css |
| 79 | +.custom-select { |
| 80 | + width: 110px; |
| 81 | +} /* mobile */ |
| 82 | + |
| 83 | +@media (min-width: 992px) { |
| 84 | + .custom-select { |
| 85 | + width: 150px; |
| 86 | + } /* desktop */ |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +### Design Tokens |
| 91 | + |
| 92 | +```css |
| 93 | +:root { |
| 94 | + --clr-navy: #070f2b; |
| 95 | + --clr-gradient-mid: #3572ef; |
| 96 | + --clr-linkedin: #2856c7; |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +**Glassmorphism:** |
| 101 | + |
| 102 | +```css |
| 103 | +background: rgba(255, 255, 255, 0.1); |
| 104 | +backdrop-filter: blur(10px); |
| 105 | +``` |
| 106 | + |
| 107 | +**SVG colors:** Use filters instead of multiple files: |
| 108 | + |
| 109 | +```css |
| 110 | +filter: brightness(0) invert(1); /* white */ |
| 111 | +filter: brightness(0) invert(0); /* dark */ |
| 112 | +``` |
| 113 | + |
| 114 | +--- |
| 115 | + |
| 116 | +## Testing |
| 117 | + |
| 118 | +### Coverage: ~90% |
| 119 | + |
| 120 | +**What's tested:** |
| 121 | + |
| 122 | +- Translation loading (success + errors) |
| 123 | +- DOM updates (textContent, alt, meta tags) |
| 124 | +- localStorage integration |
| 125 | +- Dropdown behavior |
| 126 | + |
| 127 | +**What's not:** |
| 128 | + |
| 129 | +- Browser APIs (tested by browsers) |
| 130 | +- Bootstrap (tested by Bootstrap) |
| 131 | +- DOMContentLoaded listener (manual test) |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +## Tooling |
| 136 | + |
| 137 | +### ESLint Flat Config |
| 138 | + |
| 139 | +ESLint 9+ requires it. Multiple environments need different globals: |
| 140 | + |
| 141 | +- Browser JS: `setLanguage`, `getCurrentLanguage` |
| 142 | +- Test files: `describe`, `test`, `expect` |
| 143 | +- Node scripts: `module`, `require` |
| 144 | + |
| 145 | +### Prettier |
| 146 | + |
| 147 | +```json |
| 148 | +{ |
| 149 | + "singleQuote": false, |
| 150 | + "trailingComma": "none", |
| 151 | + "arrowParens": "always", |
| 152 | + "tabWidth": 4 |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +Personal preference from C#/TypeScript background. |
| 157 | + |
| 158 | +### Jest + jsdom |
| 159 | + |
| 160 | +Need DOM APIs without a browser. Jest runs tests in Node.js with simulated DOM. |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Deployment & CI/CD |
| 165 | + |
| 166 | +### GitHub Actions Workflow |
| 167 | + |
| 168 | +**On Pull Request:** |
| 169 | + |
| 170 | +```yaml |
| 171 | +- Format check (Prettier) |
| 172 | +- Lint (ESLint) |
| 173 | +- Tests (Jest with coverage) |
| 174 | +- Upload coverage artifact |
| 175 | +``` |
| 176 | +
|
| 177 | +**On Push to Main:** |
| 178 | +
|
| 179 | +```yaml |
| 180 | +- All PR checks |
| 181 | +- Build (prepare src/ folder) |
| 182 | +- Deploy to GitHub Pages |
| 183 | +``` |
| 184 | +
|
| 185 | +**Branch Protection:** Direct pushes to main are blocked. Must use PRs. |
| 186 | +
|
| 187 | +### GitHub Pages |
| 188 | +
|
| 189 | +**Configuration:** |
| 190 | +
|
| 191 | +- Source: GitHub Actions (not branch-based) |
| 192 | +- Deploys: `./src` directory |
| 193 | +- URL: https://fernandotonacoder.github.io |
| 194 | + |
| 195 | +No build step needed - just copy static files. |
| 196 | + |
| 197 | +--- |
| 198 | + |
| 199 | +## Performance |
| 200 | + |
| 201 | +- Async translation loading (non-blocking) |
| 202 | +- Only loads selected language |
| 203 | +- System fonts (no font loading) |
| 204 | +- Total JS: ~250 lines |
| 205 | +- Page load: < 1s on 3G |
| 206 | + |
| 207 | +--- |
| 208 | + |
| 209 | +## Adding Features |
| 210 | + |
| 211 | +### New Translation Key |
| 212 | + |
| 213 | +1. Add to all 4 locale files |
| 214 | +2. Use `data-translate="key"` in HTML |
| 215 | +3. Test manually |
| 216 | + |
| 217 | +### New Language |
| 218 | + |
| 219 | +1. Create `/locales/{code}.json` |
| 220 | +2. Update `supportedLangs` in `translations.js` |
| 221 | +3. Update `languageNames` in `custom-select.js` |
| 222 | +4. Add HTML option |
0 commit comments