Compare commits

..

3 Commits

Author SHA1 Message Date
Xavier Roche
a62bcbb47e Anchor the locale test's lift so it cannot silently stop testing
The lift ended at the first blank line, so a blank line grown inside the
locale block would truncate it: reinstating the old LANG-only bug below one
left the test passing 14/14 while the launcher returned English. Anchor on
the next section instead, and pin exported-empty as unset, which lang_of
cannot express.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-16 23:54:26 +02:00
Xavier Roche
24cb916562 Trim the locale test's comments
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-16 23:40:53 +02:00
Xavier Roche
f8235621c0 Fix webhttrack picking the wrong language from the locale
The launcher resolved LC_MESSAGES/LC_ALL/LANG into HTSLANG, then immediately
overwrote it with a value recomputed from $LANG alone, so LC_ALL and
LC_MESSAGES were ignored: a user with only LC_ALL=fr_FR.UTF-8 set got English.
That same line cut the region off with "cut -f1 -d'_'", leaving the zh_tw and
pt_br entries of lang.indexes unreachable, so Traditional Chinese fell back to
Simplified and Brazilian Portuguese to European Portuguese.

Resolve the locale in POSIX precedence order and look the tag up region-first,
falling back to the bare language. Also fix Slovenian's ISO 639-1 code, which
was "si" (Sinhala) in both lang.indexes and Slovenian.txt: sl_SI users got
English, and the served pages claimed lang="si".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Xavier Roche <roche@httrack.com>
2026-07-16 23:36:05 +02:00
9 changed files with 114 additions and 101 deletions

View File

@@ -23,7 +23,7 @@ pt:7
ro:25
ru:8
sk:20
si:24
sl:24
sv:17
tr:10
uk:22

View File

@@ -930,6 +930,7 @@ Server terminated
Palvelin lopetettu
A fatal error has occurred during this mirror
Tällä peilillä tapahtui vakava virhe
Proxy type:
Välityspalvelimen tyyppi:
Proxy protocol. HTTP: standard proxy. HTTP (CONNECT tunnel): sends every request through a CONNECT tunnel, for CONNECT-only proxies like Tor's HTTPTunnelPort. SOCKS5: default port 1080.

View File

@@ -374,7 +374,7 @@ Create error logging and report files
Crea file di log per segnalare errori e informazioni
Generate DOS 8-3 filenames ONLY
Genera solo nomi di file in formato 8.3
Generate ISO9660 filenames ONLY for CDROM medias
Generate ISO 9660 filenames ONLY for CDROM medias
Genera nomi di file in formato ISO9660 per i CDROM
Do not create HTML error pages
Non generare pagine d'errore HTML

View File

@@ -918,7 +918,7 @@ normal\nextended\ndebug
normal\nextendido\ncorrigir
Download web site(s)\nDownload web site(s) + questions\nGet individual files\nDownload all sites in pages (multiple mirror)\nTest links in pages (bookmark test)\n* Continue interrupted download\n* Update existing download
Copiar site(s) da Web\nCopiar site(s) interativos da Web (perguntas)\nReceber arquivos específicos\nCopiar todas as páginas do site (alternação múltipla)\nTestar links nas páginas (testar indicador)\n* Retomar download interrompido\n* Atualizar download existente
Relative URI / Absolute URL (default)\nAbsolute URL / Absolute URL\nAbsolute URI / Absolute URL\nOriginal URL / Original URL
Relative URL / Absolute URL (default)\nAbsolute URL / Absolute URL\nAbsolute URI / Absolute URL\nOriginal URL / Original URL
URI Relativa / URL Absoluta (padrão)\nURL Absoluta / URL Absoluta\nURI Absoluta / URL Absoluta\nURL Original / URL Original
Open Source offline browser
Abrir origem offline no navegador

View File

@@ -3,7 +3,7 @@ Slovenian
LANGUAGE_FILE
Slovenian
LANGUAGE_ISO
si
sl
LANGUAGE_AUTHOR
Jadran Rudec,iur.\r\njrudec@email.si \r\n
LANGUAGE_CHARSET

View File

@@ -33,6 +33,19 @@ function log {
return 0
}
# Map a POSIX locale ("zh_TW.UTF-8@euro") to its lang.indexes number, English (1) if unknown
function lang_index {
local locale=$1 indexes=$2 tag n t
tag=$(echo "${locale}" | cut -f1 -d'.' | cut -f1 -d'@' | tr '[:upper:]' '[:lower:]' | tr -cd 'a-z_')
# a few languages carry a region-specific entry (zh_tw, pt_br); else use the bare language
for t in "${tag}" "${tag%%_*}"; do
n=$(grep -E "^${t}:" "${indexes}" | cut -f2 -d':')
test -n "${n}" && break
done
test -n "${n}" || n=1
echo "${n}"
}
function launch_browser {
log "Launching $1"
browser=$1
@@ -59,13 +72,11 @@ test -f "${DISTPATH}/lang.indexes" || ! log "Could not find ${DISTPATH}/lang.ind
test -d "${DISTPATH}/lang" || ! log "Could not find ${DISTPATH}/lang" || exit 1
test -d "${DISTPATH}/html" || ! log "Could not find ${DISTPATH}/html" || exit 1
# Locale
HTSLANG="${LC_MESSAGES}"
! test -n "${HTSLANG}" && HTSLANG="${LC_ALL}"
# Locale: POSIX precedence, LC_ALL overrides LC_MESSAGES overrides LANG
HTSLANG="${LC_ALL}"
! test -n "${HTSLANG}" && HTSLANG="${LC_MESSAGES}"
! test -n "${HTSLANG}" && HTSLANG="${LANG}"
HTSLANG="$(echo "$LANG" | cut -f1 -d'.' | cut -f1 -d'_')"
LANGN=$(grep -E "^${HTSLANG}:" "${DISTPATH}/lang.indexes" | cut -f2 -d':')
! test -n "${LANGN}" && LANGN=1
LANGN=$(lang_index "${HTSLANG}" "${DISTPATH}/lang.indexes")
# Find the browser
# note: not all systems have sensible-browser or www-browser alternative

View File

@@ -0,0 +1,91 @@
#!/bin/bash
#
# webhttrack must map the caller's locale to the right lang.indexes number.
# Guards: LC_ALL/LC_MESSAGES were ignored, and zh_tw/pt_br were unreachable.
set -euo pipefail
testdir=$(cd "$(dirname "$0")" && pwd)
distdir=$(cd "${testdir}/.." && pwd)
script="${distdir}/src/webhttrack"
fail() {
echo "FAIL: $*" >&2
exit 1
}
test -r "${script}" || fail "no ${script}"
test -r "${distdir}/lang.indexes" || fail "no ${distdir}/lang.indexes"
# Lift the locale code out of the launcher, so this tests the shipped logic itself.
funcs=$(sed -n '/^function lang_index/,/^}/p' "${script}")
# End on the next section, not a blank line: a blank line grown inside the block
# would truncate the lift and silently stop testing everything below it.
grep -q '^# Find the browser' "${script}" || fail "locale block terminator moved in ${script}"
block=$(sed -n '/^# Locale/,/^# Find the browser/p' "${script}" | sed '$d')
echo "${block}" | grep -q 'LANGN=.*lang_index' || fail "could not lift the whole locale block from ${script}"
# Run the lifted block against whatever locale vars the caller exported.
run_block() {
# shellcheck disable=SC2034 # read by the eval'd locale block
DISTPATH="${distdir}"
# the launcher runs under neither -e ("! test -n x && y" returns 1) nor -u
set +eu
eval "${funcs}"
eval "${block}"
# shellcheck disable=SC2153 # LANGN is set by the eval'd block, not a LANG typo
echo "${LANGN}"
}
# Resolve LANG/LC_ALL/LC_MESSAGES (empty = unset) to the language number.
lang_of() {
(
unset LANG LC_ALL LC_MESSAGES
# only the value matters here; hide bash's setlocale gripe when the locale is not installed
# shellcheck disable=SC2030 # confining the locale to this subshell is the point
{
if test -n "$1"; then export LANG="$1"; fi
if test -n "$2"; then export LC_ALL="$2"; fi
if test -n "$3"; then export LC_MESSAGES="$3"; fi
} 2>/dev/null
run_block
)
}
check() {
local want=$1 got desc
shift
desc="LANG='$1' LC_ALL='$2' LC_MESSAGES='$3'"
got=$(lang_of "$1" "$2" "$3")
test "${got}" = "${want}" || fail "${desc}: want ${want}, got ${got}"
echo "ok: ${desc} -> ${got}"
}
# want LANG LC_ALL LC_MESSAGES
check 2 "fr_FR.UTF-8" "" ""
check 14 "zh_TW.UTF-8" "" "" # region-qualified: Chinese-BIG5, not Simplified (13)
check 12 "pt_BR.UTF-8" "" "" # region-qualified: Portugues-Brasil, not Portugues (7)
check 13 "zh_CN.UTF-8" "" "" # unlisted region falls back to the bare language
check 24 "sl_SI.UTF-8" "" "" # Slovenian is ISO 639-1 "sl"
check 4 "" "de_DE.UTF-8" "" # LC_ALL alone
check 4 "" "" "de_DE.UTF-8" # LC_MESSAGES alone
check 4 "fr_FR.UTF-8" "de_DE.UTF-8" "" # LC_ALL wins over LANG
check 4 "fr_FR.UTF-8" "" "de_DE.UTF-8" # LC_MESSAGES wins over LANG
check 2 "de_DE.UTF-8" "fr_FR.UTF-8" "de_DE.UTF-8" # LC_ALL wins over LC_MESSAGES
check 2 "fr_FR@euro" "" "" # @modifier without a charset
check 1 "C" "" "" # C, unknown and unset all mean English
check 1 "xx_YY.UTF-8" "" ""
check 1 "" "" ""
# Exported but empty is unset (POSIX), so it must not mask LANG. lang_of cannot
# express this: it exports nothing for an empty argument.
got=$(
unset LANG LC_ALL LC_MESSAGES
# shellcheck disable=SC2031 # confining the locale to this subshell is the point
{ export LANG="fr_FR.UTF-8" LC_ALL="" LC_MESSAGES=""; } 2>/dev/null
run_block
)
test "${got}" = 2 || fail "empty LC_ALL/LC_MESSAGES must not mask LANG: want 2, got ${got}"
echo "ok: LANG='fr_FR.UTF-8' with LC_ALL='' exported -> ${got}"
echo "PASS"

View File

@@ -1,90 +0,0 @@
#!/bin/bash
#
# lang/*.txt are line pairs: an English msgid, then its translation. A stray
# blank or a drifted msgid silently unhooks every translation that follows,
# rather than failing, so assert the pairing and the join against English.txt.
# Pairs are physical lines here; a msgid ending in \ continues onto the next one
# for the engine (linput_cpp) but not for us. None do today.
set -euo pipefail
langdir="${top_srcdir:-..}/lang"
def="${top_srcdir:-..}/lang.def"
eng="$langdir/English.txt"
for f in "$eng" "$def"; do
[ -f "$f" ] || {
echo "cannot find $f"
exit 1
}
done
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
keys="$tmp/english.keys"
# Byte-wise: each file is in its own declared legacy charset, lang.def is CRLF.
LC_ALL=C awk 'NR%2==1 { sub(/\r$/, ""); print }' "$eng" >"$keys"
# An empty parse would make every check below pass vacuously.
nkeys=$(wc -l <"$keys")
if [ "$nkeys" -lt 400 ]; then
echo "only $nkeys msgids parsed from $eng; the parse is broken"
exit 1
fi
nfiles=0
fail=0
for f in "$langdir"/*.txt; do
nfiles=$((nfiles + 1))
LC_ALL=C awk -v keys="$keys" -v name="${f##*/}" '
BEGIN { while ((getline k < keys) > 0) known[k] = 1 }
{ sub(/\r$/, "") }
NR % 2 == 1 {
if ($0 == "")
printf "%s:%d: blank line where an English msgid belongs\n", name, NR
else if (!($0 in known))
printf "%s:%d: msgid absent from English.txt: %s\n", name, NR, $0
else
next
bad++
}
END {
if (NR % 2) {
printf "%s: odd line count (%d): msgid/translation pairing is broken\n", name, NR
bad++
}
exit(bad > 0)
}
' "$f" || fail=1
done
if [ "$nfiles" -lt 25 ]; then
echo "only $nfiles language files found in $langdir"
exit 1
fi
# lang.def maps each LANG_* macro to the English string the GUI compiles in,
# which is the lookup key into every lang/*.txt: no msgid, no translations.
LC_ALL=C awk -v keys="$keys" '
BEGIN { while ((getline k < keys) > 0) known[k] = 1 }
{ sub(/\r$/, "") }
NR % 2 == 1 { macro = $0; next }
macro ~ /^(LANG_|LISTDEF_)/ {
n++
if (!($0 in known)) {
printf "lang.def:%d: %s maps to \"%s\", which is not a msgid in English.txt\n", NR, macro, $0
bad++
}
}
END {
if (n < 400) {
printf "lang.def: only %d LANG_ entries parsed; the parse is broken\n", n
bad++
}
exit(bad > 0)
}
' "$def" || fail=1
[ "$fail" -eq 0 ] || exit 1
echo "$nfiles language files consistent with $nkeys English msgids"

View File

@@ -137,6 +137,6 @@ TESTS = \
56_local-proxy-noleak.test \
57_local-proxy-connect.test \
58_watchdog.test \
62_lang-integrity.test
61_webhttrack-locale.test
CLEANFILES = check-network_sh.cache