mykiwi/mykiwi.blogpublic Fork
⑂ 549059e
Commits
⬇ Clone ▾
git clone https://git.rickub.com/mykiwi/mykiwi.blog.git
git clone ssh://git@rickub.com/mykiwi/mykiwi.blog.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Check the built pages' typography with JoliTypo

`make typo` (nix run .#typo-check) runs JoliTypo 2.0's phar over every
language in ./result and prints what it would change, exiting 1 if that
is anything. It reports rather than fixes, so the fix lands in the
markdown or the templates.
Romain Gautier committed 2026-09-26T15:46:55+02:00 Browse files
549059e parent: b148e7c
modified Makefile +7 -1
@@ -21,7 +21,7 @@ PROD_PORT ?= 8788
2121 # deploy-% targets aren't listed here: giving an instantiated pattern target
2222 # (e.g. deploy-en) to .PHONY creates an explicit no-recipe rule for it, which
2323 # then blocks the deploy-% pattern rule below from ever supplying a recipe.
24-.PHONY: help dev build check-tracked serve prod-server infra-plan infra-apply deploy clean
24+.PHONY: help dev build check-tracked serve prod-server typo infra-plan infra-apply deploy clean
2525
2626 help:
2727 @grep -E '^[a-zA-Z_.-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'
@@ -71,6 +71,12 @@ serve: ## Run the Hugo dev server with live reload
7171 prod-server: build ## Serve the built site exactly as deployed, e.g. `make prod-server SITE_LANG=fr PROD_PORT=8080`
7272 nix run .#prod-server -- $(SITE_LANG) $(PROD_PORT)
7373
74+# Read-only: prints what JoliTypo would change in ./result, e.g. `make typo
75+# TYPO_LANGS=fr`. Exits 1 if there is anything, so it can gate CI later.
76+TYPO_LANGS ?= $(LANGS)
77+typo: build ## Show typography fixes JoliTypo would make in the built pages
78+ nix run .#typo-check -- $(TYPO_LANGS)
79+
7480 infra-plan: ## Show what infra-apply would change on Cloudflare
7581 nix run .#infra -- plan
7682
@@ -21,7 +21,7 @@ PROD_PORT ?= 8788
21 # deploy-% targets aren't listed here: giving an instantiated pattern target21 # deploy-% targets aren't listed here: giving an instantiated pattern target
22 # (e.g. deploy-en) to .PHONY creates an explicit no-recipe rule for it, which22 # (e.g. deploy-en) to .PHONY creates an explicit no-recipe rule for it, which
23 # then blocks the deploy-% pattern rule below from ever supplying a recipe.23 # then blocks the deploy-% pattern rule below from ever supplying a recipe.
24-.PHONY: help dev build check-tracked serve prod-server infra-plan infra-apply deploy clean24+.PHONY: help dev build check-tracked serve prod-server typo infra-plan infra-apply deploy clean
25 25
26 help:26 help:
27 @grep -E '^[a-zA-Z_.-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'27 @grep -E '^[a-zA-Z_.-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'
@@ -71,6 +71,12 @@ serve: ## Run the Hugo dev server with live reload
71 prod-server: build ## Serve the built site exactly as deployed, e.g. `make prod-server SITE_LANG=fr PROD_PORT=8080`71 prod-server: build ## Serve the built site exactly as deployed, e.g. `make prod-server SITE_LANG=fr PROD_PORT=8080`
72 nix run .#prod-server -- $(SITE_LANG) $(PROD_PORT)72 nix run .#prod-server -- $(SITE_LANG) $(PROD_PORT)
73 73
74+# Read-only: prints what JoliTypo would change in ./result, e.g. `make typo
75+# TYPO_LANGS=fr`. Exits 1 if there is anything, so it can gate CI later.
76+TYPO_LANGS ?= $(LANGS)
77+typo: build ## Show typography fixes JoliTypo would make in the built pages
78+ nix run .#typo-check -- $(TYPO_LANGS)
79+
74 infra-plan: ## Show what infra-apply would change on Cloudflare80 infra-plan: ## Show what infra-apply would change on Cloudflare
75 nix run .#infra -- plan81 nix run .#infra -- plan
76 82
modified flake.nix +70 -0
@@ -25,6 +25,11 @@
2525 # once Newsreader/JetBrains Mono (self-hosted, already covering every
2626 # other language here) come up short on a codepoint.
2727 ipaexfont = pin.version "ipaexfont" "004.01";
28+ # 2.0.0 is the first release whose phar actually contains the library.
29+ jolitypo = pkgs.fetchurl {
30+ url = "https://github.com/jolicode/JoliTypo/releases/download/v2.0.0/jolitypo.phar";
31+ hash = "sha256-d6SZko7GvHwc1cDpRsPHLb+owM0E5za5/fBHhW+dIYE=";
32+ };
2833 in
2934 {
3035 packages.${system}.default = pkgs.stdenvNoCC.mkDerivation {
@@ -267,6 +272,71 @@ FONTCONF
267272 '';
268273 }}/bin/mykiwi-blog-prod-server";
269274 };
275+
276+ # `nix run .#typo-check -- [lang...]` - runs JoliTypo over the built
277+ # `./result/<lang>` pages (every language in languages.txt by default)
278+ # and prints what it would change, exiting 1 if that is anything. It
279+ # fixes nothing: the answer belongs in the markdown or the templates,
280+ # not in a post-processing step.
281+ #
282+ # The CLI rewrites files in place and re-serialises the whole page
283+ # (adds the doctype, quotes attributes, expands `<rect />`), so a plain
284+ # diff against `result/` is all noise. Instead each page is copied
285+ # twice and put through two passes: UnicodeNormalization alone, a no-op
286+ # on composed text that only gives the baseline the same
287+ # serialisation, and the real rules. The diff between those two is the
288+ # typography.
289+ #
290+ # It has to be given single files, not a directory: given one, the CLI
291+ # also rewrites index.xml, feed.js and feed.xsl, and then crashes.
292+ #
293+ # Hyphen is left out on purpose: soft hyphens are spelled out letter by
294+ # letter by screen readers, and it has no dictionary for es or ja.
295+ #
296+ # ja skips four more. JoliTypo has no Japanese quotation style, so
297+ # SmartQuotes would push "…" towards “…” where 「…」 is the norm, and
298+ # Dash, SpaceBeforePunctuation and NoSpaceBeforeComma bind or strip
299+ # spaces by Latin-script rules, in a script that puts no spaces
300+ # between words at all.
301+ typo-check = {
302+ type = "app";
303+ program = "${pkgs.writeShellApplication {
304+ name = "mykiwi-blog-typo-check";
305+ runtimeInputs = [ pkgs.php pkgs.git pkgs.coreutils pkgs.diffutils pkgs.findutils pkgs.gnused ];
306+ text = ''
307+ root="$(git rev-parse --show-toplevel)"
308+ # shellcheck disable=SC2046 # one language per word, on purpose
309+ [ $# -gt 0 ] || set -- $(cat "$root/languages.txt")
310+ work="$(mktemp -d)"
311+ trap 'rm -rf "$work"' EXIT
312+
313+ for lang in "$@"; do
314+ src="$root/result/$lang"
315+ if [ ! -d "$src" ]; then
316+ echo "$src does not exist - run 'make build' first." >&2
317+ exit 1
318+ fi
319+ # The language code is enough of a locale: JoliTypo only reads
320+ # its language part, and fr/fr_FR, en/en_GB... fix alike.
321+ rules=(UnicodeNormalization Ellipsis Dimension Unit CurlyQuote Trademark)
322+ [ "$lang" = ja ] || rules+=(Dash SmartQuotes SpaceBeforePunctuation NoSpaceBeforeComma)
323+ while IFS= read -r -d "" page; do
324+ install -Dm644 "$src/$page" "$work/base/$lang/$page"
325+ install -Dm644 "$src/$page" "$work/fixed/$lang/$page"
326+ php ${jolitypo} --silent "$lang" "$work/base/$lang/$page" --rule=UnicodeNormalization
327+ php ${jolitypo} --silent "$lang" "$work/fixed/$lang/$page" "''${rules[@]/#/--rule=}"
328+ done < <(cd "$src" && find . -name '*.html' -print0)
329+ done
330+
331+ # One element per line and visible spaces, or a minified page is
332+ # a single unreadable line. The serialiser already writes U+00A0
333+ # as &nbsp;, but leaves U+202F raw.
334+ find "$work" -name '*.html' -exec sed -i -e 's/></>\n</g' -e 's/\xe2\x80\xaf/[nnbsp]/g' {} +
335+ cd "$work"
336+ diff -ru base fixed
337+ '';
338+ }}/bin/mykiwi-blog-typo-check";
339+ };
270340 };
271341 };
272342 }
@@ -25,6 +25,11 @@
25 # once Newsreader/JetBrains Mono (self-hosted, already covering every25 # once Newsreader/JetBrains Mono (self-hosted, already covering every
26 # other language here) come up short on a codepoint.26 # other language here) come up short on a codepoint.
27 ipaexfont = pin.version "ipaexfont" "004.01";27 ipaexfont = pin.version "ipaexfont" "004.01";
28+ # 2.0.0 is the first release whose phar actually contains the library.
29+ jolitypo = pkgs.fetchurl {
30+ url = "https://github.com/jolicode/JoliTypo/releases/download/v2.0.0/jolitypo.phar";
31+ hash = "sha256-d6SZko7GvHwc1cDpRsPHLb+owM0E5za5/fBHhW+dIYE=";
32+ };
28 in33 in
29 {34 {
30 packages.${system}.default = pkgs.stdenvNoCC.mkDerivation {35 packages.${system}.default = pkgs.stdenvNoCC.mkDerivation {
@@ -267,6 +272,71 @@ FONTCONF
267 '';272 '';
268 }}/bin/mykiwi-blog-prod-server";273 }}/bin/mykiwi-blog-prod-server";
269 };274 };
275+
276+ # `nix run .#typo-check -- [lang...]` - runs JoliTypo over the built
277+ # `./result/<lang>` pages (every language in languages.txt by default)
278+ # and prints what it would change, exiting 1 if that is anything. It
279+ # fixes nothing: the answer belongs in the markdown or the templates,
280+ # not in a post-processing step.
281+ #
282+ # The CLI rewrites files in place and re-serialises the whole page
283+ # (adds the doctype, quotes attributes, expands `<rect />`), so a plain
284+ # diff against `result/` is all noise. Instead each page is copied
285+ # twice and put through two passes: UnicodeNormalization alone, a no-op
286+ # on composed text that only gives the baseline the same
287+ # serialisation, and the real rules. The diff between those two is the
288+ # typography.
289+ #
290+ # It has to be given single files, not a directory: given one, the CLI
291+ # also rewrites index.xml, feed.js and feed.xsl, and then crashes.
292+ #
293+ # Hyphen is left out on purpose: soft hyphens are spelled out letter by
294+ # letter by screen readers, and it has no dictionary for es or ja.
295+ #
296+ # ja skips four more. JoliTypo has no Japanese quotation style, so
297+ # SmartQuotes would push "…" towards “…” where 「…」 is the norm, and
298+ # Dash, SpaceBeforePunctuation and NoSpaceBeforeComma bind or strip
299+ # spaces by Latin-script rules, in a script that puts no spaces
300+ # between words at all.
301+ typo-check = {
302+ type = "app";
303+ program = "${pkgs.writeShellApplication {
304+ name = "mykiwi-blog-typo-check";
305+ runtimeInputs = [ pkgs.php pkgs.git pkgs.coreutils pkgs.diffutils pkgs.findutils pkgs.gnused ];
306+ text = ''
307+ root="$(git rev-parse --show-toplevel)"
308+ # shellcheck disable=SC2046 # one language per word, on purpose
309+ [ $# -gt 0 ] || set -- $(cat "$root/languages.txt")
310+ work="$(mktemp -d)"
311+ trap 'rm -rf "$work"' EXIT
312+
313+ for lang in "$@"; do
314+ src="$root/result/$lang"
315+ if [ ! -d "$src" ]; then
316+ echo "$src does not exist - run 'make build' first." >&2
317+ exit 1
318+ fi
319+ # The language code is enough of a locale: JoliTypo only reads
320+ # its language part, and fr/fr_FR, en/en_GB... fix alike.
321+ rules=(UnicodeNormalization Ellipsis Dimension Unit CurlyQuote Trademark)
322+ [ "$lang" = ja ] || rules+=(Dash SmartQuotes SpaceBeforePunctuation NoSpaceBeforeComma)
323+ while IFS= read -r -d "" page; do
324+ install -Dm644 "$src/$page" "$work/base/$lang/$page"
325+ install -Dm644 "$src/$page" "$work/fixed/$lang/$page"
326+ php ${jolitypo} --silent "$lang" "$work/base/$lang/$page" --rule=UnicodeNormalization
327+ php ${jolitypo} --silent "$lang" "$work/fixed/$lang/$page" "''${rules[@]/#/--rule=}"
328+ done < <(cd "$src" && find . -name '*.html' -print0)
329+ done
330+
331+ # One element per line and visible spaces, or a minified page is
332+ # a single unreadable line. The serialiser already writes U+00A0
333+ # as &nbsp;, but leaves U+202F raw.
334+ find "$work" -name '*.html' -exec sed -i -e 's/></>\n</g' -e 's/\xe2\x80\xaf/[nnbsp]/g' {} +
335+ cd "$work"
336+ diff -ru base fixed
337+ '';
338+ }}/bin/mykiwi-blog-typo-check";
339+ };
270 };340 };
271 };341 };
272 }342 }