Skip to Content
Documentation

i18n

Messages

Format strings with ICU message syntax in VitNode, including variables, pluralization, rich text tags, and select rules.

VitNode localizes strings using standard ICU MessageFormat, parsed and rendered through use-intl.

Quick start

1. Define the strings

plugins/blog/src/locales/en.json
{
  "@vitnode/blog": {
    "welcome": "Welcome back, {name}!",
    "articles_count": "{count, plural, =0 {No articles} one {1 article} other {# articles}}",
    "terms_notice": "By clicking continue, you agree to our <link>Terms of Service</link>."
  }
}

2. Render them in a component

plugins/blog/src/views/blog-header.tsx
import { useTranslations } from "use-intl"
import { HrefLink } from "@vitnode/core/tanstack/link"

export const BlogHeader = ({ count, name }: { count: number; name: string }) => {
  const t = useTranslations("@vitnode/blog")

  return (
    <header className="flex flex-col gap-2">
      <h1>{t("welcome", { name })}</h1>
      <p>{t("articles_count", { count })}</p>

      {/* Rich text with custom tags */}
      <small className="text-muted-foreground">
        {t.rich("terms_notice", {
          link: (chunks) => <HrefLink className="underline" href="/terms">{chunks}</HrefLink>,
        })}
      </small>
    </header>
  )
}

ICU syntax

Variables and numbers

{
  "price": "Total: {amount, number, ::currency/USD}",
  "date": "Published on {date, date, medium}"
}

Plurals

{
  "unread": "{count, plural, =0 {No unread messages} one {1 unread message} other {# unread messages}}"
}

Select

{
  "status": "{status, select, draft {Draft} published {Published} other {Archived}}"
}

Where message files live

FolderAudiencePurpose
src/locales/{locale}.jsonFrontendBrowser UI, page text, and AdminCP navigation
src/locales/api/{locale}.jsonAPI ServerEmails and other output with no UI. API responses return i18n keys, which the frontend translates