wxt-dev/wxt

Feature request: named object substitutions in i18n.t()

Open

#2332 opened on May 3, 2026

View on GitHub
 (6 comments) (0 reactions) (0 assignees)TypeScript (9,861 stars) (511 forks)user submission
contribution welcomegood first issue

Description

Feature Request

Add support for passing a plain object as the substitution argument to i18n.t(), allowing named {placeholder} syntax in message strings as an alternative to the positional $1–$9 approach.

🔥 Motivation

The current positional substitution system works well for simple cases, but becomes fragile and hard to read when a message has 2 or more dynamic values:

# en.yml
msg: My name is $1, and I create extensions using $2.
i18n.t('msg', ['Muzamil', 'WXT'])

Problems with positional substitutions at scale:

  • Order-dependent — reordering args silently breaks translations
  • No translator context$1 and $2 give translators no hint of what the values represent
  • Hard to maintain — readability degrades quickly with 3+ substitutions
  • Word order inflexibility — some languages require a different word order than English, which positional args can't support

✅ Proposed API

# en.yml
msg: My name is {name}, and I create extensions using {tool}.
i18n.t('msg', {
  name: 'Muzamil',
  tool: 'WXT',
})
// => "My name is Muzamil, and I create extensions using WXT."

✅ Expected behaviour

  • Object keys map to {key} tokens in the message string
  • Unmatched placeholders are left as-is — no silent empty strings
  • Fully backward-compatible — existing $1–$9 array substitutions are unchanged
  • Works alongside plural forms — count as second arg, object as third
  • Type-safe: TypeScript can infer required keys from the message template

✅ Reference implementation

The core logic is a single regex replace — minimal surface area, no new dependencies:

// In packages/i18n/src/index.ts
 
// Detect object arg alongside existing number/array detection
} else if (typeof arg === 'object' && !Array.isArray(arg)) {
  objectSub = arg;
}
 
// Apply after browser.i18n.getMessage resolves the message
if (objectSub) {
  message = message.replace(/\{(\w+)\}/g, (match, key) =>
    Object.prototype.hasOwnProperty.call(objectSub, key)
      ? String(objectSub[key])
      : match
  );
}

✅ Notes

Happy to open a PR with a full implementation including type definitions and tests if this direction is approved.

Contributor guide