Message formatting

18ways uses waysParser by default. That parser is what lets you keep the source copy readable while still handling variables and locale-aware formatting.

Variables

Use braces for variables and pass values through vars.

ts
const label = await engine.t(
  'Welcome back, {name}',
  {
    vars: { name: 'Ada' },
  }
);

Pluralization

Use ICU-style plural blocks when the sentence genuinely changes shape.

ts
const summary = await engine.t(
  '{count, plural, =0{No messages} =1{One message} other{{count} messages}}',
  {
    vars: { count: 3 },
  }
);

Dates and money

If you pass a real Date or a money-like object with amount and currency, 18ways will format it automatically even with a bare placeholder like {publishedAt} or {renewalTotal}.

Use the explicit formatter only when you want to control the output, such as dateStyle:long or a custom money divisor.

ts
const publishedAt = new Date(
  '2026-03-14T09:00:00Z'
);
const renewalTotal = {
  amount: 1900,
  currency: 'USD',
};
 
const updatedLabel = await engine.t(
  'Last updated {publishedAt}',
  {
    vars: { publishedAt },
  }
);
 
const totalLabel = await engine.t(
  'Renewal total {renewalTotal}',
  {
    vars: { renewalTotal },
  }
);
 
const detailedDate = await engine.t(
  'Detailed date: {publishedAt, date, dateStyle:long}',
  {
    vars: { publishedAt },
  }
);

By default, money amounts are treated as minor units. If you already have major units, use the explicit money formatter with divisor: 1.

Select blocks

Use select when the copy depends on a small set of discrete values.

ts
const banner = await engine.t(
  'Sign in to continue',
  {
    vars: {
      isSignedIn: true,
      name: 'Ada',
    },
  }
);

Component composition

@18ways/core returns strings. If you need rich text, keep the sentence whole and handle markup in your own renderer.

ts
const cta = await engine.t(
  'Click here to see more'
);

If your app needs automatic JSX composition, move up to @18ways/react or @18ways/next.

Practical rule

If the sentence can be written as a complete phrase with a few variables, keep it in one message. That makes translation quality dramatically better than stitching fragments together in code.