Skip to content

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

@oyzamil

Description

@oyzamil

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions