Skip to content

feat(translate): apply vars presets from .yfm behind --presets - #2297

Open
martyanovandrey wants to merge 7 commits into
masterfrom
feat/translate-vars-presets
Open

martyanovandrey wants to merge 7 commits into
masterfrom
feat/translate-vars-presets

Conversation

@martyanovandrey

@martyanovandrey martyanovandrey commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

What

yfm translate takes variables only from --vars and ignores presets.yaml (the option says so). A project whose build selects its variables with varsPreset in .yfm has to repeat them for translation by hand, and a run without them evaluates every condition on an unknown variable as true. On YT that meant the merge of yandex-specific/toc-internal.yaml behind when: audience == "internal" was applied to the public translation (DOCSTOOLS-6814); the current workaround duplicates audience: public from presets.yaml into the cube config.

How

An opt-in switch, so regular runs do not change:

  • --presets (or presets: true in the translate section of .yfm) makes translate and seed apply presets as build does: the run loads every presets.yaml under the input (the same VarsService build uses); a file takes the varsPreset section of each presets.yaml on the path of its translation, merged with the default section; --vars overrides both;
  • the run is judged the way the build of the target language sees it: every lookup - toc items and merges, the includes that translate.filter follows, the content - takes the presets on the path of the target file, ru/x.md translated into English as en/x.md. The source presets describe the source build: their lang: ru would keep the Russian branch of {% if lang == "ru" %} in the English page (10 pages on YT), and a file selection left on them would drop an include the English page keeps. A file outside a language directory (the input is that directory itself) keeps its own path;
  • so a run with presets takes one target language: translate and seed refuse several with a config error (the neurotranslate cube calls them per language anyway);
  • without the switch nothing changes: no presets.yaml is read, conditions are evaluated exactly as before, presets.yaml or not;
  • the preset comes from --vars-preset, then the sections of the same .yfm in order - translate and the root for translate; translate.seed, translate and the root for seed - then default. The sections are read from the file itself, not from the resolved config (which already carries default from the config defaults), so a section can select default over a root preset. A .yfm without a translate section still contributes its root varsPreset;
  • seed takes the switch from the translate section when its own is silent, as with the code mode, so both commands split files the same way (the unit texts are the cache keys); both sides of an alignment take the vars the translate run gives the source file. The translate section is read from the file itself: a .yfm without a translate.seed section (YT has exactly that) resolves the seed scope to the defaults and loses its path, and the seed ignored presets and code of the translate section - the code part of it predates this PR;
  • a condition on a variable that no preset defines keeps its block, as before: translation never drops content it cannot judge;
  • the frontmatter of a file keeps the form it is written in. Liquid re-serializes it through YAML (indentation, quotes, a no-break space as \_), and with presets every file gets vars, so every translated file would get a reformatted frontmatter, and the escaped space broke the extraction of a heading that repeats the title - the seed of such a file failed (11 more unseeded files on the Tracker docs). The re-serialized frontmatter stays only when a condition in it changed a value; the units loader and the yandex provider share this;
  • translate extract and translate compose never apply presets: Run takes usePresets as an option and extract keeps it off, so the XLIFF handed to external tools keeps taking variables from --vars only. An e2e case guards it with a fixture where either preset would drop a block.

Follow-up

The neurotranslate cube copies only the translate: section of the project's .yfm into its working directory, so the root varsPreset: public of YT does not reach it. For ru -> en that no longer matters: en/presets.yaml of YT keeps audience: public and lang: en in default, and translate: {presets: true} alone gives no internal merge, the public mapreduce-debug.md and the English chat link in try-yt.md (checked on yt/docs). For en -> ru (release notes) ru/presets.yaml sets audience only in its public section, so YT needs translate: {presets: true, varsPreset: public} - without the preset the internal toc merge is applied again. Then vars: {audience: public} can go from its cube config.

@martyanovandrey
martyanovandrey requested review from separatrixxx and stenin-nikita and removed request for a team September 22, 2026 18:47
@martyanovandrey martyanovandrey changed the title feat(translate): apply vars presets from .yfm to translate and seed feat(translate): apply vars presets from .yfm behind --presets Sep 23, 2026
`yfm translate` took variables only from `--vars` and ignored
presets.yaml, so a project whose build selects its variables with
`varsPreset` in .yfm had to repeat them for translation by hand, and a
run without them evaluated every condition on an unknown variable as
true: on YT the merge of `yandex-specific/toc-internal.yaml` behind
`when: audience == "internal"` was applied to the public translation.

Presets now apply as for build. The run loads every presets.yaml under
the input; a file takes the `varsPreset` section of each presets.yaml on
its path merged with the `default` section, and `--vars` overrides them.
The preset comes from `--vars-preset`, then the command's own config
section, then the enclosing sections of the same .yfm (the root for
translate, `translate` and the root for seed), then `default`. The
presets of the source file apply to both sides of a seed alignment, so
the units stay in parity with the translate run.

`translate extract` and `translate compose` are untouched: the XLIFF for
external tools keeps taking variables from `--vars` only.

Checked on yt/docs with its `.yfm` (`varsPreset: public`) and no `--vars`:
`ru/user-guide/problems/mapreduce-debug.md` translates as the public page
(16 units), the internal merge no longer leaks; `--vars '{"audience":
"internal"}'` still selects the internal page (57 units).
…from the raw .yfm

Two review findings on the presets change:

- `extract` shares the `Run` with `translate`, so the presets started to
  reach the XLIFF for external tools: with `default: {audience: internal}`
  in presets.yaml a block behind `audience == "public"` and a toc item
  behind the same `when` vanished from the extraction. `Run` takes
  `usePresets` as an option now and `extract` turns it off; an e2e case
  extracts a fixture where either preset would drop one of the blocks and
  checks that both stay.

- The command's own `varsPreset` was read from the resolved config, where
  the config defaults already put `default`, so a section could not select
  `default` over a root preset: `varsPreset: internal` at the root with
  `translate: {varsPreset: default}` still gave `internal`. The sections
  are now read from the .yfm itself, the command's own section first, with
  a strict lookup that skips a missing section instead of falling back to
  the root out of order.
Regular translation runs must not change: presets now apply only with
`--presets` (or `presets: true` in the translate section of .yfm), so a
run without it evaluates conditions exactly as before, presets.yaml or
not. `seed` follows the translate section when its own is silent, as
with the code mode, so both commands split files the same way.

`--vars-preset` selects the preset for `--presets`; the .yfm root still
supplies the default. Extract stays off regardless.
@martyanovandrey
martyanovandrey force-pushed the feat/translate-vars-presets branch from 4e0e7e6 to bbc4723 Compare September 23, 2026 08:47
…ead the translate section in seed

Two review findings on --presets:

- Conditions took the presets of the source file, so `lang: ru` from
  ru/presets.yaml kept the Russian branch of `{% if lang == "ru" %}` and
  the English page got the link to the Russian chat (10 pages on YT).
  A file is now judged under the presets of its translation, ru/x.md as
  en/x.md, the way the build of the target language sees it. The seed
  takes the same vars on both sides, so its units stay in parity with
  the translate run.
- `translate seed` lost the path of a .yfm without a `translate.seed`
  section (the strict scope falls back to the defaults), so it ignored
  `presets: true` of the translate section, and `code` the same way:
  the seed split files differently from the translate run. YT has
  exactly such a .yfm. The seed now reads the translate section from
  the file itself.
Comment thread src/commands/translate/run.ts Outdated
varsFor(path: string, targetLanguage: string) {
const file = normalizePath(path);

return this.vars.for(file, languagePath(file, this.config.source.language, targetLanguage));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content is processed using the target language presets, while dependencies use the source language presets. When --presets and translate.filter: true are used, a file included within a {% if lang == "en" %} block is excluded from the translation—even though the include statement itself remains—resulting in a link to a missing file.

On Windows the temp dir comes as an 8.3 name (RUNNER~1). The run takes
its scope from realpathSync, which keeps the short name, and checks each
read against the async realpath, which expands it, so presets.yaml of
the project was refused as out of scope and the presets case failed.
Review finding: the content was judged under the presets of the
translation, while the files of the run - the toc items and merges and
the includes that `translate.filter` follows - were still judged under
the source ones. With `--presets` and `filter: true` a file included under
`{% if lang == "en" %}` stayed out of the translation while the English
page kept its include, a link to a missing file; a toc item under the same
condition pointed to a page that was not translated.

With presets on, the run now takes a vars service that answers every
lookup with the presets on the path of the target file, so tocs,
includes, merges and the content agree. That needs one target language
per run: `translate` and `seed` refuse several with `--presets`, the
neurotranslate cube calls them per language anyway. Providers go back to
plain per-file vars.
Liquid re-serializes the frontmatter of a document through YAML:
indentation, quotes and a no-break space written as `\_`. A translation
is composed from that text, so with --presets, where every file gets
vars, each translated file got a reformatted frontmatter; and the
escaped space broke the extraction of a heading that repeats the title,
which failed the seed of the existing translation (Tracker docs: 11 more
files unseeded with presets). `applyConditions` keeps the frontmatter as
written unless a condition in it changed a value; both the units loader
and the yandex provider use it.

An e2e case guards the parity of seed and translate under presets taken
from the translate section of .yfm, the way the neurotranslate cube runs:
every unit comes from the seed, one page seeded from a translation that
kept its conditions and one from a translation without them.
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants