mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-15 11:44:10 +00:00
## Summary Defense-in-depth fix for the `Invalid linked format` crash: escape vue-i18n message-syntax characters at node-def generation so the locale bundle is always valid, **and** harden the runtime so any compile error (custom nodes, already-shipped bundles) degrades to the raw message instead of crashing the app. This folds in the runtime guard from #13603 (@xmarre) — see credit below — so the two layers land together. ## Changes **Layer 1 — generation-time escaping (source data)** - Add `escapeVueI18nMessageSyntax()` to `packages/shared-frontend-utils/src/formatUtil.ts`. It escapes every character vue-i18n's message compiler treats as syntax in text — `@ { } | %` (verified against `@intlify/message-compiler`'s `readText` tokenizer): `@` and unbalanced `{`/`}` throw, `|` mis-renders as plural branches, `%` matters immediately before `{`. Each becomes a literal interpolation `{'x'}` (the only escape vue-i18n supports); the set isn't exported by the library so it's hardcoded with source/doc references. Single-pass, applied once. - Apply it in `scripts/collect-i18n-node-defs.ts` to the fields **compiled** via `t()`/`st()`: `display_name`, `description`, input/output `name`, widget labels, `dataTypes`, `nodeCategories`. Tooltip fields go through `tm()`/`stRaw()` (no compile step) and are intentionally left unescaped. - Regression test (`src/locales/escapeNodeDefI18n.test.ts`) compiles the escaped output for all five characters through real vue-i18n and asserts it round-trips verbatim. **Layer 2 — runtime fallback (consumer), folded in from #13603 by @xmarre** - `st()` now wraps `t(key)` in try/catch: a vue-i18n `SyntaxError` falls back to the raw locale message (`tm()`) instead of crashing bootstrap. Shared `rawTranslationOrFallback` helper reused by `stRaw()`. Tests in `src/i18n.safeTranslation.test.ts` (isolated per-test, and covering the non-`SyntaxError` rethrow branch). **Tooling** - Lint the collection scripts instead of ignoring them: import the shared helper via the `@/utils/formatUtil` tsconfig alias (fixes `import-x/no-relative-packages`; verified it resolves under Playwright) and drop a stray progress `console.log`. No lint config changes and no rules relaxed — the scripts are now genuinely linted (they were previously excluded). - **Breaking**: none. ## Review Focus - **Root cause**: values read via `t()`/`st()` are compiled by vue-i18n, which parses `@ { } | %` as message syntax; a malformed `@` throws `Invalid linked format`. `app.ts` translates every node description at startup, so one bad string aborts router load. Surfaced by the 1.47.7 locale sync (#13246), whose `ByteDanceSeedAudio` description contains `@Audio1-3`. - **The two layers compose**: escaped output compiles cleanly, so the runtime catch never fires for generated strings; the catch is the safety net for strings that bypass generation (custom/third-party nodes via `mergeCustomNodesI18n`, and locale bundles already shipped in 1.47.x/1.48.x). - **Compiled vs. raw boundary**: escaping is applied only to compiler-consumed fields; leaving tooltips raw matches the `stRaw()`/`tm()` work in #12469. - **Translation propagation**: lobe-i18n's config prompt already keeps `{...}` placeholders intact, so the escaped English propagates to all 13 locales. - **Follow-up (out of scope)**: `scripts/collect-i18n-general.ts` (commands/settings/menus) shares the same generation-time hazard and could reuse the same helper later; Layer 2 already covers it at runtime. ## Credit The runtime fallback (Layer 2) is @xmarre's work from #13603, folded in here with authorship preserved on the original commits. Closing #13603 in favor of this combined PR. Fixes #13545 Fixes #13575 --------- Co-authored-by: Connor Byrne <c.byrne@comfy.org> Co-authored-by: xmarre <54859656+xmarre@users.noreply.github.com>