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.
246 lines
10 KiB
Bash
Executable File
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"
|