Files
onepager/render.sh
m 846fc04444 feat: i18n template infrastructure — render.sh reads _en vars, emits data-de/data-en
Phase 1 of i18n rollout:
- render.sh: i18n_attrs helper, reads *_en fields from site.yaml, emits
  data-de/data-en attributes on title, description, role, tagline, cta,
  tags, section titles, card titles/descriptions, bio, content
- base.html: i18n.js auto-included, title/description get i18n attrs
- All 6 templates: translatable elements get i18n attr placeholders,
  footer toggle button with machine-translation disclaimer
- ichbinotto.de pilot: added machine-translation disclaimer per m's request

Templated sites can now be translated by adding _en fields to site.yaml.
2026-04-01 12:49:34 +02:00

246 lines
10 KiB
Bash
Executable File

#!/bin/bash
# Render a site from site.yaml + template
# Usage: ./render.sh sites/example.de/site.yaml templates/person-dark.html
# Outputs rendered HTML to stdout
set -euo pipefail
SITE_YAML="$1"
TEMPLATE_FILE="$2"
SITE_DIR=$(dirname "$SITE_YAML")
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# Check dependencies
if ! command -v yq &>/dev/null; then
echo "ERROR: yq is required. Install: https://github.com/mikefarah/yq" >&2
exit 1
fi
# Read site config
domain=$(yq -r '.domain' "$SITE_YAML")
title=$(yq -r '.title // .domain' "$SITE_YAML")
description=$(yq -r '.description // ""' "$SITE_YAML")
lang=$(yq -r '.lang // "de"' "$SITE_YAML")
favicon=$(yq -r '.vars.favicon // ""' "$SITE_YAML")
name=$(yq -r '.vars.name // .title // ""' "$SITE_YAML")
role=$(yq -r '.vars.role // ""' "$SITE_YAML")
initials=$(yq -r '.vars.initials // ""' "$SITE_YAML")
tagline=$(yq -r '.vars.tagline // ""' "$SITE_YAML")
accent=$(yq -r '.vars.accent // "#c9a84c"' "$SITE_YAML")
accent_light=$(yq -r '.vars.accent_light // "rgba(201, 168, 76, 0.1)"' "$SITE_YAML")
font_primary=$(yq -r '.vars.font_primary // "Inter"' "$SITE_YAML")
font_secondary=$(yq -r '.vars.font_secondary // "Inter"' "$SITE_YAML")
emoji=$(yq -r '.vars.emoji // ""' "$SITE_YAML")
cta_text=$(yq -r '.vars.cta.text // ""' "$SITE_YAML")
cta_href=$(yq -r '.vars.cta.href // "#"' "$SITE_YAML")
year=$(date +%Y)
# i18n: helper to generate data-de/data-en attribute string
i18n_attrs() {
local de="$1" en="$2"
if [ -n "$en" ] && [ "$en" != "null" ]; then
de=$(echo "$de" | sed 's/"/\"/g')
en=$(echo "$en" | sed 's/"/\"/g')
echo " data-de=\"${de}\" data-en=\"${en}\""
fi
}
# i18n: read English overrides
title_en=$(yq -r '.title_en // ""' "$SITE_YAML")
description_en=$(yq -r '.description_en // ""' "$SITE_YAML")
role_en=$(yq -r '.vars.role_en // ""' "$SITE_YAML")
tagline_en=$(yq -r '.vars.tagline_en // ""' "$SITE_YAML")
cta_text_en=$(yq -r '.vars.cta.text_en // ""' "$SITE_YAML")
# i18n: build attribute strings for simple fields
title_i18n=$(i18n_attrs "$title" "$title_en")
description_i18n=$(i18n_attrs "$description" "$description_en")
role_i18n=$(i18n_attrs "$role" "$role_en")
tagline_i18n=$(i18n_attrs "$tagline" "$tagline_en")
cta_i18n=$(i18n_attrs "$cta_text" "$cta_text_en")
# Build tags HTML
tags_html=""
tag_count=$(yq -r '.vars.tags | length' "$SITE_YAML" 2>/dev/null || echo "0")
if [ "$tag_count" -gt 0 ]; then
for i in $(seq 0 $((tag_count - 1))); do
tag=$(yq -r ".vars.tags[$i]" "$SITE_YAML")
tag_en=$(yq -r ".vars.tags_en[$i] // \"\"" "$SITE_YAML" 2>/dev/null || echo "")
tag_i18n=""
if [ -n "$tag_en" ] && [ "$tag_en" != "null" ]; then
tag_i18n="$(i18n_attrs "$tag" "$tag_en")"
fi
tags_html="${tags_html} <span class=\"tag\"${tag_i18n}>${tag}</span>\n"
done
fi
# Build sections HTML
sections_html=""
section_count=$(yq -r '.vars.sections | length' "$SITE_YAML" 2>/dev/null || echo "0")
if [ "$section_count" -gt 0 ]; then
for i in $(seq 0 $((section_count - 1))); do
sec_type=$(yq -r ".vars.sections[$i].type" "$SITE_YAML")
sec_title=$(yq -r ".vars.sections[$i].title // \"\"" "$SITE_YAML")
if [ "$sec_type" = "features" ]; then
sec_title_en=$(yq -r ".vars.sections[$i].title_en // \"\"" "$SITE_YAML")
sections_html="${sections_html} <div class=\"section fade-in stagger-$((i+2))\">\n"
if [ -n "$sec_title" ]; then
sec_title_i18n=$(i18n_attrs "$sec_title" "$sec_title_en")
sections_html="${sections_html} <h2${sec_title_i18n}>${sec_title}</h2>\n"
fi
sections_html="${sections_html} <div class=\"grid\">\n"
item_count=$(yq -r ".vars.sections[$i].items | length" "$SITE_YAML" 2>/dev/null || echo "0")
for j in $(seq 0 $((item_count - 1))); do
item_title=$(yq -r ".vars.sections[$i].items[$j].title" "$SITE_YAML")
item_desc=$(yq -r ".vars.sections[$i].items[$j].desc // \"\"" "$SITE_YAML")
item_title_en=$(yq -r ".vars.sections[$i].items[$j].title_en // \"\"" "$SITE_YAML")
item_desc_en=$(yq -r ".vars.sections[$i].items[$j].desc_en // \"\"" "$SITE_YAML")
item_title_i18n=$(i18n_attrs "$item_title" "$item_title_en")
item_desc_i18n=$(i18n_attrs "$item_desc" "$item_desc_en")
sections_html="${sections_html} <div class=\"card\">\n"
sections_html="${sections_html} <h3${item_title_i18n}>${item_title}</h3>\n"
[ -n "$item_desc" ] && sections_html="${sections_html} <p${item_desc_i18n}>${item_desc}</p>\n"
sections_html="${sections_html} </div>\n"
done
sections_html="${sections_html} </div>\n </div>\n"
elif [ "$sec_type" = "profile" ]; then
bio=$(yq -r ".vars.sections[$i].bio // \"\"" "$SITE_YAML")
bio_en=$(yq -r ".vars.sections[$i].bio_en // \"\"" "$SITE_YAML")
sec_title_en=$(yq -r ".vars.sections[$i].title_en // \"\"" "$SITE_YAML")
sections_html="${sections_html} <div class=\"section fade-in stagger-$((i+2))\">\n"
if [ -n "$sec_title" ]; then
sec_title_i18n=$(i18n_attrs "$sec_title" "$sec_title_en")
sections_html="${sections_html} <h2${sec_title_i18n}>${sec_title}</h2>\n"
fi
bio_i18n=$(i18n_attrs "$bio" "$bio_en")
sections_html="${sections_html} <p class=\"bio\"${bio_i18n}>${bio}</p>\n"
sections_html="${sections_html} </div>\n"
fi
done
fi
# Build content HTML (for editorial template)
content_html=""
content=$(yq -r '.vars.content // ""' "$SITE_YAML")
content_i18n=""
if [ -n "$content" ] && [ "$content" != "null" ]; then
# Convert newlines to paragraphs
content_html=$(echo "$content" | sed 's/^$/<\/p><p>/g' | sed '1s/^/<p>/' | sed '$s/$/<\/p>/')
content_en=$(yq -r '.vars.content_en // ""' "$SITE_YAML")
if [ -n "$content_en" ] && [ "$content_en" != "null" ]; then
content_html_en=$(echo "$content_en" | sed 's/^$/<\/p><p>/g' | sed '1s/^/<p>/' | sed '$s/$/<\/p>/')
content_i18n=$(i18n_attrs "$content_html" "$content_html_en")
fi
fi
# Read template file and extract CSS/body sections
template_content=$(cat "$TEMPLATE_FILE")
# Extract template CSS (between markers)
template_css=$(echo "$template_content" | sed -n '/{{template_css_start}}/,/{{template_css_end}}/p' | sed '1d;$d')
# Extract template body (between markers)
template_body=$(echo "$template_content" | sed -n '/{{template_body_start}}/,/{{template_body_end}}/p' | sed '1d;$d')
# Read base template
base=$(cat "$SCRIPT_DIR/templates/base.html")
# Read shared CSS
css_variables=$(cat "$SCRIPT_DIR/shared/css/variables.css")
css_responsive=$(cat "$SCRIPT_DIR/shared/css/responsive.css")
css_animations=$(cat "$SCRIPT_DIR/shared/css/animations.css")
css_noise=$(cat "$SCRIPT_DIR/shared/css/noise.css")
fonts=$(cat "$SCRIPT_DIR/shared/fonts.html")
# Additional font links if non-Inter fonts are used
extra_fonts=""
if [ "$font_primary" != "Inter" ]; then
font_url=$(echo "$font_primary" | tr ' ' '+')
extra_fonts="${extra_fonts} <link href=\"https://fonts.googleapis.com/css2?family=${font_url}:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n"
fi
if [ "$font_secondary" != "Inter" ] && [ "$font_secondary" != "$font_primary" ]; then
font_url=$(echo "$font_secondary" | tr ' ' '+')
extra_fonts="${extra_fonts} <link href=\"https://fonts.googleapis.com/css2?family=${font_url}:wght@400;500;700&display=swap\" rel=\"stylesheet\">\n"
fi
fonts="${fonts}${extra_fonts}"
# Assemble: substitute into base template
output="$base"
# Replace shared includes
output=$(echo "$output" | sed "s|{{fonts}}|$(echo "$fonts" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|")
# For CSS blocks, use temp files to handle multiline
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
echo "$css_variables" > "$tmpdir/css_variables"
echo "$css_responsive" > "$tmpdir/css_responsive"
echo "$css_animations" > "$tmpdir/css_animations"
echo "$css_noise" > "$tmpdir/css_noise"
echo "$template_css" > "$tmpdir/template_css"
printf '%b' "$template_body" > "$tmpdir/template_body"
# Use awk for reliable multiline substitution
awk -v vars="$tmpdir/css_variables" \
-v resp="$tmpdir/css_responsive" \
-v anim="$tmpdir/css_animations" \
-v noise="$tmpdir/css_noise" \
-v tcss="$tmpdir/template_css" \
-v tbody="$tmpdir/template_body" \
-v fonts_file=<(echo "$fonts") \
'
function read_file(path, line, content) {
content = ""
while ((getline line < path) > 0) content = content line "\n"
close(path)
return content
}
BEGIN {
cv = read_file(vars)
cr = read_file(resp)
ca = read_file(anim)
cn = read_file(noise)
tc = read_file(tcss)
tb = read_file(tbody)
}
{
gsub(/\{\{css_variables\}\}/, cv)
gsub(/\{\{css_responsive\}\}/, cr)
gsub(/\{\{css_animations\}\}/, ca)
gsub(/\{\{css_noise\}\}/, cn)
gsub(/\{\{template_css\}\}/, tc)
gsub(/\{\{body\}\}/, tb)
print
}
' <<< "$output" | sed \
-e "s|{{title}}|${title}|g" \
-e "s|{{description}}|${description}|g" \
-e "s|{{lang}}|${lang}|g" \
-e "s|{{favicon}}|${favicon}|g" \
-e "s|{{name}}|${name}|g" \
-e "s|{{role}}|${role}|g" \
-e "s|{{initials}}|${initials}|g" \
-e "s|{{tagline}}|${tagline}|g" \
-e "s|{{accent}}|${accent}|g" \
-e "s|{{accent_light}}|${accent_light}|g" \
-e "s|{{font_primary}}|${font_primary}|g" \
-e "s|{{font_secondary}}|${font_secondary}|g" \
-e "s|{{emoji}}|${emoji}|g" \
-e "s|{{cta_text}}|${cta_text}|g" \
-e "s|{{cta_href}}|${cta_href}|g" \
-e "s|{{year}}|${year}|g" \
-e "s|{{domain}}|${domain}|g" \
-e "s|{{title_i18n}}|$(echo "$title_i18n" | sed 's/[&/\]/\\&/g')|g" \
-e "s|{{description_i18n}}|$(echo "$description_i18n" | sed 's/[&/\]/\\&/g')|g" \
-e "s|{{role_i18n}}|$(echo "$role_i18n" | sed 's/[&/\]/\\&/g')|g" \
-e "s|{{tagline_i18n}}|$(echo "$tagline_i18n" | sed 's/[&/\]/\\&/g')|g" \
-e "s|{{cta_i18n}}|$(echo "$cta_i18n" | sed 's/[&/\]/\\&/g')|g" \
-e "s|{{content_i18n}}|$(echo "$content_i18n" | sed 's/[&/\]/\\&/g')|g" | \
sed "s|{{tags_html}}|$(printf '%b' "$tags_html" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|g" | \
sed "s|{{sections_html}}|$(printf '%b' "$sections_html" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|g" | \
sed "s|{{content_html}}|$(printf '%b' "$content_html" | sed 's/[&/\]/\\&/g; s/$/\\/' | sed '$ s/\\$//')|g"