wuchale (pronounced /wʊtʃɑleɪ/ or wuh-'CHAH-lay) is a compile-time internationalization
toolkit that requires no code changes. Write your components naturally, and
wuchale automatically extracts and replaces translatable messages at build
time.
- No extra syntax! - Your normal code is enough, your codebase stays clean
- Tiny catalogs to bundle - Text catalogs are just arrays, no keys necessary, like Protobuf
- Zero-effort integration - Add i18n to existing projects without rewriting code
- Framework agnostic - Works with React, Preact, Svelte(Kit), SolidJS, Astro, and plain JS/TS
- Full URLs support - E.g.
/aboutto/de/uber-uns - LLM translation support - Configurable integration for automatic on-the-fly translations
- Full, granular HMR - Live updates during development, including LLM translation
- Tiny footprint - Very few (less than 5) additional dependencies, no bloated
node_modules - Smart extraction - Uses AST analysis to handle nested markup, conditionals, loops, and complex interpolations
- Storage agnostic - Standard PO files by default, storage interface to store translations anywhere
With traditional i18n:
<p>{t('Hello')}</p>
<p><Trans>Welcome {userName}</Trans></p>With wuchale:
<p>Hello</p>
<p>Welcome {userName}</p>No imports, no special components, no annotations. wuchale handles everything
at compile time by analyzing your code and automatically extracting
translatable strings.
See the Getting Started guide for instructions specific to your project type.
- Scans your source code using AST and identify translatable text content
- Extracts strings into standard
.potranslation files (or custom storage) for translators - Compiles catalogs into compact modules which export arrays
- Replaces strings with translation function calls that access messages by indices from the arrays
Your original code stays clean and readable, while the build output is automatically internationalized.
Let's say you have:
// src/components/Welcome.jsx
function Welcome({ user }) {
return (
<div>
<p><b>Hello, {user}</b>, and welcome!</p>
<button>Get started</button>
</div>
)
}The messages are extracted into a .po file. for Spanish for example, after translation, it looks like:
#~ src/components/Welcome.jsx
msgid "<0>Hello, {0}</0>, and welcome!"
msgstr "<0>Hola, {0}</0>, y ¡bienvenido!"
#~ src/components/Welcome.jsx
msgid "Get started"
msgstr "Comenzar"Then they are compiled into a compact form optimized for loading (just an array):
export let c = [[[0, "Hola, ", 0], ", y ¡bienvenido!"], "Comenzar"]And your code is transformed into a version that accesses them by index:
// src/components/Welcome.jsx
import W_tx_ from "@wuchale/jsx/runtime.jsx"
function Welcome({ user }) {
const _w_runtime_ = _w_load_rx_();
return (
<div>
<p><W_tx_ t={[_w_ctx_ => <b key="_0"><W_tx_ x={_w_ctx_} n a={[user]} /></b>]} x={_w_runtime_.c(0)} /></p>
<button>{_w_runtime_(1)}</button>
</div>
)
}Notice the automatic handling of the nested <b>, that's wuchale's full
supported for indefinite nesting at play. You can nest as deep as you want, and
it still makes it work for i18n. That gives the translator freedom to even
reorder placeholders and it gets rendered accordingly.
Check out full working examples for different setups at
wuchalejs/examples to see
wuchale in action with different frameworks.
This is a monorepo that houses these packages:
| Package | Description | Latest |
|---|---|---|
wuchale |
Core + CLI + Vanilla adapter | |
@wuchale/jsx |
JSX adapter (for React and SolidJS) | |
@wuchale/svelte |
Svelte adapter | |
@wuchale/astro |
Astro adapter | |
@wuchale/json |
JSON storage |
Contributions are welcome! Please check out the test suites located inside each package for examples of supported scenarios.
Note: The tests are also written in TypeScript and therefore you should have Node 22+ to run them.
This project is supported by the community. Become a sponsor and get your name or logo listed on the README and the website!
Sponsors:
Special thanks to our supporters:
And one private donor 🙏.