156 lines
6.1 KiB
Text
156 lines
6.1 KiB
Text
#!/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 "&" "&"
|
||
| str replace --all "<" "<"
|
||
| str replace --all ">" ">"
|
||
| str replace --all """ "\""
|
||
| str replace --all "'" "'"
|
||
| str replace --all "—" "—"
|
||
| str replace --all "–" "–"
|
||
| str replace --all " " " "
|
||
| str replace --all "í" "í"
|
||
| str replace --all "é" "é"
|
||
| str replace --all "ó" "ó"
|
||
| str replace --all "ú" "ú"
|
||
| str replace --all "á" "á"
|
||
| str replace --all "ñ" "ñ"
|
||
| 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"
|
||
}
|
||
}
|