Skip to content

First Working Translation In 5 Minutes

This guide is the shortest path to the good Palamedes feeling: write a message, extract it, translate it, and see it render without changing the mental model.

  • one translated component
  • one extraction run
  • one .po import
  • one active runtime instance

It uses Vite plus React because that is the smallest copy-paste setup today. The same Vite plugin, runtime model, and .po flow now also work with Solid through @palamedes/solid and vite-plugin-solid.

1. Install the packages

pnpm add @palamedes/core @palamedes/react @palamedes/runtime @palamedes/vite-plugin
pnpm add -D @palamedes/cli @vitejs/plugin-react vite typescript

For Solid, swap the host package pair:

pnpm add @palamedes/core @palamedes/solid @palamedes/runtime @palamedes/vite-plugin
pnpm add -D @palamedes/cli vite-plugin-solid vite typescript

2. Add palamedes.yaml

locales: [en, de]
source-locale: en
catalogs:
  - path: src/locales/{locale}
    include: [src]

3. Wire the Vite plugin

// vite.config.ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import { palamedes } from "@palamedes/vite-plugin"

export default defineConfig({
  plugins: [palamedes(), react()],
})

4. Register the runtime

// src/i18n.ts
import { createI18n } from "@palamedes/core"
import { setClientI18n } from "@palamedes/runtime"

const i18n = createI18n()
setClientI18n(i18n)

export { i18n }

5. Add one translated component

// src/App.tsx
import { t } from "@palamedes/core/macro"

export function App() {
  return <h1>{t`Welcome to Palamedes`}</h1>
}

6. Extract catalogs

pnpm exec pmds extract

You should now have:

  • src/locales/en.po
  • src/locales/de.po

7. Add one translation

Open src/locales/de.po and change the translated string:

msgid "Welcome to Palamedes"
msgstr "Willkommen bei Palamedes"

8. Load .po messages

TypeScript needs an ambient declaration for .po imports. Add it once:

// src/po.d.ts
declare module "*.po" {
  import type { CatalogMessages } from "@palamedes/core"

  export const messages: CatalogMessages
}
// src/main.tsx
import React from "react"
import ReactDOM from "react-dom/client"
import { i18n } from "./i18n"
import { App } from "./App"
import { messages as enMessages } from "./locales/en.po"
import { messages as deMessages } from "./locales/de.po"

i18n.load("en", enMessages)
i18n.load("de", deMessages)
i18n.activate("de")

ReactDOM.createRoot(document.getElementById("root")!).render(<App />)

Expected Result

After pnpm dev, the page should render:

Willkommen bei Palamedes

That proves the full local loop is working:

  • macros transform correctly
  • extraction works
  • catalogs update correctly
  • .po imports compile
  • the runtime model is wired

From there, the same catalog flow can grow into CI audits, richer ICU diagnostics, and framework-specific app wiring without changing how messages are identified.