Skip to content

Plural forms with interpolation are extracted as separate messages instead of one plural entry #406

Description

@Stadly

Summary

When a plural() candidate form contains an interpolation (i.e. it's a template literal, not a plain string literal), wuchale does not treat the array as a plural. Instead, each form is extracted as its own standalone singular message. The resulting .po has no msgid_plural, so the strings are neither pluralized nor grouped.

Version

wuchale 0.25.x (reproduced on 0.25.5), @wuchale/svelte, SvelteKit loader.

Reproduction

<script>
  let name = 'Alice';
  let count = 3;
</script>

{plural(count, [`${name}'s # file`, `${name}'s # files`])}

The intent: # is the plural count, and ${name} is an ordinary (string) argument interpolated into every form — e.g. "Alice's 1 file" / "Alice's 3 files".

Expected .po

A single plural entry carrying the interpolation placeholder:

#. 0: name
msgid "{0}'s # file"
msgid_plural "{0}'s # files"
msgstr[0] "{0}'s # file"
msgstr[1] "{0}'s # files"

Actual .po

Two independent singular messages, no msgid_plural:

#. 0: name
msgid "{0}'s # file"
msgstr "{0}'s # file"

#. 0: name
msgid "{0}'s # files"
msgstr "{0}'s # files"

By contrast, the same call with plain-string forms (plural(count, ["# file", "# files"])) is extracted correctly as one plural entry, so the trigger is specifically the interpolation in the form.

Likely origin

In the vanilla adapter transformer, the array-candidate branch of the plural() handler requires every element to be a string Literal and bails out to the default call-expression handling the moment it encounters a TemplateLiteral:

// wuchale/dist/adapter-vanilla/transformer.js (~L338)
const candidates = [];
for (const elm of argVal.elements) {
    if (!elm || elm.type !== 'Literal' || typeof elm.value !== 'string') {
        return this.defaultVisitCallExpression(node); // <-- bails on TemplateLiteral
    }
    candidates.push(elm.value);
}

Once it falls back to the default handler, each template-literal form is visited individually and emitted as its own message, which is exactly the observed output.

Why this seems supportable (not a fundamental limitation)

  • PO format: gettext plural messages fully support positional placeholders alongside the plural count, so msgid "{0}'s # file" / msgid_plural "{0}'s # files" is valid.
  • Compiled representation: wuchale already encodes both interpolation (composite arrays with numeric arg indices, e.g. ["Add to ", 0, "."]) and plural forms (arrays of strings, e.g. ["# file", "# files"]). A plural-with-interpolation is just the nesting of the two — an array of composite arrays — and the runtime's mixedToString already expands composite arrays against args.

The main work appears to be (1) accepting TemplateLiteral elements in the plural candidate loop and extracting their static+interpolation parts the same way single-message template literals are handled, and (2) threading the extra args through the plural runtime helper so the selected form gets its placeholders expanded (in addition to #).

Scope note

This example deliberately uses a string argument (name) so the only number involved is the plural count itself, keeping the case unambiguous. (A form that also interpolates a second number — e.g. "3 of 5 files" — raises a separate question about which value drives pluralization; that's out of scope here.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions