From 70d4d0e94274e638362d53ae088763812d215a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:12:39 +0200 Subject: [PATCH 1/2] fix: keep custom language keys in the resolved translations translationsForLang rebuilt the language table from an allow-list, and formatLanguage ran it after merging the user's language object over the defaults, so a key Quarto does not ship was accepted by the schema, merged, and then discarded before templates saw it. Reading it back through $quarto.language.$ gave an empty string with no warning. Copy non-object values through as well. Locale variations are objects and stay out, so the variation loop below keeps merging them itself. Closes #14772 --- news/changelog-1.11.md | 6 +++ src/core/language.ts | 5 ++- .../docs/smoke-all/2026/08/13/issue-14772.qmd | 41 +++++++++++++++++++ .../docs/smoke-all/2026/08/13/typst-show.typ | 10 +++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/docs/smoke-all/2026/08/13/issue-14772.qmd create mode 100644 tests/docs/smoke-all/2026/08/13/typst-show.typ diff --git a/news/changelog-1.11.md b/news/changelog-1.11.md index 35f6ee3a280..5fef2b15b79 100644 --- a/news/changelog-1.11.md +++ b/news/changelog-1.11.md @@ -1,5 +1,11 @@ All changes included in 1.11: +## Formats + +### All Formats + +- ([#14772](https://github.com/quarto-dev/quarto-cli/issues/14772)): Custom `language` keys now reach templates through `$quarto.language.$`. A key that Quarto does not ship passed schema validation and was merged over the defaults, then discarded before templates saw it, so it expanded to an empty string with no warning. + ## Engines ### `knitr` diff --git a/src/core/language.ts b/src/core/language.ts index 52f3d39294f..09b76a62289 100644 --- a/src/core/language.ts +++ b/src/core/language.ts @@ -163,10 +163,13 @@ export function translationsForLang(language: FormatLanguage, lang: string) { let translations = {} as FormatLanguage; Object.keys(language).forEach((key) => { // crossrefs can be custom, so be more lenient + // any other non-object value is a user or extension defined string, + // objects are locale variations and are merged below instead if ( kLanguageDefaultsKeys.includes(key) || key.match(/^crossref-.*-title$/) || - key.match(/^crossref-.*-prefix$/) + key.match(/^crossref-.*-prefix$/) || + typeof language[key] !== "object" ) { translations[key] = language[key]; } diff --git a/tests/docs/smoke-all/2026/08/13/issue-14772.qmd b/tests/docs/smoke-all/2026/08/13/issue-14772.qmd new file mode 100644 index 00000000000..f9a88d2c6cc --- /dev/null +++ b/tests/docs/smoke-all/2026/08/13/issue-14772.qmd @@ -0,0 +1,41 @@ +--- +title: "Custom `language` keys reach the template namespace (#14772)" +format: + typst: + template-partials: + - typst-show.typ + keep-typ: true +language: + toc-title-document: "Overridden built-in" + custom-key: "Custom value" + en: + variation-key: "Variation value" +_quarto: + tests: + typst: + ensureTypstFileRegexMatches: + - + - 'built-in overridden: `Overridden built-in`' + - 'built-in untouched: `Abstract`' + - 'custom key: `Custom value`' + - 'variation key: `Variation value`' + - + - 'custom key: ``' + - 'variation key: ``' + noErrors: true +--- + +Regression test for #14772. `translationsForLang` rebuilt the language table +from an allow-list, and `formatLanguage` ran it after merging the user's +`language` object over the defaults, so a key Quarto does not ship was +accepted by the schema, merged, and then discarded before templates saw it. +`$quarto.language.custom-key$` expanded to an empty string with no warning. + +The four keys read back by `typst-show.typ` isolate the parts that must keep +working alongside the fix: an override of a built-in key still wins over the +default, an untouched built-in key still resolves to its default, and a +locale-variation sub-object still merges through the variation loop rather +than leaking as an object, which is what the non-object test in the filter +protects. Only the custom key regressed. + +The end. diff --git a/tests/docs/smoke-all/2026/08/13/typst-show.typ b/tests/docs/smoke-all/2026/08/13/typst-show.typ new file mode 100644 index 00000000000..8e8eb08c242 --- /dev/null +++ b/tests/docs/smoke-all/2026/08/13/typst-show.typ @@ -0,0 +1,10 @@ +#show: doc => { + [built-in overridden: `$quarto.language.toc-title-document$`] + linebreak() + [built-in untouched: `$quarto.language.section-title-abstract$`] + linebreak() + [custom key: `$quarto.language.custom-key$`] + linebreak() + [variation key: `$quarto.language.variation-key$`] + doc +} From 5e4fae4154003532bbf44f3165ea7f6ce453b5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:26:43 +0200 Subject: [PATCH 2/2] docs: add author suffix to changelog entry --- news/changelog-1.11.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/changelog-1.11.md b/news/changelog-1.11.md index 5fef2b15b79..33d99e70494 100644 --- a/news/changelog-1.11.md +++ b/news/changelog-1.11.md @@ -4,7 +4,7 @@ All changes included in 1.11: ### All Formats -- ([#14772](https://github.com/quarto-dev/quarto-cli/issues/14772)): Custom `language` keys now reach templates through `$quarto.language.$`. A key that Quarto does not ship passed schema validation and was merged over the defaults, then discarded before templates saw it, so it expanded to an empty string with no warning. +- ([#14772](https://github.com/quarto-dev/quarto-cli/issues/14772)): Custom `language` keys now reach templates through `$quarto.language.$`. A key that Quarto does not ship passed schema validation and was merged over the defaults, then discarded before templates saw it, so it expanded to an empty string with no warning. (author: @mcanouil) ## Engines