agalwood/Motrix · error · PluginManifestInvalid

PLUGIN_MANIFEST_INVALID

PLUGIN_MANIFEST_INVALID

Error message

manifest field "${value}" mixes literal and %key% — whole-field placeholders only

What it means

resolveOne() enforces whole-field-only placeholders: a manifest string field is either a literal, exactly '%key%' (matched by PLACEHOLDER_RE /^%([\w.-]+)%$/), or rejected if it contains a partial placeholder (matched by PARTIAL_PLACEHOLDER_RE /%[\w.-]+%/). Mixing literal text with an inline %key% (e.g. 'Hello %name%') is forbidden because the resolver only swaps whole fields, never substrings.

Source

Thrown at src/core/plugin/manifest/i18n-resolve.ts:43

      Object.assign(out, flattenLocaleDict(v as Record<string, unknown>, key))
    }
  }
  return out
}

export interface ResolveOptions {
  currentDict: ManifestLocaleDict
  fallbackDict: ManifestLocaleDict // en-US — required
}

const PLACEHOLDER_RE = /^%([\w.-]+)%$/
const PARTIAL_PLACEHOLDER_RE = /%[\w.-]+%/

function resolveOne(value: string, opts: ResolveOptions): string {
  const m = PLACEHOLDER_RE.exec(value)
  if (!m) {
    if (PARTIAL_PLACEHOLDER_RE.test(value)) {
      throw new PluginManifestInvalid(
        'plugin.manifest.i18n.mixed_placeholder',
        `manifest field "${value}" mixes literal and %key% — whole-field placeholders only`
      )
    }
    return value
  }
  const key = m[1]
  return opts.currentDict[key] ?? opts.fallbackDict[key] ?? value
}

function walkSchemaNode(
  node: JsonSchemaNode,
  opts: ResolveOptions
): JsonSchemaNode {
  const out: JsonSchemaNode = { ...node }
  if (typeof node.title === 'string') out.title = resolveOne(node.title, opts)
  if (typeof node.description === 'string') {
    out.description = resolveOne(node.description, opts)

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Move the full string into the locale dict and reference it whole: field = '%greeting%' with locale {greeting:'Welcome, User'}.
  2. If the literal must stay, remove the %...% token entirely.
  3. Use only whole-field placeholders: a field value must be exactly '%some.key%'.

Example fix

// before — manifest.json
// { "name": "Audio %level% Kit" }
// after — whole-field placeholder pointing at a locale key
// { "name": "%plugin.name%" }
// locales/en-US.json: { "plugin.name": "Audio Pro Kit" }
Defensive patterns

Strategy: validation

Validate before calling

const WHOLE = /^%([\w.-]+)%$/
const PARTIAL = /%[\w.-]+%/
function assertNoMixedPlaceholder(field:string){
  if(!WHOLE.test(field) && PARTIAL.test(field))
    throw new Error(`field "${field}" mixes literal and %key%`)
}
// run over every resolvable string field in the manifest before packaging

Type guard

const isWholeOrLiteral = (v:string): boolean => /^%([\w.-]+)%$/.test(v) || !/%[\w.-]+%/.test(v)

Try / catch

try { resolveManifestI18n(manifest, opts) }
catch(e){ if(e instanceof PluginManifestInvalid && e.validationCode==='plugin.manifest.i18n.mixed_placeholder'){ /* move the whole string into the locale dict */ } else throw e }

Prevention

When it happens

Trigger: A manifest string field like contributes.configuration.title = 'Settings for %plugin%' or name = '%vendor% Tool' — the field is not purely '%key%' but contains a %token%.

Common situations: Plugin author assumed sprintf-style interpolation; copied template strings from another i18n system; wanted 'Welcome, %user%' but the host only supports full-field replacement.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/c43bc4e07812a866. Report an issue: GitHub.