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