@ccssmnn/intl

中文文档
0.1.1发布于 2025/09/27MITTypeScriptES Module近 30 天下载 3074

Type-safe Unicode MessageFormat 2.0 utilities with runtime formatting and React bindings.

原始使用文档
文档目录23 节
  1. Features
  2. Supported MessageFormat syntax
  3. Installation
  4. Quick Start
  5. Usage
  6. MessageFormat 2.0 Examples
  7. Core API
  8. Error Handling
  9. React integration
  10. API Reference
  11. Core Functions
  12. React Integration
  13. Usage Guide
  14. Organization workflow
  15. Typical layout
  16. Feature message slice
  17. Assembling the global catalog
  18. React consumption
  19. Locale switching
  20. Server Side
  21. Workflow tips
  22. Development
  23. License

@ccssmnn/intl

A internationalization utility that does not require extraction/codegen, is super typesafe and works great with coding agents. Basically just type and react utilities built on top of the official Unicode MessageFormat 2.0 formatter.

Features

  • ✅ No code generation / message extraction: author messages and use them immediately.
  • ✅ Unicode MessageFormat 2 syntax via messageformat (the only dependency).
  • ✅ React utilities for providers, hooks, and components with markup rendering.
  • ✅ Pairs seamlessly with coding agents—messages due to familiar syntax and friendly type errors.
  • ✅ Works equally well on the server e.g. notifications and emails.

Supported MessageFormat syntax

Runtime support: The library delegates parsing and formatting to messageformat@4 for full MessageFormat 2.0 support.

Type-level support: The TypeScript types currently validate a focused subset of the specification:

✅ Parameters:

  • {$param} - String parameters
  • {$param :number} - Number parameters
  • {$param :datetime} - Date/DateTime parameters
  • {$param :date} - Date parameters
  • {$param :time} - Time parameters

✅ Markup:

  • {#tag}content{/tag} - Simple markup spans (no attributes)
  • Nested markup tags
  • Type-safe component mapping in React

✅ Selection/Pluralization:

  • .input {$var} .match $var patterns
  • one/* plural rules
  • Custom selection branches

❌ Not type-validated (but work at runtime):

  • Parameter options like style=currency
  • Markup tag attributes
  • Complex function calls
  • Other MessageFormat 2.0 features

The type system focuses on the most common i18n patterns while letting you use advanced features when needed.

Installation

pnpm add @ccssmnn/intl

Peer Dependencies:

  • React 17+ (only needed if using React integration)

Requirements:

  • Node.js 16+ or modern browser with ES2020 support

Quick Start

import { messages, createIntl } from "@ccssmnn/intl"

// 1. Define your messages
const copy = messages({
    greeting: "Hello {$name}!",
    count: "You have {$num :number} items",
})

// 2. Create translator
const t = createIntl(copy, "en")

// 3. Use it
t("greeting", { name: "World" }) // "Hello World!"
t("count", { num: 42 }) // "You have 42 items"

Usage

MessageFormat 2.0 Examples

Common patterns with type validation:

const messages = messages({
    // Basic interpolation
    greeting: "Hello {$name}!",

    // Typed parameters (type-validated)
    price: "Price: {$amount :number}",
    date: "Today is {$today :datetime}",
    time: "Meeting at {$when :time}",

    // Pluralization
    items:
        ".input {$count :number} .match $count one {{one item}} * {{{$count} items}}",

    // Selection
    status:
        ".input {$role} .match $role admin {{Welcome admin}} user {{Hello user}} * {{Hi there}}",

    // Markup tags
    welcome:
        "Welcome {#bold}{$name}{/bold}! Click {#link}here{/link} to continue",

    // Complex nested structure
    cart: `.input {$count :number} {$hasDiscount}
         .match $count $hasDiscount
         0 true {{Your cart is empty, but you have a discount!}}
         0 * {{Your cart is empty}}
         one true {{You have one item with discount applied}}
         one * {{You have one item}}
         * true {{You have {$count} items with discount applied}}
         * * {{You have {$count} items}}`,
})

Core API

import { messages, createIntl, translate } from "@ccssmnn/intl"

// annotates `base` with the messages types
let base = messages({
    greeting: "Hello {$name}!",
    count: "You have {$num :number} items",
})

// guarantees on a type level that `german` has the same message signature as `base`
let german = translate(base, {
    greeting: "Hallo {$name}!",
    count: "Du hast {$num :number} Elemente",
})

// create typesafe message consumption utility!
let t = createIntl(german, "de")

t("greeting", { name: "Carl" })
// → "Hallo Carl!"

All helpers are strongly typed. For example, t refuses calls without the name parameter, and translate enforces that translations keep the same params/markup as the base copy.

Error Handling

The library provides graceful error handling for malformed messages:

const problematic = messages({
    valid: "Hello {$name}!",
    broken: "Invalid syntax {{{", // Malformed message
})

const t = createIntl(problematic, "en")

t("valid", { name: "Carl" }) // "Hello Carl!"
t("broken") // "❌: broken" (fallback with key name)
// Console shows table with error details

When MessageFormat compilation fails, the library:

  1. Logs detailed error information to console
  2. Creates fallback messages showing the problematic key
  3. Continues execution without throwing

React integration

import { createIntlForReact, messages } from "@ccssmnn/intl"

const copy = messages({
    signIn: "Hey {$name}! {#link}Sign in here{/link}",
})

const { IntlProvider, useIntl, T } = createIntlForReact(copy, "en")

function Toolbar() {
    const t = useIntl()
    return <p>{t("signIn", { name: "Carl" })}</p>
}

function Entry() {
    return (
        <IntlProvider>
            <Toolbar />
            <T
                k="signIn"
                params={{ name: "Carl" }}
                components={{ link: ({ children }) => <a href="/login">{children}</a> }}
            />
        </IntlProvider>
    )
}

The hook exposes the same typed t function as the core API, so TypeScript will flag missing params/markup right in React components. The <T> component uses formatToParts to let you supply React components for markup tags.

API Reference

Core Functions

  • messages(obj) - Create typed message catalog from object literal
  • createIntl(messages, locale) - Create translation function for rendering strings
  • createIntlToParts(messages, locale) - Create formatter for manual rendering (useful for building UI framework adapters)
  • translate(base, translation) - Create type-safe translation that preserves structure
  • merge(...catalogs) - Combine multiple message catalogs (prevents key conflicts)
  • check(base, ...parts) - Verify translation coverage and merge parts

React Integration

  • createIntlForReact(messages, locale) - Returns { IntlProvider, useIntl, T, useLocale }
  • IntlProvider - Context provider for messages and locale
  • useIntl() - Hook returning translation function t(key, params?, components?)
  • T - Component for rendering messages with markup: <T k="key" params={{}} components={{}} />
  • useLocale() - Hook returning current locale string

Usage Guide

Organization workflow

  • Organize message catalogues per feature (e.g. ~/intl/messages.todos.ts) to keep related messages colocated and make them easier to work with.
  • Assemble the global catalogue in ~/intl/messages.ts by merging feature slices with merge(...) and validating each locale against the base via check(...).
  • Instantiate React bindings once in shared/intl/setup.ts using createIntlForReact(messagesEn, "en"); export the resulting IntlProvider, useIntl, T, and useLocale.
  • Reuse the framework-agnostic helpers (createIntl, createIntlToParts) for server tasks such as localized push notifications.

Benefits of this approach:

  • Smaller, focused message modules are easier to navigate and modify
  • Related messages stay close to the features that use them
  • Works better with coding agents that can understand and modify specific feature contexts
  • Prevents merge conflicts when multiple features add messages simultaneously

Typical layout

shared/
  intl/
    setup.ts            # configured React exports
    messages.ts         # merges base + locale slices
    messages.todos.ts   # per-feature catalogue (base + translate)
    messages.*.ts       # additional slices
server/
  notifications/
    send-push.ts        # imports createIntl() for localized payloads

Feature message slice

// ~/intl/messages.todos.ts
import { messages, translate } from "@ccssmnn/intl"

export { baseTodoMessages, deTodoMessages }

let baseTodoMessages = messages({
    "todos.header": "Tasks for {$name}",
    "todos.remaining": "You have {#strong}{$count :number}{/strong} remaining",
})

let deTodoMessages = translate(baseTodoMessages, {
    "todos.header": "Aufgaben für {$name}",
    "todos.remaining": "Du hast noch {#strong}{$count :number}{/strong} übrig",
})

Assembling the global catalog

// ~/intl/messages.ts
import { merge, check } from "@ccssmnn/intl"
import { baseTodoMessages, deTodoMessages } from "./messages.todos"
// import other feature slices...

export { messagesEn, messagesDe }

let messagesEn = merge(
    baseTodoMessages
    /* other base slices */
)

let messagesDe = check(
    messagesEn,
    deTodoMessages
    /* other locale slices */
)

React consumption

// ~/intl/setup.ts
import { createIntlForReact } from "@ccssmnn/intl/react"
import { messagesEn } from "./messages"

// create the provider with the default messages and locale
export const { IntlProvider, useIntl, T, useLocale } = createIntlForReact(
    messagesEn,
    "en"
)

// ui/components/todo-header.tsx
import { useIntl, T } from "~/intl/setup"

export function TodoHeader({
    username,
    remaining,
}: {
    username: string
    remaining: number
}) {
    const t = useIntl()

    return (
        <header>
            <h1>{t("todos.header", { name: username })}</h1>
            <T
                k="todos.remaining"
                params={{ count: remaining }}
                components={{
                    strong({ children }) {
                        return <strong>{children}</strong>
                    },
                }}
            />
        </header>
    )
}

Locale switching

// app/root.tsx
import { IntlProvider } from "~/intl/setup"
import { messagesEn, messagesDe } from "~/intl/messages"

const catalogs = {
    en: messagesEn,
    de: messagesDe,
}

export function App({ locale }: { locale: keyof typeof catalogs }) {
    return (
        <IntlProvider messages={catalogs[locale]} locale={locale}>
            {/* ... */}
        </IntlProvider>
    )
}

Server Side

// server/notifications/send-push.ts
import { createIntl } from "@ccssmnn/intl"
import { messagesEn, messagesDe } from "~/intl/messages"

const catalogs = {
    en: messagesEn,
    de: messagesDe,
}

export function buildTodoNotification(
    locale: keyof typeof catalogs,
    taskTitle: string,
    dueAt: Date
) {
    const t = createIntl(catalogs[locale], locale)
    return {
        title: t("notifications.todo.title", { title: taskTitle }),
        body: t("notifications.todo.body", { dueAt }),
    }
}

Workflow tips

  • Add new message keys to the base slice first; compilation fails until translations match, keeping locales synchronised.
  • Use <T> only when markup tags are needed; useIntl() suffices for plain strings.
  • Run pnpm test (with type-checking enabled) after modifying catalogues to catch ICU or markup mismatches immediately.
  • When extracting into a shared library, export the same primitives (messages, merge, translate, check, createIntl, createIntlForReact) so both client and server consumers retain the ergonomics.

Development

  • pnpm install
  • pnpm test – run the Vitest suites (core and React).
  • pnpm build – emit ESM + d.ts output into dist/.

License

MIT

版本与发布记录

@ccssmnn/intl 共发布 2 个版本,首次发布于 2025/09/27;过去 12 个月发布了 0 个版本。

版本发布日期距今
0.1.1latest2025/09/271.1 年前
0.1.02025/09/271.1 年前

关于 @ccssmnn/intl 的常见问题

@ccssmnn/intl 是什么?

Type-safe Unicode MessageFormat 2.0 utilities with runtime formatting and React bindings.

如何安装 @ccssmnn/intl?

在项目目录运行 npm install @ccssmnn/intl,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^17.0.0 || ^18.0.0 || ^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@ccssmnn/intl 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@ccssmnn/intl 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@ccssmnn/intl 可以商用吗?

@ccssmnn/intl 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@ccssmnn/intl 的下载量有多少?

根据 npm 官方下载统计,@ccssmnn/intl 在 2026/10/10 – 2026/11/08 这 30 天内被下载 3,074 次,日均约 102 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@ccssmnn/intl 还在维护吗?

最新版本 0.1.1 发布于 2025/09/27(1.1 年前);至今共发布 2 个版本,其中过去 12 个月发布了 0 个;首次发布于 2025/09/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。