website-htmx-rustelo-code/scripts/sync/sync-page-ftl.nu
2026-07-10 03:44:13 +01:00

156 lines
6.1 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# sync-page-ftl.nu
# Extracts data-key/data-en/data-es from a source index.html and writes FTL files.
#
# Usage:
# nu scripts/sync/sync-page-ftl.nu --html <path> --out-en <path> --out-es <path>
# nu scripts/sync/sync-page-ftl.nu --html <path> --out-en <path> --out-es <path> --dry-run
#
# Convention required in source HTML:
# <element data-key="ftl-key-name" data-en="English text" data-es="Texto español">
def main [
--html: string, # Absolute path to source index.html
--out-en: string, # Output path for English FTL
--out-es: string, # Output path for Spanish FTL
--dry-run # Print changes without writing
] {
if not ($html | path exists) {
error make { msg: $"Source HTML not found: ($html)" }
}
let content = open $html
# Normalize multiline tags: collapse newlines + surrounding whitespace to a single space.
# This handles HTML where data-key, data-en, data-es are on separate lines within a tag.
let flat = $content | str replace --all --regex '\n[ \t]+' ' '
# Strategy: regex with a separator that skips quoted attribute values containing > or <.
# Covers all 6 orderings of data-key / data-en / data-es within a tag.
#
# q = (?:"[^"]*"|[^>"])*? — matches space between attrs, skipping complete "..." values
# Built via concatenation to avoid Nu interpolation parsing ( as expression.
let k = 'data-key="(?P<key>[^"]+)"'
let e = 'data-en="(?P<en>[^"]*)"'
let s = 'data-es="(?P<es>[^"]*)"'
let q = '(?:"[^"]*"|[^>"])*?'
let pat_kes = ($k + $q + $e + $q + $s)
let pat_kse = ($k + $q + $s + $q + $e)
let pat_eks = ($e + $q + $k + $q + $s)
let pat_esk = ($e + $q + $s + $q + $k)
let pat_ske = ($s + $q + $k + $q + $e)
let pat_sek = ($s + $q + $e + $q + $k)
let all_entries = (
[
($flat | parse --regex $pat_kes),
($flat | parse --regex $pat_kse),
($flat | parse --regex $pat_eks),
($flat | parse --regex $pat_esk),
($flat | parse --regex $pat_ske),
($flat | parse --regex $pat_sek),
]
| flatten
| uniq-by key
| sort-by key
)
if ($all_entries | length) == 0 {
print $"⚠ No data-key elements found in ($html)"
print " Ensure elements have: data-key=\"ftl-key\" data-en=\"...\" data-es=\"...\""
return
}
def decode-entities [val: string]: nothing -> string {
$val
| str replace --all "&amp;" "&"
| str replace --all "&lt;" "<"
| str replace --all "&gt;" ">"
| str replace --all "&quot;" "\""
| str replace --all "&#39;" "'"
| str replace --all "&mdash;" "—"
| str replace --all "&ndash;" ""
| str replace --all "&nbsp;" " "
| str replace --all "&iacute;" "í"
| str replace --all "&eacute;" "é"
| str replace --all "&oacute;" "ó"
| str replace --all "&uacute;" "ú"
| str replace --all "&aacute;" "á"
| str replace --all "&ntilde;" "ñ"
| str replace --all "&#[0-9]+;" ""
}
# Parse existing FTL into a map of key→value (preserves manually-maintained keys)
def parse-ftl [path: string]: nothing -> list<record<key: string, val: string>> {
if not ($path | path exists) { return [] }
open $path
| lines
| where { |l| not ($l | str starts-with '#') and ($l | str contains ' = ') }
| each { |l|
let idx = $l | str index-of ' = '
let k = $l | str substring 0..<$idx
let v = $l | str substring ($idx + 3)..
{ key: $k, val: $v }
}
}
# Build HTML-extracted maps (key → decoded value)
let html_en = $all_entries | each { |row| { key: $row.key, val: (decode-entities $row.en) } }
let html_es = $all_entries | each { |row| { key: $row.key, val: (decode-entities $row.es) } }
# Merge: start from existing FTL, update/add keys from HTML, preserve FTL-only keys
def merge-ftl [existing: list<record<key: string, val: string>>, from_html: list<record<key: string, val: string>>]: nothing -> string {
let html_map = $from_html | reduce -f {} { |row, acc| $acc | insert $row.key $row.val }
let merged = $existing | each { |row|
let updated = $html_map | get -o $row.key
if $updated != null { { key: $row.key, val: $updated } } else { $row }
}
# Add new keys from HTML that weren't in existing FTL
let existing_keys = $existing | get key
let new_keys = $from_html | where { |row| not ($existing_keys | any { |k| $k == $row.key }) }
($merged | append $new_keys) | each { |row| $"($row.key) = ($row.val)" } | str join "\n"
}
let existing_en = parse-ftl $out_en
let existing_es = parse-ftl $out_es
let ftl_en = if ($existing_en | length) > 0 {
merge-ftl $existing_en $html_en
} else {
$html_en | each { |row| $"($row.key) = ($row.val)" } | str join "\n"
}
let ftl_es = if ($existing_es | length) > 0 {
merge-ftl $existing_es $html_es
} else {
$html_es | each { |row| $"($row.key) = ($row.val)" } | str join "\n"
}
print $" Found ($all_entries | length) translatable keys from HTML"
if $dry_run {
print "\n── EN (preview first 10) ──"
print ($ftl_en | lines | first 10 | str join "\n")
print "\n── ES (preview first 10) ──"
print ($ftl_es | lines | first 10 | str join "\n")
return
}
# Write EN
let current_en = if ($out_en | path exists) { open $out_en } else { "" }
if $ftl_en != $current_en {
$ftl_en | save --force $out_en
print $" ✓ ($out_en)"
} else {
print $" — ($out_en) unchanged"
}
# Write ES
let current_es = if ($out_es | path exists) { open $out_es } else { "" }
if $ftl_es != $current_es {
$ftl_es | save --force $out_es
print $" ✓ ($out_es)"
} else {
print $" — ($out_es) unchanged"
}
}