inlang

This page shows the concrete JSON shapes used by bundles, messages, variants, declarations, selectors, matches, and patterns.

Use these shapes when inserting data through project.db, insertBundleNested(), or a plugin's importFiles() return value.

Minimal Message

const messageId = crypto.randomUUID();
const variantId = crypto.randomUUID();

await insertBundleNested(project.db, {
  id: "greeting",
  declarations: [],
  messages: [
    {
      id: messageId,
      bundleId: "greeting",
      locale: "en",
      selectors: [],
      variants: [
        {
          id: variantId,
          messageId,
          matches: [],
          pattern: [{ type: "text", value: "Hello world!" }],
        },
      ],
    },
  ],
});

Pattern

A pattern is an array. It can mix text, expressions, and markup.

Text

[{ type: "text", value: "Hello world!" }];

Interpolation

Use an input-variable declaration for variables provided by the caller, then reference it with an expression.

const messageId = crypto.randomUUID();
const variantId = crypto.randomUUID();

{
  id: "greeting",
  declarations: [{ type: "input-variable", name: "name" }],
  messages: [
    {
      id: messageId,
      bundleId: "greeting",
      locale: "en",
      selectors: [],
      variants: [
        {
          id: variantId,
          messageId,
          matches: [],
          pattern: [
            { type: "text", value: "Hello " },
            {
              type: "expression",
              arg: { type: "variable-reference", name: "name" },
            },
            { type: "text", value: "!" },
          ],
        },
      ],
    },
  ],
}

Expression With Annotation

Annotations describe formatting functions. Plugins decide which annotations they can import or export.

{
  type: "expression",
  arg: { type: "variable-reference", name: "count" },
  annotation: {
    type: "function-reference",
    name: "number",
    options: [],
  },
}

Options can use literals or variable references:

{
  type: "function-reference",
  name: "number",
  options: [
    {
      name: "style",
      value: { type: "literal", value: "currency" },
    },
    {
      name: "currency",
      value: { type: "variable-reference", name: "currency" },
    },
  ],
}

Markup

Markup is represented as pattern parts. This example corresponds to Click <link>here</link><icon/>.

[
  { type: "text", value: "Click " },
  { type: "markup-start", name: "link" },
  { type: "text", value: "here" },
  { type: "markup-end", name: "link" },
  { type: "markup-standalone", name: "icon" },
];

Markup can include options and attributes:

{
  type: "markup-start",
  name: "link",
  options: [
    {
      name: "href",
      value: { type: "literal", value: "/pricing" },
    },
  ],
  attributes: [
    {
      name: "external",
      value: true,
    },
  ],
}

Selectors And Matches

Selectors choose which variables a message uses to pick a variant. Matches on each variant must refer to selector names.

Literal Match

{
  type: "literal-match",
  key: "platform",
  value: "ios",
}

Catch-All Match

{
  type: "catchall-match",
  key: "platform",
}

Plural-Style Selector

Use an input variable for the caller-provided value, a local variable for the derived selector value, and variants that match the local variable.

const messageId = crypto.randomUUID();
const oneVariantId = crypto.randomUUID();
const otherVariantId = crypto.randomUUID();

{
  id: "items_count",
  declarations: [
    { type: "input-variable", name: "count" },
    {
      type: "local-variable",
      name: "countPlural",
      value: {
        type: "expression",
        arg: { type: "variable-reference", name: "count" },
        annotation: {
          type: "function-reference",
          name: "plural",
          options: [],
        },
      },
    },
  ],
  messages: [
    {
      id: messageId,
      bundleId: "items_count",
      locale: "en",
      selectors: [{ type: "variable-reference", name: "countPlural" }],
      variants: [
        {
          id: oneVariantId,
          messageId,
          matches: [{ type: "literal-match", key: "countPlural", value: "one" }],
          pattern: [{ type: "text", value: "One item" }],
        },
        {
          id: otherVariantId,
          messageId,
          matches: [
            { type: "literal-match", key: "countPlural", value: "other" },
          ],
          pattern: [
            {
              type: "expression",
              arg: { type: "variable-reference", name: "count" },
            },
            { type: "text", value: " items" },
          ],
        },
      ],
    },
  ],
}

Variant Linkage: CRUD Versus Plugin Import

Use messageId when you already have a concrete message row id. This is the normal shape for direct CRUD writes and insertBundleNested().

const messageId = crypto.randomUUID();

{
  id: "variant_1",
  messageId,
  matches: [],
  pattern: [{ type: "text", value: "Hello" }],
}

Use messageBundleId and messageLocale when returning variants from a plugin's importFiles(). In that flow, message ids are often omitted so the SDK can generate or reuse them while importing.

{
  messageBundleId: "greeting",
  messageLocale: "en",
  matches: [],
  pattern: [{ type: "text", value: "Hello" }],
}

The SDK resolves messageBundleId plus messageLocale to the matching message and generates ids when needed.

Rule of thumb:

  • Direct database writes: use messageId.
  • insertBundleNested(): use messageId, and reuse the same id from the message object.
  • Plugin importFiles(): use messageBundleId plus messageLocale, unless your plugin deliberately manages stable message ids itself.

Next Steps