From addc7548aeba69e769f836b1304450b835410408 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 12 Oct 2026 14:49:59 +0200 Subject: [PATCH 1/5] Install hyphen-* packages when missing patterns cause a babel error (#515) With some babel options (e.g. nswissgerman from `lang: de-CH`), missing hyphenation patterns trigger the fatal error "You haven't defined the language 'ngerman' yet" instead of a warning, so check_extra() never ran and parse_packages() found nothing to install. Detect this error in parse_packages() and map the language to its hyphen-* package. The mapping from language names to hyphen-* packages is now shared by check_babel(), check_polyglossia() and the new detect_hyphen(), and also covers pattern names that differ from package names in texlive.tlpdb (e.g. ngerman/swissgerman -> hyphen-german). --- NEWS.md | 1 + R/latex.R | 48 +++++++++++++++++++++++++++++------- tests/test-cran/test-latex.R | 23 +++++++++++++++++ 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0456592f3..31f509de9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # CHANGES IN tinytex VERSION 0.62 +- `latexmk()` can automatically install missing `hyphen-*` packages when the missing hyphenation patterns lead to the babel error `You haven't defined the language 'xxx' yet` instead of a warning, e.g., for the babel option `nswissgerman` (from `lang: de-CH` in R Markdown or Quarto), which needs the patterns of `ngerman` from **hyphen-german** (thanks, @stibu81, #515). # CHANGES IN tinytex VERSION 0.61 diff --git a/R/latex.R b/R/latex.R index cb43264d0..083fdea16 100644 --- a/R/latex.R +++ b/R/latex.R @@ -426,11 +426,39 @@ check_extra = function(file) { check_babel = function(text) { r = "^(\\(babel\\).* |Package babel Warning: No hyphenation patterns were preloaded for the )language [`']([^']+)'.*$" - if (length(m <- grep_sub(r, 'hyphen-\\2', text)) == 0) return(FALSE) + if (length(m <- grep_sub(r, '\\2', text)) == 0) return(FALSE) + tlmgr_install(hyphen_pkgs(m)) == 0 +} + +# map language names in babel/polyglossia messages to TeX Live hyphen-* packages +hyphen_pkgs = function(x) { + x = tolower(gsub("[`']", '', x)) # (babel) the language `German (new orthography)' into the format - m = gsub('\\s.*', '', m) - m[m == 'hyphen-pinyin'] = 'hyphen-chinese' - tlmgr_install(tolower(m)) == 0 + x = gsub('\\s.*', '', x) + # hyphenation pattern names (in language.dat) that differ from the package + # names, according to `execute AddHyphen` in texlive.tlpdb + m = c( + ngerman = 'german', swissgerman = 'german', pinyin = 'chinese', + ukenglish = 'english', british = 'english', usenglishmax = 'english', + bokmal = 'norwegian', norsk = 'norwegian', nynorsk = 'norwegian', + schoolfinnish = 'finnish', monogreek = 'greek', ibycus = 'ancientgreek', + classiclatin = 'latin', liturgicallatin = 'latin', + mongolianlmc = 'mongolian', serbianc = 'serbian' + ) + i = x %in% names(m) + x[i] = m[x[i]] + unique(paste0('hyphen-', x)) +} + +# missing hyphenation patterns may also lead to babel errors (instead of +# warnings), e.g., with the babel option nswissgerman (#515): +# ! Package babel Error: You haven't defined the language 'ngerman' yet. +# (babel) Perhaps you misspelled it or your installation +# (babel) is not complete. +detect_hyphen = function(text) { + r = ".*Package babel Error: You haven't defined the language [`']([^'`]+)' yet.*" + if (length(m <- grep_sub(r, '\\1', text)) == 0) return(character()) + hyphen_pkgs(m) } # Package glossaries Warning: No language module detected for `english'. @@ -448,9 +476,7 @@ check_glossaries = function(text) { check_polyglossia = function(text) { r = "^Package polyglossia Warning: No hyphenation patterns were loaded for ([`'][^']+'|British English).*" if (length(m <- grep_sub(r, '\\1', text)) == 0) return(FALSE) - m[m == 'British English'] = 'english' - m = gsub("[`']", '', m) - tlmgr_install(paste0('hyphen-', m)) == 0 + tlmgr_install(hyphen_pkgs(m)) == 0 } # Package datetime2 Warning: Date-Time Language Module `english' not installed on @@ -505,9 +531,13 @@ exist_files = function(files) { parse_packages = function( log, text = read_lines(log), files = detect_files(text), quiet = rep(FALSE, 3) ) { - pkgs = character(); quiet = rep_len(quiet, length.out = 3) + quiet = rep_len(quiet, length.out = 3) + # hyphen-* packages are inferred from language names instead of file names + # (`text` may be unavailable, e.g., parse_packages(files = 'bibtex')) + pkgs = if (missing(log) && missing(text)) character() else detect_hyphen(text) x = unique(c(files, miss_font())) if (length(x) == 0) { + if (length(pkgs)) return(pkgs) if (!quiet[1]) message( 'I was unable to find any missing LaTeX packages from the error log', if (missing(log)) '.' else c(' ', log, '.') @@ -520,7 +550,7 @@ parse_packages = function( # Known packages where tlmgr search returns results but no file path ends # with the exact package name (e.g., no file ends with "/latex-lab"). # These packages are returned directly without further tlmgr search parsing. - if (x[j] %in% c('fandol', 'latex-lab')) return(x[j]) + if (x[j] %in% c('fandol', 'latex-lab')) return(unique(c(pkgs, x[j]))) # why $? e.g. searching for mf returns a list like this # metafont.x86_64-darwin: # bin/x86_64-darwin/mf <- what we want diff --git a/tests/test-cran/test-latex.R b/tests/test-cran/test-latex.R index e7f30c8df..3047bd74a 100644 --- a/tests/test-cran/test-latex.R +++ b/tests/test-cran/test-latex.R @@ -36,6 +36,29 @@ assert('detect_files() can detect filenames from LaTeX log', { (detect_files("(pdf backend): cannot open file for embedding") %==% 'colorprofiles.sty') }) +assert('hyphen_pkgs() maps babel/polyglossia language names to hyphen-* packages', { + (hyphen_pkgs('french') %==% 'hyphen-french') + (hyphen_pkgs("`hungarian'") %==% 'hyphen-hungarian') + (hyphen_pkgs('German (new orthography)') %==% 'hyphen-german') + (hyphen_pkgs('British English') %==% 'hyphen-english') + (hyphen_pkgs('pinyin') %==% 'hyphen-chinese') + (hyphen_pkgs(c('ngerman', 'swissgerman', 'german')) %==% 'hyphen-german') + (hyphen_pkgs('nynorsk') %==% 'hyphen-norwegian') +}) + +assert('missing hyphenation patterns are detected from babel errors (#515)', { + x = c( + "! Package babel Error: You haven't defined the language 'ngerman' yet.", + "(babel) Perhaps you misspelled it or your installation", + "(babel) is not complete." + ) + (detect_hyphen(x) %==% 'hyphen-german') + (detect_hyphen("! Package babel Error: You haven't defined the language `french' yet.") %==% 'hyphen-french') + (detect_hyphen("! Package babel Error: Unknown option 'ngerman'.") %==% character()) + # the error is not about a missing file, so no tlmgr search is needed + (length(detect_files(x)) == 0) + (parse_packages(text = x, quiet = rep(TRUE, 3)) %==% 'hyphen-german') +}) assert('rerun are correctly detected', { (needs_rerun(text = "Package biblatex Warning: Please rerun LaTeX.")) From 1038ac7ac14a4b817d641d27da2f9524f2ef3f94 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 12 Oct 2026 12:51:59 +0200 Subject: [PATCH 2/5] Only keep valid package names inferred from babel/polyglossia messages Language names parsed from a LaTeX log end up as package names passed to tlmgr via system2(), which uses a shell on Unix. hyphen_pkgs() now only returns names matching ^hyphen-[a-z0-9-]+$, which covers every hyphenation name in language.dat and texlive.tlpdb (checked against the full CTAN tlpdb). check_babel() and check_polyglossia() no longer call tlmgr_install() when nothing is left, since tlmgr_install(character()) returns 0 and would trigger a pointless rerun. --- R/latex.R | 10 +++++++--- tests/test-cran/test-latex.R | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/R/latex.R b/R/latex.R index 083fdea16..eb5fd92cb 100644 --- a/R/latex.R +++ b/R/latex.R @@ -427,7 +427,7 @@ check_extra = function(file) { check_babel = function(text) { r = "^(\\(babel\\).* |Package babel Warning: No hyphenation patterns were preloaded for the )language [`']([^']+)'.*$" if (length(m <- grep_sub(r, '\\2', text)) == 0) return(FALSE) - tlmgr_install(hyphen_pkgs(m)) == 0 + length(pkgs <- hyphen_pkgs(m)) > 0 && tlmgr_install(pkgs) == 0 } # map language names in babel/polyglossia messages to TeX Live hyphen-* packages @@ -447,7 +447,11 @@ hyphen_pkgs = function(x) { ) i = x %in% names(m) x[i] = m[x[i]] - unique(paste0('hyphen-', x)) + x = unique(paste0('hyphen-', x)) + # names come from a log file, so only keep what looks like a package name + # (all names in language.dat/texlive.tlpdb are lowercase letters, digits, and + # hyphens), since package names are passed to tlmgr via system2() + x[grepl('^hyphen-[a-z0-9-]+$', x)] } # missing hyphenation patterns may also lead to babel errors (instead of @@ -476,7 +480,7 @@ check_glossaries = function(text) { check_polyglossia = function(text) { r = "^Package polyglossia Warning: No hyphenation patterns were loaded for ([`'][^']+'|British English).*" if (length(m <- grep_sub(r, '\\1', text)) == 0) return(FALSE) - tlmgr_install(hyphen_pkgs(m)) == 0 + length(pkgs <- hyphen_pkgs(m)) > 0 && tlmgr_install(pkgs) == 0 } # Package datetime2 Warning: Date-Time Language Module `english' not installed on diff --git a/tests/test-cran/test-latex.R b/tests/test-cran/test-latex.R index 3047bd74a..79e181173 100644 --- a/tests/test-cran/test-latex.R +++ b/tests/test-cran/test-latex.R @@ -44,6 +44,24 @@ assert('hyphen_pkgs() maps babel/polyglossia language names to hyphen-* packages (hyphen_pkgs('pinyin') %==% 'hyphen-chinese') (hyphen_pkgs(c('ngerman', 'swissgerman', 'german')) %==% 'hyphen-german') (hyphen_pkgs('nynorsk') %==% 'hyphen-norwegian') + # names in language.dat may be in mixed case + (hyphen_pkgs('UKenglish') %==% 'hyphen-english') +}) + +assert('hyphen_pkgs() drops names that are not valid package names', { + # names come from the log, and they must not be interpreted by the shell + (hyphen_pkgs('ngerman;id') %==% character()) + (hyphen_pkgs('$(id)') %==% character()) + (hyphen_pkgs('a|b') %==% character()) + (hyphen_pkgs('a&b') %==% character()) + (hyphen_pkgs('--repository=x') %==% character()) + (hyphen_pkgs(character()) %==% character()) + # valid names are kept + (hyphen_pkgs(c('french', 'x;id', 'swissgerman')) %==% c('hyphen-french', 'hyphen-german')) + # nothing to install, so tlmgr must not be called (and no rerun is needed) + (detect_hyphen("! Package babel Error: You haven't defined the language 'x;id' yet.") %==% character()) + (!check_babel("Package babel Warning: No hyphenation patterns were preloaded for the language `x;id'.")) + (!check_polyglossia("Package polyglossia Warning: No hyphenation patterns were loaded for `x;id'")) }) assert('missing hyphenation patterns are detected from babel errors (#515)', { From dd739feec2e2fa2b47469fa48ca4e1fa570a58f0 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 12 Oct 2026 13:08:15 +0200 Subject: [PATCH 3/5] Map Indic language names to hyphen-indic assamese, bengali, gujarati, hindi, kannada, malayalam, marathi, oriya, pali, panjabi, tamil, and telugu are all defined by the hyphen-indic package, so e.g. hindi used to lead to the nonexistent hyphen-hindi. --- R/latex.R | 6 +++++- tests/test-cran/test-latex.R | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/R/latex.R b/R/latex.R index eb5fd92cb..f529632ef 100644 --- a/R/latex.R +++ b/R/latex.R @@ -443,7 +443,11 @@ hyphen_pkgs = function(x) { bokmal = 'norwegian', norsk = 'norwegian', nynorsk = 'norwegian', schoolfinnish = 'finnish', monogreek = 'greek', ibycus = 'ancientgreek', classiclatin = 'latin', liturgicallatin = 'latin', - mongolianlmc = 'mongolian', serbianc = 'serbian' + mongolianlmc = 'mongolian', serbianc = 'serbian', + # all of these are in the hyphen-indic package + assamese = 'indic', bengali = 'indic', gujarati = 'indic', hindi = 'indic', + kannada = 'indic', malayalam = 'indic', marathi = 'indic', oriya = 'indic', + pali = 'indic', panjabi = 'indic', tamil = 'indic', telugu = 'indic' ) i = x %in% names(m) x[i] = m[x[i]] diff --git a/tests/test-cran/test-latex.R b/tests/test-cran/test-latex.R index 79e181173..eab876691 100644 --- a/tests/test-cran/test-latex.R +++ b/tests/test-cran/test-latex.R @@ -44,6 +44,12 @@ assert('hyphen_pkgs() maps babel/polyglossia language names to hyphen-* packages (hyphen_pkgs('pinyin') %==% 'hyphen-chinese') (hyphen_pkgs(c('ngerman', 'swissgerman', 'german')) %==% 'hyphen-german') (hyphen_pkgs('nynorsk') %==% 'hyphen-norwegian') + # all Indic languages are in a single package + (hyphen_pkgs(c('hindi', 'tamil', 'bengali', 'Panjabi')) %==% 'hyphen-indic') + (hyphen_pkgs(c( + 'assamese', 'bengali', 'gujarati', 'hindi', 'kannada', 'malayalam', + 'marathi', 'oriya', 'pali', 'panjabi', 'tamil', 'telugu' + )) %==% 'hyphen-indic') # names in language.dat may be in mixed case (hyphen_pkgs('UKenglish') %==% 'hyphen-english') }) From d33c079d8db785ef6c17f7a8a32bd34179df35db Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 12 Oct 2026 13:09:51 +0200 Subject: [PATCH 4/5] Map older hyphenation language names to their packages amharic, geez, persian, patois, francais, polygreek, portuges, slovene, and espanol are synonyms in texlive.tlpdb for languages in the hyphen-ethiopic, -farsi, -french, -greek, -portuguese, -slovenian, and -spanish packages, so they used to lead to nonexistent packages. --- R/latex.R | 4 ++++ tests/test-cran/test-latex.R | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/R/latex.R b/R/latex.R index f529632ef..6e89b3bb7 100644 --- a/R/latex.R +++ b/R/latex.R @@ -444,6 +444,10 @@ hyphen_pkgs = function(x) { schoolfinnish = 'finnish', monogreek = 'greek', ibycus = 'ancientgreek', classiclatin = 'latin', liturgicallatin = 'latin', mongolianlmc = 'mongolian', serbianc = 'serbian', + # older names (synonyms in texlive.tlpdb) + amharic = 'ethiopic', geez = 'ethiopic', persian = 'farsi', + patois = 'french', francais = 'french', polygreek = 'greek', + portuges = 'portuguese', slovene = 'slovenian', espanol = 'spanish', # all of these are in the hyphen-indic package assamese = 'indic', bengali = 'indic', gujarati = 'indic', hindi = 'indic', kannada = 'indic', malayalam = 'indic', marathi = 'indic', oriya = 'indic', diff --git a/tests/test-cran/test-latex.R b/tests/test-cran/test-latex.R index eab876691..c60836732 100644 --- a/tests/test-cran/test-latex.R +++ b/tests/test-cran/test-latex.R @@ -50,6 +50,14 @@ assert('hyphen_pkgs() maps babel/polyglossia language names to hyphen-* packages 'assamese', 'bengali', 'gujarati', 'hindi', 'kannada', 'malayalam', 'marathi', 'oriya', 'pali', 'panjabi', 'tamil', 'telugu' )) %==% 'hyphen-indic') + # older names that are synonyms of other languages + (hyphen_pkgs(c('amharic', 'geez')) %==% 'hyphen-ethiopic') + (hyphen_pkgs('persian') %==% 'hyphen-farsi') + (hyphen_pkgs(c('patois', 'francais', 'french')) %==% 'hyphen-french') + (hyphen_pkgs(c('polygreek', 'monogreek', 'greek')) %==% 'hyphen-greek') + (hyphen_pkgs('portuges') %==% 'hyphen-portuguese') + (hyphen_pkgs('slovene') %==% 'hyphen-slovenian') + (hyphen_pkgs('espanol') %==% 'hyphen-spanish') # names in language.dat may be in mixed case (hyphen_pkgs('UKenglish') %==% 'hyphen-english') }) From 0997537856f24912bc4c90964d5e99c29c4eef11 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Mon, 12 Oct 2026 13:40:13 +0200 Subject: [PATCH 5/5] Update NEWS for hyphen package name validation and extra mappings --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 31f509de9..55a372605 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # CHANGES IN tinytex VERSION 0.62 -- `latexmk()` can automatically install missing `hyphen-*` packages when the missing hyphenation patterns lead to the babel error `You haven't defined the language 'xxx' yet` instead of a warning, e.g., for the babel option `nswissgerman` (from `lang: de-CH` in R Markdown or Quarto), which needs the patterns of `ngerman` from **hyphen-german** (thanks, @stibu81, #515). +- `latexmk()` can automatically install missing `hyphen-*` packages when the missing hyphenation patterns lead to the babel error `You haven't defined the language 'xxx' yet` instead of a warning, e.g., for the babel option `nswissgerman` (from `lang: de-CH` in R Markdown or Quarto), which needs the patterns of `ngerman` from **hyphen-german** (thanks, @stibu81, #515). Language names found in the log are now only used if they look like valid package names, and more of them are mapped to the package that provides them, e.g., `hindi` and the other Indic languages to **hyphen-indic**, and `slovene` to **hyphen-slovenian**. # CHANGES IN tinytex VERSION 0.61