Inlang messages + JSON Language Packs

SkillCommunication

Helps your agent add and edit translated interface texts using the Inlang message format.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Inlang messages + JSON Language Packs skill

About this skill

Define ZotLit UI messages in the Inlang Message Format and consume them through the generated JSON Language Pack facade. Use when editing messages/*.json, adding interpolation or variants, changing locale resolution, or working with m.* calls.

What this skill tells your AI

The instructions your AI receives, as published by aidenlx/zotlit in .agents/skills/inlang-i18n/SKILL.md and read by ahel’s review.

Use messages/{locale}.json as the source of truth. The Obsidian build compiles those sources into a typed message facade and bundled English Language Pack; the runtime interprets pack data synchronously.

Pair this skill with /i18n-ui-text when writing English UI copy.

Project layout

  • project.inlang/settings.json — Inlang project configuration, base locale, and locale list.
  • messages/{locale}.json — shared message sources.
  • apps/obsidian/scripts/generate-language-packs.ts — compiler entry point.
  • apps/obsidian/src/lib/i18n/generated/messages.ts — generated typed facade.
  • apps/obsidian/src/lib/i18n/generated/en.json — bundled English pack.
  • apps/obsidian/src/lib/i18n/runtime.ts — eval-free interpreter.
  • apps/obsidian/src/lib/i18n.ts — Obsidian Locale Alias resolution and startup.

Generated files are gitignored. Run:

pnpm --filter @zotlit/obsidian generate:language-packs

Turbo runs generation before typecheck and tests. Vite regenerates at build start and watches the message sources, project settings, and generator.

Adding or changing a message

  1. Add or edit the key in messages/en.json. Use snake_case; keys prefixed docs_ belong only to the docs site and are excluded from plugin artifacts.
  2. Regenerate the facade when running package tools directly.
  3. Import the facade and keep named-input call shapes:
import * as m from "@/lib/i18n/generated/messages";

m.welcome_view_name();
m.annot_view_filter_count({ shown: 3, total: 8 });

Input-free messages are functions with no arguments. Input-bearing messages take one object whose required properties come from input declarations.

Inlang Message Format

Simple messages

{
  "hello_world": "Hello world",
  "greeting": "Good morning {name}!"
}

Variables use {curlyBraces}. Escape literal braces with \\{ and \\} in JSON.

Variants

Wrap structured messages in an array:

{
  "item_count": [
    {
      "declarations": ["input count", "local category = count: plural"],
      "selectors": ["category"],
      "match": {
        "category=one": "There is one item.",
        "category=*": "There are {count} items."
      }
    }
  ]
}

Declarations run in source order:

  • input name declares a facade input.
  • local result = source: formatter derives a local value.
  • Formatter options are key=value; $inputName references another input.

Matching is evaluated in source order. Use literal matches where needed and * as the catch-all.

Supported formatters

FormatterRuntime
pluralIntl.PluralRules
numberIntl.NumberFormat
datetimeIntl.DateTimeFormat

Examples:

{
  "price": [
    {
      "declarations": [
        "input amount",
        "input currency",
        "local formatted = amount: number style=currency currency=$currency"
      ],
      "match": { "formatted=*": "Price: {formatted}" }
    }
  ],
  "event_date": [
    {
      "declarations": [
        "input date",
        "local formatted = date: datetime dateStyle=long timeZone=UTC"
      ],
      "match": { "formatted=*": "Date: {formatted}" }
    }
  ]
}

Use an explicit timeZone when output must be stable across environments.

Markup, unknown declarations, unknown expressions, and unknown formatters are rejected by the compiler.

Locale behavior

apps/obsidian/src/lib/i18n.ts resolves Obsidian language codes through the Locale Alias table. Canonical locale names follow Inlang (zh-CN); unmatched codes resolve to English. Startup always installs bundled English first. Downloaded Language Packs are pure JSON data and use the same generated facade.

Signals

GitHub stars
1k
Forks
63
Last commit
Sep 2026
Advanced
Item type
skill
Key
inlang-i18n
Source
github.com/aidenlx/zotlit