Skip to content

Mixed and nested structures

While simple messages are easy to extract, mixed and nested messages are not. But wuchale handles them seamlessly. When writing them in the .po files, it uses a simple convention that is easy to work with for translators.

These are created when extracting template literals and markup message with expressions in the middle. For example,

const msg = `Hello ${userName}, welcome to ${appName}!`

And

<p>Hello {userName}, welcome to ${appName}!</p>

Are both extracted into the catalogs:

msgid "Hello {0}, welcome to {1}!"
msgstr ""

Then it gets compiled into the optimized form to require as few operations as possible to render:

export let c = [["Hello ", 0, ", welcome to ", 1, "!"]]

And the code is turned into a version that gets it by index (in this case 0) and give it the dynamic values in the same order:

const msg = _w_runtime_.t(0, [userName, appName])

And

<p>{_w_runtime_.t(0, [userName, appName])}</p>

This makes the job of the Runtime.t method very simple during runtime. No replace, no weird regex. Just concatenate in the order getting the references by index and return.

And this enables it to accomodate any change in the translation. If the translator changes the order of things, it will just follow and use the that order.

Text mixed with markup that contains other text or expressions:

<p>Welcome to <i>the app {appName}</i>, <b>{userName}</b>!</p>

This is extracted as:

msgid "Welcome to <0>the app {0}</0>, <1/>!"
msgstr ""

This example shows two behaviours wuchale has when handling nesting content.

  • When the nested content contains only text or text mixed with something else, it is extracted in numeric HTML-like tags with both opening and closing tags <0>...</0>.
  • When it doesn’t contain any text, the whole thing is extracted as a self-closing numeric tag <1/>. Because the translator doesn’t need to know what is inside the tag because it is not translatable. They can translated without being overwhelmed with unnecessary details.

Then it gets compiled into the optimized form:

export let c = [["Welcome to ", [0, "the app ", 0], ", ", [1], "!"]]

And depending on the adapter, the code is transformed into:

Uses a simple W_tx_ implementation (JSX)

<p>
<W_tx_
tags={[
_w_ctx_ => <i key="_0">{_w_runtime_.tx(_w_ctx_, [appName])}</i>,
() => <b key="_0">{userName}</b>,
]}
ctx={_w_runtime_.cx(0)}
/>
</p>

Basically, the W_tx_ does the same simple job: looping through the elements of the array and rendering them. This makes it very performant by avoiding string manipulations during runtime.

When compiling the translations, you may think, how do I escape special characters like <>{}? wuchale comes with its own message compiler that is very strict, so strict that it makes escaping unnecessary. If the message is valid in the PO syntax, it should work.

The way it works is this: it only considers valid numeric tags and numeric references as special, nothing else. And it is not tolerant to whitespace mistakes.

Example: <0>...</0> and </0>

These are the only supported syntax to signal nested content. And outside of translations, they are not valid HTML anyway. That means you can, for example, extract HTML without a problem, which may be stored in a string (extracted if the heuristic allows).

msgid "<b>the number 0</b> < <i><0>the number 1</0></i>"
msgstr ""

You would think that the above would need escaping but it doesn’t when using wuchale because it is very strict. It only considers the <0>...</0> as special and treats the rest as text.

Example: {0}

These are used to reference the expression values in interpolations. Also you will almost never use them in other contexts and therefore it recognizes them as special. For example,

msgid "You may use { and } freely and {a} is not special. {0} is, { 0 } is not"
msgstr ""

In the above, everything except {0} is just text and will not cause problems.