Add French/Portuguese/Japanese support, block indexing until launch
Multihost multilingual setup: each language in blog/hugo.toml gets its own baseURL (mykiwi.blog, fr./pt./ja.mykiwi.blog) so Hugo builds each as a fully separate site with no /fr/ path prefix, meant to be deployed as the docroot of its own subdomain. UI chrome (nav, footer, reading time, post counts) is pulled from blog/i18n/*.toml instead of hardcoded English, and the footer grows a language switcher listing only the languages a given page actually has a translation for, since not every post will exist in all four. hreflang alternate tags and a per-language RSS link round out the SEO side. Also adds a single params.noindex toggle (hugo.toml) that adds <meta name="robots" content="noindex, nofollow"> to every page and flips robots.txt to Disallow: / - on by default so the site can be deployed and iterated on without being indexed before it's ready.
f8c7bc5 parent: bf3cf90 modified
blog/assets/css/style.scss +9 -0 | @@ -981,6 +981,15 @@ blockquote.alert { | ||
| 981 | 981 | } |
| 982 | 982 | } |
| 983 | 983 | |
| 984 | + .lang-switch { | |
| 985 | + display: inline-flex; | |
| 986 | + gap: 0.5rem; | |
| 987 | + } | |
| 988 | + | |
| 989 | + .lang-switch__current { | |
| 990 | + color: var(--ink-muted); | |
| 991 | + } | |
| 992 | + | |
| 984 | 993 | // Own row, so the path gets the whole measure rather than whatever is left |
| 985 | 994 | // over beside the RSS link. |
| 986 | 995 | .store-path-row { |
| @@ -981,6 +981,15 @@ blockquote.alert { | |||
| 981 | } | 981 | } |
| 982 | } | 982 | } |
| 983 | 983 | ||
| 984 | + .lang-switch { | ||
| 985 | + display: inline-flex; | ||
| 986 | + gap: 0.5rem; | ||
| 987 | + } | ||
| 988 | + | ||
| 989 | + .lang-switch__current { | ||
| 990 | + color: var(--ink-muted); | ||
| 991 | + } | ||
| 992 | + | ||
| 984 | // Own row, so the path gets the whole measure rather than whatever is left | 993 | // Own row, so the path gets the whole measure rather than whatever is left |
| 985 | // over beside the RSS link. | 994 | // over beside the RSS link. |
| 986 | .store-path-row { | 995 | .store-path-row { |
modified
blog/hugo.toml +41 -2 | @@ -1,7 +1,41 @@ | ||
| 1 | -baseURL = 'https://example.org/' | |
| 2 | -locale = 'en-us' | |
| 3 | 1 | title = 'mykiwi.blog' |
| 4 | 2 | disableKinds = ['taxonomy', 'term'] |
| 3 | +enableRobotsTXT = true | |
| 4 | + | |
| 5 | +# Multihost: every language below sets its own baseURL, which is what tells | |
| 6 | +# Hugo to build each one as a fully separate site (own absolute URLs, no | |
| 7 | +# /fr/ path prefix) instead of one shared build with a language-prefixed | |
| 8 | +# path. `hugo` then writes public/en/, public/fr/, public/pt/, public/ja/ - | |
| 9 | +# each folder is a complete site meant to be deployed as the docroot of its | |
| 10 | +# own (sub)domain (public/en/ -> mykiwi.blog, public/fr/ -> fr.mykiwi.blog, | |
| 11 | +# etc). A content file with no language suffix (e.g. posts/hello-world.md) | |
| 12 | +# is the English version; translations sit alongside it as | |
| 13 | +# posts/hello-world.fr.md, .pt.md, .ja.md. | |
| 14 | +defaultContentLanguage = "en" | |
| 15 | + | |
| 16 | +[languages.en] | |
| 17 | +baseURL = "https://mykiwi.blog/" | |
| 18 | +label = "English" | |
| 19 | +locale = "en-us" | |
| 20 | +weight = 1 | |
| 21 | + | |
| 22 | +[languages.fr] | |
| 23 | +baseURL = "https://fr.mykiwi.blog/" | |
| 24 | +label = "Français" | |
| 25 | +locale = "fr-fr" | |
| 26 | +weight = 2 | |
| 27 | + | |
| 28 | +[languages.pt] | |
| 29 | +baseURL = "https://pt.mykiwi.blog/" | |
| 30 | +label = "Português" | |
| 31 | +locale = "pt-br" | |
| 32 | +weight = 3 | |
| 33 | + | |
| 34 | +[languages.ja] | |
| 35 | +baseURL = "https://ja.mykiwi.blog/" | |
| 36 | +label = "日本語" | |
| 37 | +locale = "ja-jp" | |
| 38 | +weight = 4 | |
| 5 | 39 | |
| 6 | 40 | [params] |
| 7 | 41 | # Optional: overrides the <meta name="description"> tag with different text |
| @@ -9,6 +43,11 @@ disableKinds = ['taxonomy', 'term'] | ||
| 9 | 43 | # derived automatically from params.profile.bio, stripped down to plain text. |
| 10 | 44 | # description = "..." |
| 11 | 45 | |
| 46 | +# Blocks search engines from indexing the site (adds <meta name="robots" | |
| 47 | +# content="noindex, nofollow"> to every page and a Disallow: / robots.txt) - | |
| 48 | +# flip to false (or delete the line) once the site is ready to be found. | |
| 49 | +noindex = true | |
| 50 | + | |
| 12 | 51 | [params.profile] |
| 13 | 52 | # Drop a square photo at blog/assets/images/avatar.jpg (or change this path) |
| 14 | 53 | # to have it show up; the colophon quietly skips itself if the file is missing. |
| @@ -1,7 +1,41 @@ | |||
| 1 | -baseURL = 'https://example.org/' | ||
| 2 | -locale = 'en-us' | ||
| 3 | title = 'mykiwi.blog' | 1 | title = 'mykiwi.blog' |
| 4 | disableKinds = ['taxonomy', 'term'] | 2 | disableKinds = ['taxonomy', 'term'] |
| 3 | +enableRobotsTXT = true | ||
| 4 | + | ||
| 5 | +# Multihost: every language below sets its own baseURL, which is what tells | ||
| 6 | +# Hugo to build each one as a fully separate site (own absolute URLs, no | ||
| 7 | +# /fr/ path prefix) instead of one shared build with a language-prefixed | ||
| 8 | +# path. `hugo` then writes public/en/, public/fr/, public/pt/, public/ja/ - | ||
| 9 | +# each folder is a complete site meant to be deployed as the docroot of its | ||
| 10 | +# own (sub)domain (public/en/ -> mykiwi.blog, public/fr/ -> fr.mykiwi.blog, | ||
| 11 | +# etc). A content file with no language suffix (e.g. posts/hello-world.md) | ||
| 12 | +# is the English version; translations sit alongside it as | ||
| 13 | +# posts/hello-world.fr.md, .pt.md, .ja.md. | ||
| 14 | +defaultContentLanguage = "en" | ||
| 15 | + | ||
| 16 | +[languages.en] | ||
| 17 | +baseURL = "https://mykiwi.blog/" | ||
| 18 | +label = "English" | ||
| 19 | +locale = "en-us" | ||
| 20 | +weight = 1 | ||
| 21 | + | ||
| 22 | +[languages.fr] | ||
| 23 | +baseURL = "https://fr.mykiwi.blog/" | ||
| 24 | +label = "Français" | ||
| 25 | +locale = "fr-fr" | ||
| 26 | +weight = 2 | ||
| 27 | + | ||
| 28 | +[languages.pt] | ||
| 29 | +baseURL = "https://pt.mykiwi.blog/" | ||
| 30 | +label = "Português" | ||
| 31 | +locale = "pt-br" | ||
| 32 | +weight = 3 | ||
| 33 | + | ||
| 34 | +[languages.ja] | ||
| 35 | +baseURL = "https://ja.mykiwi.blog/" | ||
| 36 | +label = "日本語" | ||
| 37 | +locale = "ja-jp" | ||
| 38 | +weight = 4 | ||
| 5 | 39 | ||
| 6 | [params] | 40 | [params] |
| 7 | # Optional: overrides the <meta name="description"> tag with different text | 41 | # Optional: overrides the <meta name="description"> tag with different text |
| @@ -9,6 +43,11 @@ disableKinds = ['taxonomy', 'term'] | |||
| 9 | # derived automatically from params.profile.bio, stripped down to plain text. | 43 | # derived automatically from params.profile.bio, stripped down to plain text. |
| 10 | # description = "..." | 44 | # description = "..." |
| 11 | 45 | ||
| 46 | +# Blocks search engines from indexing the site (adds <meta name="robots" | ||
| 47 | +# content="noindex, nofollow"> to every page and a Disallow: / robots.txt) - | ||
| 48 | +# flip to false (or delete the line) once the site is ready to be found. | ||
| 49 | +noindex = true | ||
| 50 | + | ||
| 12 | [params.profile] | 51 | [params.profile] |
| 13 | # Drop a square photo at blog/assets/images/avatar.jpg (or change this path) | 52 | # Drop a square photo at blog/assets/images/avatar.jpg (or change this path) |
| 14 | # to have it show up; the colophon quietly skips itself if the file is missing. | 53 | # to have it show up; the colophon quietly skips itself if the file is missing. |
added
blog/i18n/en.toml +27 -0 | new file mode 100644 | ||
| @@ -0,0 +1,27 @@ | ||
| 1 | +[skipToContent] | |
| 2 | +other = "Skip to content" | |
| 3 | + | |
| 4 | +[about] | |
| 5 | +other = "About" | |
| 6 | + | |
| 7 | +[minRead] | |
| 8 | +other = "{{ .Count }} min read" | |
| 9 | + | |
| 10 | +[postsCount] | |
| 11 | +one = "{{ .Count }} post" | |
| 12 | +other = "{{ .Count }} posts" | |
| 13 | + | |
| 14 | +[licenseText] | |
| 15 | +other = "Text is" | |
| 16 | + | |
| 17 | +[subscribeRSS] | |
| 18 | +other = "Subscribe via RSS" | |
| 19 | + | |
| 20 | +[rssLabel] | |
| 21 | +other = "RSS" | |
| 22 | + | |
| 23 | +[latest] | |
| 24 | +other = "Latest" | |
| 25 | + | |
| 26 | +[readMore] | |
| 27 | +other = "Read →" | |
| new file mode 100644 | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | +[skipToContent] | ||
| 2 | +other = "Skip to content" | ||
| 3 | + | ||
| 4 | +[about] | ||
| 5 | +other = "About" | ||
| 6 | + | ||
| 7 | +[minRead] | ||
| 8 | +other = "{{ .Count }} min read" | ||
| 9 | + | ||
| 10 | +[postsCount] | ||
| 11 | +one = "{{ .Count }} post" | ||
| 12 | +other = "{{ .Count }} posts" | ||
| 13 | + | ||
| 14 | +[licenseText] | ||
| 15 | +other = "Text is" | ||
| 16 | + | ||
| 17 | +[subscribeRSS] | ||
| 18 | +other = "Subscribe via RSS" | ||
| 19 | + | ||
| 20 | +[rssLabel] | ||
| 21 | +other = "RSS" | ||
| 22 | + | ||
| 23 | +[latest] | ||
| 24 | +other = "Latest" | ||
| 25 | + | ||
| 26 | +[readMore] | ||
| 27 | +other = "Read →" | ||
added
blog/i18n/fr.toml +27 -0 | new file mode 100644 | ||
| @@ -0,0 +1,27 @@ | ||
| 1 | +[skipToContent] | |
| 2 | +other = "Passer au contenu" | |
| 3 | + | |
| 4 | +[about] | |
| 5 | +other = "À propos" | |
| 6 | + | |
| 7 | +[minRead] | |
| 8 | +other = "{{ .Count }} min de lecture" | |
| 9 | + | |
| 10 | +[postsCount] | |
| 11 | +one = "{{ .Count }} article" | |
| 12 | +other = "{{ .Count }} articles" | |
| 13 | + | |
| 14 | +[licenseText] | |
| 15 | +other = "Texte sous licence" | |
| 16 | + | |
| 17 | +[subscribeRSS] | |
| 18 | +other = "S'abonner via RSS" | |
| 19 | + | |
| 20 | +[rssLabel] | |
| 21 | +other = "RSS" | |
| 22 | + | |
| 23 | +[latest] | |
| 24 | +other = "Dernier" | |
| 25 | + | |
| 26 | +[readMore] | |
| 27 | +other = "Lire →" | |
| new file mode 100644 | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | +[skipToContent] | ||
| 2 | +other = "Passer au contenu" | ||
| 3 | + | ||
| 4 | +[about] | ||
| 5 | +other = "À propos" | ||
| 6 | + | ||
| 7 | +[minRead] | ||
| 8 | +other = "{{ .Count }} min de lecture" | ||
| 9 | + | ||
| 10 | +[postsCount] | ||
| 11 | +one = "{{ .Count }} article" | ||
| 12 | +other = "{{ .Count }} articles" | ||
| 13 | + | ||
| 14 | +[licenseText] | ||
| 15 | +other = "Texte sous licence" | ||
| 16 | + | ||
| 17 | +[subscribeRSS] | ||
| 18 | +other = "S'abonner via RSS" | ||
| 19 | + | ||
| 20 | +[rssLabel] | ||
| 21 | +other = "RSS" | ||
| 22 | + | ||
| 23 | +[latest] | ||
| 24 | +other = "Dernier" | ||
| 25 | + | ||
| 26 | +[readMore] | ||
| 27 | +other = "Lire →" | ||
added
blog/i18n/ja.toml +26 -0 | new file mode 100644 | ||
| @@ -0,0 +1,26 @@ | ||
| 1 | +[skipToContent] | |
| 2 | +other = "コンテンツへスキップ" | |
| 3 | + | |
| 4 | +[about] | |
| 5 | +other = "このサイトについて" | |
| 6 | + | |
| 7 | +[minRead] | |
| 8 | +other = "{{ .Count }}分で読了" | |
| 9 | + | |
| 10 | +[postsCount] | |
| 11 | +other = "{{ .Count }}件の投稿" | |
| 12 | + | |
| 13 | +[licenseText] | |
| 14 | +other = "テキストは" | |
| 15 | + | |
| 16 | +[subscribeRSS] | |
| 17 | +other = "RSSで購読" | |
| 18 | + | |
| 19 | +[rssLabel] | |
| 20 | +other = "RSS" | |
| 21 | + | |
| 22 | +[latest] | |
| 23 | +other = "最新" | |
| 24 | + | |
| 25 | +[readMore] | |
| 26 | +other = "続きを読む →" | |
| new file mode 100644 | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | +[skipToContent] | ||
| 2 | +other = "コンテンツへスキップ" | ||
| 3 | + | ||
| 4 | +[about] | ||
| 5 | +other = "このサイトについて" | ||
| 6 | + | ||
| 7 | +[minRead] | ||
| 8 | +other = "{{ .Count }}分で読了" | ||
| 9 | + | ||
| 10 | +[postsCount] | ||
| 11 | +other = "{{ .Count }}件の投稿" | ||
| 12 | + | ||
| 13 | +[licenseText] | ||
| 14 | +other = "テキストは" | ||
| 15 | + | ||
| 16 | +[subscribeRSS] | ||
| 17 | +other = "RSSで購読" | ||
| 18 | + | ||
| 19 | +[rssLabel] | ||
| 20 | +other = "RSS" | ||
| 21 | + | ||
| 22 | +[latest] | ||
| 23 | +other = "最新" | ||
| 24 | + | ||
| 25 | +[readMore] | ||
| 26 | +other = "続きを読む →" | ||
added
blog/i18n/pt.toml +27 -0 | new file mode 100644 | ||
| @@ -0,0 +1,27 @@ | ||
| 1 | +[skipToContent] | |
| 2 | +other = "Pular para o conteúdo" | |
| 3 | + | |
| 4 | +[about] | |
| 5 | +other = "Sobre" | |
| 6 | + | |
| 7 | +[minRead] | |
| 8 | +other = "{{ .Count }} min de leitura" | |
| 9 | + | |
| 10 | +[postsCount] | |
| 11 | +one = "{{ .Count }} post" | |
| 12 | +other = "{{ .Count }} posts" | |
| 13 | + | |
| 14 | +[licenseText] | |
| 15 | +other = "Texto sob licença" | |
| 16 | + | |
| 17 | +[subscribeRSS] | |
| 18 | +other = "Assine via RSS" | |
| 19 | + | |
| 20 | +[rssLabel] | |
| 21 | +other = "RSS" | |
| 22 | + | |
| 23 | +[latest] | |
| 24 | +other = "Mais recente" | |
| 25 | + | |
| 26 | +[readMore] | |
| 27 | +other = "Ler →" | |
| new file mode 100644 | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | +[skipToContent] | ||
| 2 | +other = "Pular para o conteúdo" | ||
| 3 | + | ||
| 4 | +[about] | ||
| 5 | +other = "Sobre" | ||
| 6 | + | ||
| 7 | +[minRead] | ||
| 8 | +other = "{{ .Count }} min de leitura" | ||
| 9 | + | ||
| 10 | +[postsCount] | ||
| 11 | +one = "{{ .Count }} post" | ||
| 12 | +other = "{{ .Count }} posts" | ||
| 13 | + | ||
| 14 | +[licenseText] | ||
| 15 | +other = "Texto sob licença" | ||
| 16 | + | ||
| 17 | +[subscribeRSS] | ||
| 18 | +other = "Assine via RSS" | ||
| 19 | + | ||
| 20 | +[rssLabel] | ||
| 21 | +other = "RSS" | ||
| 22 | + | ||
| 23 | +[latest] | ||
| 24 | +other = "Mais recente" | ||
| 25 | + | ||
| 26 | +[readMore] | ||
| 27 | +other = "Ler →" | ||
modified
blog/layouts/_default/baseof.html +16 -2 | @@ -6,6 +6,9 @@ | ||
| 6 | 6 | <meta name="color-scheme" content="light dark"> |
| 7 | 7 | <meta name="theme-color" content="#f5f2ec" media="(prefers-color-scheme: light)"> |
| 8 | 8 | <meta name="theme-color" content="#15171a" media="(prefers-color-scheme: dark)"> |
| 9 | + {{ if site.Params.noindex }} | |
| 10 | + <meta name="robots" content="noindex, nofollow"> | |
| 11 | + {{ end }} | |
| 9 | 12 | |
| 10 | 13 | <title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ .Title }} · {{ site.Title }}{{ end }}</title> |
| 11 | 14 | {{- /* |
| @@ -32,7 +35,18 @@ | ||
| 32 | 35 | {{ end }} |
| 33 | 36 | {{ end }} |
| 34 | 37 | |
| 35 | - <link rel="alternate" type="application/rss+xml" title="{{ site.Title }}" href="{{ "index.xml" | absURL }}"> | |
| 38 | + <link rel="alternate" type="application/rss+xml" title="{{ site.Title }}" href="{{ "index.xml" | absLangURL }}"> | |
| 39 | + {{- /* One hreflang tag per translated version of this page, so search | |
| 40 | + engines serve readers the right language instead of always the one | |
| 41 | + that happens to rank. x-default points at the lowest-weight (English) | |
| 42 | + translation, since that's the site's default language. */}} | |
| 43 | + {{- $translations := sort .AllTranslations "Language.Weight" }} | |
| 44 | + {{- range $translations }} | |
| 45 | + <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}"> | |
| 46 | + {{- end }} | |
| 47 | + {{- with index $translations 0 }} | |
| 48 | + <link rel="alternate" hreflang="x-default" href="{{ .Permalink }}"> | |
| 49 | + {{- end }} | |
| 36 | 50 | {{- /* |
| 37 | 51 | Declarative, not htmx.config.preload = {...} in a script: a <meta> tag |
| 38 | 52 | is parsed with the rest of the document, before any script (deferred or |
| @@ -105,7 +119,7 @@ | ||
| 105 | 119 | that diff in a native View Transition for a soft cross-fade. |
| 106 | 120 | */}} |
| 107 | 121 | <body hx-boost:inherited="swap:{{ $swapSpec.swap }} transition:{{ $swapSpec.transition }}"> |
| 108 | - <a class="skip-link" href="#main">Skip to content</a> | |
| 122 | + <a class="skip-link" href="#main">{{ i18n "skipToContent" }}</a> | |
| 109 | 123 | <div class="wrap"> |
| 110 | 124 | {{ partial "masthead.html" . }} |
| 111 | 125 | <main id="main">{{ block "main" . }}{{ end }}</main> |
| @@ -6,6 +6,9 @@ | |||
| 6 | <meta name="color-scheme" content="light dark"> | 6 | <meta name="color-scheme" content="light dark"> |
| 7 | <meta name="theme-color" content="#f5f2ec" media="(prefers-color-scheme: light)"> | 7 | <meta name="theme-color" content="#f5f2ec" media="(prefers-color-scheme: light)"> |
| 8 | <meta name="theme-color" content="#15171a" media="(prefers-color-scheme: dark)"> | 8 | <meta name="theme-color" content="#15171a" media="(prefers-color-scheme: dark)"> |
| 9 | + {{ if site.Params.noindex }} | ||
| 10 | + <meta name="robots" content="noindex, nofollow"> | ||
| 11 | + {{ end }} | ||
| 9 | 12 | ||
| 10 | <title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ .Title }} · {{ site.Title }}{{ end }}</title> | 13 | <title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ .Title }} · {{ site.Title }}{{ end }}</title> |
| 11 | {{- /* | 14 | {{- /* |
| @@ -32,7 +35,18 @@ | |||
| 32 | {{ end }} | 35 | {{ end }} |
| 33 | {{ end }} | 36 | {{ end }} |
| 34 | 37 | ||
| 35 | - <link rel="alternate" type="application/rss+xml" title="{{ site.Title }}" href="{{ "index.xml" | absURL }}"> | 38 | + <link rel="alternate" type="application/rss+xml" title="{{ site.Title }}" href="{{ "index.xml" | absLangURL }}"> |
| 39 | + {{- /* One hreflang tag per translated version of this page, so search | ||
| 40 | + engines serve readers the right language instead of always the one | ||
| 41 | + that happens to rank. x-default points at the lowest-weight (English) | ||
| 42 | + translation, since that's the site's default language. */}} | ||
| 43 | + {{- $translations := sort .AllTranslations "Language.Weight" }} | ||
| 44 | + {{- range $translations }} | ||
| 45 | + <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}"> | ||
| 46 | + {{- end }} | ||
| 47 | + {{- with index $translations 0 }} | ||
| 48 | + <link rel="alternate" hreflang="x-default" href="{{ .Permalink }}"> | ||
| 49 | + {{- end }} | ||
| 36 | {{- /* | 50 | {{- /* |
| 37 | Declarative, not htmx.config.preload = {...} in a script: a <meta> tag | 51 | Declarative, not htmx.config.preload = {...} in a script: a <meta> tag |
| 38 | is parsed with the rest of the document, before any script (deferred or | 52 | is parsed with the rest of the document, before any script (deferred or |
| @@ -105,7 +119,7 @@ | |||
| 105 | that diff in a native View Transition for a soft cross-fade. | 119 | that diff in a native View Transition for a soft cross-fade. |
| 106 | */}} | 120 | */}} |
| 107 | <body hx-boost:inherited="swap:{{ $swapSpec.swap }} transition:{{ $swapSpec.transition }}"> | 121 | <body hx-boost:inherited="swap:{{ $swapSpec.swap }} transition:{{ $swapSpec.transition }}"> |
| 108 | - <a class="skip-link" href="#main">Skip to content</a> | 122 | + <a class="skip-link" href="#main">{{ i18n "skipToContent" }}</a> |
| 109 | <div class="wrap"> | 123 | <div class="wrap"> |
| 110 | {{ partial "masthead.html" . }} | 124 | {{ partial "masthead.html" . }} |
| 111 | <main id="main">{{ block "main" . }}{{ end }}</main> | 125 | <main id="main">{{ block "main" . }}{{ end }}</main> |
modified
blog/layouts/_default/list.html +1 -1 | @@ -10,7 +10,7 @@ | ||
| 10 | 10 | <section class="year rise" style="--i: {{ add $i 1 }}"> |
| 11 | 11 | <h2 class="year__label"> |
| 12 | 12 | {{ $year.Key }} |
| 13 | - <span class="count">{{ len $year.Pages }} posts</span> | |
| 13 | + <span class="count">{{ i18n "postsCount" (dict "Count" (len $year.Pages)) }}</span> | |
| 14 | 14 | </h2> |
| 15 | 15 | <ul class="year__list"> |
| 16 | 16 | {{ range $year.Pages }} |
| @@ -10,7 +10,7 @@ | |||
| 10 | <section class="year rise" style="--i: {{ add $i 1 }}"> | 10 | <section class="year rise" style="--i: {{ add $i 1 }}"> |
| 11 | <h2 class="year__label"> | 11 | <h2 class="year__label"> |
| 12 | {{ $year.Key }} | 12 | {{ $year.Key }} |
| 13 | - <span class="count">{{ len $year.Pages }} posts</span> | 13 | + <span class="count">{{ i18n "postsCount" (dict "Count" (len $year.Pages)) }}</span> |
| 14 | </h2> | 14 | </h2> |
| 15 | <ul class="year__list"> | 15 | <ul class="year__list"> |
| 16 | {{ range $year.Pages }} | 16 | {{ range $year.Pages }} |
modified
blog/layouts/_default/single.html +1 -1 | @@ -6,7 +6,7 @@ | ||
| 6 | 6 | <time datetime="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{ .Format "2006-01-02" }}</time> |
| 7 | 7 | <span class="sep" aria-hidden="true">·</span> |
| 8 | 8 | {{ end }} |
| 9 | - <span>{{ .ReadingTime }} min read</span> | |
| 9 | + <span>{{ i18n "minRead" (dict "Count" .ReadingTime) }}</span> | |
| 10 | 10 | </p> |
| 11 | 11 | <h1 class="post-title">{{ .Title }}</h1> |
| 12 | 12 | <hr class="post-rule"> |
| @@ -6,7 +6,7 @@ | |||
| 6 | <time datetime="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{ .Format "2006-01-02" }}</time> | 6 | <time datetime="{{ .Format "2006-01-02T15:04:05Z07:00" }}">{{ .Format "2006-01-02" }}</time> |
| 7 | <span class="sep" aria-hidden="true">·</span> | 7 | <span class="sep" aria-hidden="true">·</span> |
| 8 | {{ end }} | 8 | {{ end }} |
| 9 | - <span>{{ .ReadingTime }} min read</span> | 9 | + <span>{{ i18n "minRead" (dict "Count" .ReadingTime) }}</span> |
| 10 | </p> | 10 | </p> |
| 11 | <h1 class="post-title">{{ .Title }}</h1> | 11 | <h1 class="post-title">{{ .Title }}</h1> |
| 12 | <hr class="post-rule"> | 12 | <hr class="post-rule"> |
modified
blog/layouts/index.html +3 -3 | @@ -10,10 +10,10 @@ | ||
| 10 | 10 | <div class="index-body"> |
| 11 | 11 | {{ with $lead }} |
| 12 | 12 | <div class="index-lead rise" style="--i: 1"> |
| 13 | - <span class="label">Latest · {{ .Date.Format "January 2, 2006" }}</span> | |
| 13 | + <span class="label">{{ i18n "latest" }} · {{ .Date.Format "January 2, 2006" }}</span> | |
| 14 | 14 | <a class="title" href="{{ .RelPermalink }}">{{ .Title }}</a> |
| 15 | 15 | <span class="excerpt">{{ .Summary }}</span> |
| 16 | - <span class="more" aria-hidden="true">Read →</span> | |
| 16 | + <span class="more" aria-hidden="true">{{ i18n "readMore" }}</span> | |
| 17 | 17 | </div> |
| 18 | 18 | {{ end }} |
| 19 | 19 | |
| @@ -21,7 +21,7 @@ | ||
| 21 | 21 | <section class="year rise" style="--i: {{ add $i 2 }}"> |
| 22 | 22 | <h2 class="year__label"> |
| 23 | 23 | {{ $year.Key }} |
| 24 | - <span class="count">{{ len $year.Pages }} posts</span> | |
| 24 | + <span class="count">{{ i18n "postsCount" (dict "Count" (len $year.Pages)) }}</span> | |
| 25 | 25 | </h2> |
| 26 | 26 | <ul class="year__list"> |
| 27 | 27 | {{ range $year.Pages }} |
| @@ -10,10 +10,10 @@ | |||
| 10 | <div class="index-body"> | 10 | <div class="index-body"> |
| 11 | {{ with $lead }} | 11 | {{ with $lead }} |
| 12 | <div class="index-lead rise" style="--i: 1"> | 12 | <div class="index-lead rise" style="--i: 1"> |
| 13 | - <span class="label">Latest · {{ .Date.Format "January 2, 2006" }}</span> | 13 | + <span class="label">{{ i18n "latest" }} · {{ .Date.Format "January 2, 2006" }}</span> |
| 14 | <a class="title" href="{{ .RelPermalink }}">{{ .Title }}</a> | 14 | <a class="title" href="{{ .RelPermalink }}">{{ .Title }}</a> |
| 15 | <span class="excerpt">{{ .Summary }}</span> | 15 | <span class="excerpt">{{ .Summary }}</span> |
| 16 | - <span class="more" aria-hidden="true">Read →</span> | 16 | + <span class="more" aria-hidden="true">{{ i18n "readMore" }}</span> |
| 17 | </div> | 17 | </div> |
| 18 | {{ end }} | 18 | {{ end }} |
| 19 | 19 | ||
| @@ -21,7 +21,7 @@ | |||
| 21 | <section class="year rise" style="--i: {{ add $i 2 }}"> | 21 | <section class="year rise" style="--i: {{ add $i 2 }}"> |
| 22 | <h2 class="year__label"> | 22 | <h2 class="year__label"> |
| 23 | {{ $year.Key }} | 23 | {{ $year.Key }} |
| 24 | - <span class="count">{{ len $year.Pages }} posts</span> | 24 | + <span class="count">{{ i18n "postsCount" (dict "Count" (len $year.Pages)) }}</span> |
| 25 | </h2> | 25 | </h2> |
| 26 | <ul class="year__list"> | 26 | <ul class="year__list"> |
| 27 | {{ range $year.Pages }} | 27 | {{ range $year.Pages }} |
modified
blog/layouts/partials/masthead.html +2 -2 | @@ -1,8 +1,8 @@ | ||
| 1 | 1 | {{- $signal := partial "signal.html" .RelPermalink -}} |
| 2 | 2 | <header class="masthead column rise"> |
| 3 | - <p class="masthead__title"><a href="{{ "/" | relURL }}">{{ site.Title }}</a></p> | |
| 3 | + <p class="masthead__title"><a href="{{ "/" | relLangURL }}">{{ site.Title }}</a></p> | |
| 4 | 4 | {{- if and .IsHome site.Params.profile }} |
| 5 | - <a class="masthead__about" href="#about">About</a> | |
| 5 | + <a class="masthead__about" href="#about">{{ i18n "about" }}</a> | |
| 6 | 6 | {{- end }} |
| 7 | 7 | <div |
| 8 | 8 | class="masthead__signal" |
| @@ -1,8 +1,8 @@ | |||
| 1 | {{- $signal := partial "signal.html" .RelPermalink -}} | 1 | {{- $signal := partial "signal.html" .RelPermalink -}} |
| 2 | <header class="masthead column rise"> | 2 | <header class="masthead column rise"> |
| 3 | - <p class="masthead__title"><a href="{{ "/" | relURL }}">{{ site.Title }}</a></p> | 3 | + <p class="masthead__title"><a href="{{ "/" | relLangURL }}">{{ site.Title }}</a></p> |
| 4 | {{- if and .IsHome site.Params.profile }} | 4 | {{- if and .IsHome site.Params.profile }} |
| 5 | - <a class="masthead__about" href="#about">About</a> | 5 | + <a class="masthead__about" href="#about">{{ i18n "about" }}</a> |
| 6 | {{- end }} | 6 | {{- end }} |
| 7 | <div | 7 | <div |
| 8 | class="masthead__signal" | 8 | class="masthead__signal" |
modified
blog/layouts/partials/site-foot.html +16 -3 | @@ -1,16 +1,29 @@ | ||
| 1 | 1 | <footer class="site-foot column"> |
| 2 | - <span>Text is <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA</a></span> | |
| 2 | + <span>{{ i18n "licenseText" }} <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA</a></span> | |
| 3 | 3 | <span class="sep" aria-hidden="true">·</span> |
| 4 | 4 | <span> |
| 5 | - <a class="rss" href="{{ "index.xml" | relURL }}" title="Subscribe via RSS" hx-boost="false"> | |
| 5 | + <a class="rss" href="{{ "index.xml" | relLangURL }}" title="{{ i18n "subscribeRSS" }}" hx-boost="false"> | |
| 6 | 6 | <svg viewBox="0 0 24 24" aria-hidden="true"> |
| 7 | 7 | <path d="M4 11a9 9 0 0 1 9 9h-2.5A6.5 6.5 0 0 0 4 13.5V11z" /> |
| 8 | 8 | <path d="M4 4a16 16 0 0 1 16 16h-2.5A13.5 13.5 0 0 0 4 6.5V4z" /> |
| 9 | 9 | <circle cx="5.5" cy="18.5" r="2" /> |
| 10 | 10 | </svg> |
| 11 | - RSS | |
| 11 | + {{ i18n "rssLabel" }} | |
| 12 | 12 | </a> |
| 13 | 13 | </span> |
| 14 | + {{- $translations := .AllTranslations }} | |
| 15 | + {{- if gt (len $translations) 1 }} | |
| 16 | + <span class="sep" aria-hidden="true">·</span> | |
| 17 | + <span class="lang-switch"> | |
| 18 | + {{- range sort $translations "Language.Weight" }} | |
| 19 | + {{- if eq .Lang $.Lang }} | |
| 20 | + <span class="lang-switch__current">{{ .Language.Label }}</span> | |
| 21 | + {{- else }} | |
| 22 | + <a href="{{ .Permalink }}" hreflang="{{ .Lang }}" hx-boost="false">{{ .Language.Label }}</a> | |
| 23 | + {{- end }} | |
| 24 | + {{- end }} | |
| 25 | + </span> | |
| 26 | + {{- end }} | |
| 14 | 27 | {{ with site.Params.nixstorepath }} |
| 15 | 28 | <span class="store-path-row"> |
| 16 | 29 | <code class="store-path" title="This site was built by Nix">{{ . }}</code> |
| @@ -1,16 +1,29 @@ | |||
| 1 | <footer class="site-foot column"> | 1 | <footer class="site-foot column"> |
| 2 | - <span>Text is <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA</a></span> | 2 | + <span>{{ i18n "licenseText" }} <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA</a></span> |
| 3 | <span class="sep" aria-hidden="true">·</span> | 3 | <span class="sep" aria-hidden="true">·</span> |
| 4 | <span> | 4 | <span> |
| 5 | - <a class="rss" href="{{ "index.xml" | relURL }}" title="Subscribe via RSS" hx-boost="false"> | 5 | + <a class="rss" href="{{ "index.xml" | relLangURL }}" title="{{ i18n "subscribeRSS" }}" hx-boost="false"> |
| 6 | <svg viewBox="0 0 24 24" aria-hidden="true"> | 6 | <svg viewBox="0 0 24 24" aria-hidden="true"> |
| 7 | <path d="M4 11a9 9 0 0 1 9 9h-2.5A6.5 6.5 0 0 0 4 13.5V11z" /> | 7 | <path d="M4 11a9 9 0 0 1 9 9h-2.5A6.5 6.5 0 0 0 4 13.5V11z" /> |
| 8 | <path d="M4 4a16 16 0 0 1 16 16h-2.5A13.5 13.5 0 0 0 4 6.5V4z" /> | 8 | <path d="M4 4a16 16 0 0 1 16 16h-2.5A13.5 13.5 0 0 0 4 6.5V4z" /> |
| 9 | <circle cx="5.5" cy="18.5" r="2" /> | 9 | <circle cx="5.5" cy="18.5" r="2" /> |
| 10 | </svg> | 10 | </svg> |
| 11 | - RSS | 11 | + {{ i18n "rssLabel" }} |
| 12 | </a> | 12 | </a> |
| 13 | </span> | 13 | </span> |
| 14 | + {{- $translations := .AllTranslations }} | ||
| 15 | + {{- if gt (len $translations) 1 }} | ||
| 16 | + <span class="sep" aria-hidden="true">·</span> | ||
| 17 | + <span class="lang-switch"> | ||
| 18 | + {{- range sort $translations "Language.Weight" }} | ||
| 19 | + {{- if eq .Lang $.Lang }} | ||
| 20 | + <span class="lang-switch__current">{{ .Language.Label }}</span> | ||
| 21 | + {{- else }} | ||
| 22 | + <a href="{{ .Permalink }}" hreflang="{{ .Lang }}" hx-boost="false">{{ .Language.Label }}</a> | ||
| 23 | + {{- end }} | ||
| 24 | + {{- end }} | ||
| 25 | + </span> | ||
| 26 | + {{- end }} | ||
| 14 | {{ with site.Params.nixstorepath }} | 27 | {{ with site.Params.nixstorepath }} |
| 15 | <span class="store-path-row"> | 28 | <span class="store-path-row"> |
| 16 | <code class="store-path" title="This site was built by Nix">{{ . }}</code> | 29 | <code class="store-path" title="This site was built by Nix">{{ . }}</code> |
added
blog/layouts/robots.txt +8 -0 | new file mode 100644 | ||
| @@ -0,0 +1,8 @@ | ||
| 1 | +User-agent: * | |
| 2 | +{{ if site.Params.noindex -}} | |
| 3 | +Disallow: / | |
| 4 | +{{- else -}} | |
| 5 | +Disallow: | |
| 6 | + | |
| 7 | +Sitemap: {{ "sitemap.xml" | absLangURL }} | |
| 8 | +{{- end }} | |
| new file mode 100644 | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | +User-agent: * | ||
| 2 | +{{ if site.Params.noindex -}} | ||
| 3 | +Disallow: / | ||
| 4 | +{{- else -}} | ||
| 5 | +Disallow: | ||
| 6 | + | ||
| 7 | +Sitemap: {{ "sitemap.xml" | absLangURL }} | ||
| 8 | +{{- end }} | ||