|
| 1 | +# nl.firstlinesoftware.com — Dutch static mirror |
| 2 | + |
| 3 | +A Dutch-language static version of [firstlinesoftware.com](https://firstlinesoftware.com), |
| 4 | +hosted on GitHub Pages at [nl.firstlinesoftware.com](https://nl.firstlinesoftware.com). |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## How this site was built |
| 9 | + |
| 10 | +### 1. Fetch the current English site with all assets |
| 11 | + |
| 12 | +```bash |
| 13 | +wget --page-requisites \ |
| 14 | + --span-hosts \ |
| 15 | + --convert-links \ |
| 16 | + --no-host-directories \ |
| 17 | + --adjust-extension \ |
| 18 | + -e robots=off \ |
| 19 | + https://firstlinesoftware.com/ |
| 20 | +``` |
| 21 | + |
| 22 | +`--convert-links` rewrites absolute URLs to relative ones in `index.html`. |
| 23 | +`--adjust-extension` appends `.css`/`.js` to files that have query strings |
| 24 | +(e.g. `style.alt.css?ver=123` → saved as `style.alt.css?ver=123.css`). |
| 25 | + |
| 26 | +**Important:** wget overwrites `index.html` with a fresh English copy. Apply |
| 27 | +all Dutch translations *after* running wget, not before. |
| 28 | + |
| 29 | +### 2. Dynamically loaded assets wget misses |
| 30 | + |
| 31 | +Some assets are injected at runtime by JavaScript and are not reachable by |
| 32 | +wget's static crawler. Download them manually: |
| 33 | + |
| 34 | +```bash |
| 35 | +# HubSpot form CSS (injected by forms/v2.js) |
| 36 | +curl -o wp-content/themes/cleanslate/althubspot.css \ |
| 37 | + "https://firstlinesoftware.com/wp-content/themes/cleanslate/althubspot.css?ver=..." |
| 38 | +``` |
| 39 | + |
| 40 | +Check the browser Network tab for any remaining 404s after first deploy. |
| 41 | + |
| 42 | +### 3. Files with `?ver=...` query strings |
| 43 | + |
| 44 | +wget saves files with a literal `?` in the filename |
| 45 | +(e.g. `theme.js?ver=1773578688`). The HTML references them with a |
| 46 | +URL-encoded `%3F` (e.g. `src="theme.js%3Fver=1773578688"`). |
| 47 | + |
| 48 | +Python's `http.server` decodes `%3F` back to `?` before the filesystem |
| 49 | +lookup, so local serving works correctly without renaming files. |
| 50 | + |
| 51 | +For files where the browser sends a real query string (e.g. `althubspot.css?ver=...`), |
| 52 | +Python's `http.server` strips the query part and serves `althubspot.css` — so |
| 53 | +save those without the `?ver=...` suffix. |
| 54 | + |
| 55 | +### 4. Dutch translation |
| 56 | + |
| 57 | +Translations were applied via Python `str.replace()` after fetching. Watch out for: |
| 58 | + |
| 59 | +- HTML entities: the source HTML uses `&` (not `&`) and `’` (not `'`) |
| 60 | +- Unicode characters that look like spaces/newlines: U+2028 (LINE SEPARATOR), |
| 61 | + U+00A0 (non-breaking space), U+2019 (right single quote) |
| 62 | +- Always inspect raw bytes when a replacement fails: `open(f, 'rb').read()` |
| 63 | + |
| 64 | +Key metadata changes for Dutch: |
| 65 | +```html |
| 66 | +<html lang="nl-NL"> |
| 67 | +<meta property="og:locale" content="nl_NL" /> |
| 68 | +<meta property="og:url" content="https://nl.firstlinesoftware.com/" /> |
| 69 | +<!-- JSON-LD --> |
| 70 | +"inLanguage": "nl-NL" |
| 71 | +``` |
| 72 | + |
| 73 | +### 5. Dutch text layout issues |
| 74 | + |
| 75 | +Dutch words are longer than English on average. Check for overflow/wrapping |
| 76 | +problems using Playwright + headless Chromium: |
| 77 | + |
| 78 | +```python |
| 79 | +# Accurate visual line count using Range.getClientRects() |
| 80 | +# (scrollHeight/lineHeight gives false positives due to CSS margins) |
| 81 | +def visual_lines(el): |
| 82 | + return el.evaluate('''e => { |
| 83 | + const range = document.createRange(); |
| 84 | + range.selectNodeContents(e); |
| 85 | + const ys = new Set([...range.getClientRects()].map(r => Math.round(r.top))); |
| 86 | + return ys.size; |
| 87 | + }''') |
| 88 | +``` |
| 89 | + |
| 90 | +Fix overflow with CSS hyphenation (added to `<head>`): |
| 91 | +```html |
| 92 | +<style> |
| 93 | +body { hyphens: auto; -webkit-hyphens: auto; } |
| 94 | +a.button, button, nav a, .footer-nav a { hyphens: none; -webkit-hyphens: none; } |
| 95 | +</style> |
| 96 | +``` |
| 97 | + |
| 98 | +Shorten translations for narrow containers (mode panes ~312px, SDLC section |
| 99 | +~530px, leadership figcaptions, stats labels). |
| 100 | + |
| 101 | +### 6. Cookiebot on a static site |
| 102 | + |
| 103 | +The WordPress Cookiebot plugin uses `data-implementation="wp"`, which causes |
| 104 | +`uc.js` to fetch `/{cbid}/cc.js` from your own domain — a dynamic WordPress |
| 105 | +endpoint that doesn't exist on a static site. |
| 106 | + |
| 107 | +**Use the standard (non-WordPress) Cookiebot embed instead:** |
| 108 | +```html |
| 109 | +<script id="Cookiebot" |
| 110 | + src="https://consent.cookiebot.com/uc.js" |
| 111 | + data-cbid="990e16bd-2861-4a00-a4ea-4dbe73a34971" |
| 112 | + data-blockingmode="auto"> |
| 113 | +</script> |
| 114 | +``` |
| 115 | + |
| 116 | +- No `data-implementation="wp"` — everything loads from Cookiebot's CDN |
| 117 | +- No `data-georegions` — that's also WordPress-plugin-specific |
| 118 | +- The CBID `990e16bd...` is configured for `nl.firstlinesoftware.com` in the |
| 119 | + Cookiebot admin (different from the main site's CBID `066e3009...`) |
| 120 | + |
| 121 | +### 7. Footer |
| 122 | + |
| 123 | +```html |
| 124 | +Copyright © 2026 First Line Software B.V. (KVK 58362126) |
| 125 | +``` |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## Updating the site |
| 130 | + |
| 131 | +When `firstlinesoftware.com` is updated: |
| 132 | + |
| 133 | +1. Run the wget command above (saves fresh assets, overwrites `index.html`) |
| 134 | +2. Re-apply Dutch translations to the new `index.html` |
| 135 | +3. Check for new dynamically loaded assets in the browser Network tab |
| 136 | +4. Run the Playwright layout checker for new overflow issues |
| 137 | +5. Commit and push |
0 commit comments