For Teams deciding whether hand-authored, co-located dictionaries are worth the repeated key and declaration work for each new message.
Write the dictionary, or write the sentence.
Intlayer and Palamedes agree on something most of this field does not: the central JSON namespace was a mistake. Intlayer's answer is to declare a dictionary file beside each component. Ours is to read the string out of the component itself. That single difference decides how much work each new message costs you, and who has to name it.
Licence
Apache-2.0
Identity
Explicit dictionary keys
Catalog layout
Co-located declarations
ICU
Selectable, not default
Intlayer figures: intlayer 9.0.1, researched July 2026. Projects move; re-check before you decide.
Choose the model that leaves you with less recurring work.
For the audience above, we recommend Palamedes. The alternative is not a disclaimer: if the right column describes your constraint, choose Intlayer and do not think twice about it.
Pick Palamedes when…
·You would rather write a sentence than a sentence plus a dictionary entry plus a key.
·ICU semantics should be the default and guaranteed end to end, not a configuration value.
·Your translators want .po files per locale, not a TypeScript file with every language inside it.
·You want catalog audits, ICU diagnostics and a benchmark you can re-run yourself.
Pick Intlayer when…
·You need Vue, Angular, Svelte, Lit or React Native. Their adapter matrix is genuinely wider than ours.
·You want no extraction step in your build under any circumstances.
·Co-located, hand-written dictionaries match how your team already thinks about component ownership.
·You need first-party bindings beyond React and Solid.
02 — Daily work
What changes after the choice is made.
These are workflow consequences, not a feature inventory: fewer conventions to maintain, one catalog boundary to review, and one runtime model to carry through the supported hosts. Each point maps back to an inspectable artifact.
A new message is one edit, not two
With Intlayer, shipping a string means writing the sentence in the component and adding it to a declaration file under a key. With Palamedes you write the sentence and stop — extraction finds it, catalog merging places it, and the audit tells you if anything went wrong. The scanner is not a chore we failed to remove; it is the thing that keeps the work at one edit.
Intlayer's dictionaries are keyed, so useIntlayer('multi_lang') still depends on somebody having named that dictionary well. Palamedes derives identity from the source string plus optional context. There is no name to invent, no name to misremember, and no name to argue about in review.
Intlayer can speak ICU — format: 'icu' is one of five options, alongside its own DSL, i18next, vue-i18n and PO. But the default is the house format, so portable ICU semantics depend on a project-level configuration choice. Palamedes is ICU throughout with a checked proof that nested select and plural survive the full pipeline.
Palamedes writes gettext .po with the source string as msgid so CAT and TMS tooling can process the artifact directly. Intlayer supports PO as a format value; whether it round-trips msgctxt, plural forms and comments losslessly could not be verified, and a handover format is only worth as much as its fidelity.
The strengths and trade-offs below are sourced from the dated research for Intlayer. They explain why the right decision can genuinely be Intlayer, even when Palamedes is our recommendation for the audience above.
Competitor strength
What Intlayer earned
·No extraction step at all, which means no scanner to trust and no possibility of a string existing in code but not in the catalog. That class of bug simply cannot occur.
·Co-location genuinely answers 'where does this string live' — the dictionary sits in the folder with the component it serves, not in a tree three directories away.
·The widest first-party adapter matrix in this whole comparison: React, Vue, Angular, Svelte, Solid, Preact, Lit, Astro, Next, Nuxt, React Native and four backend frameworks, all Apache-2.0.
Trade-off
What declaring by hand costs you
·The naming layer is still there. Every dictionary carries an explicit key, so you still invent identifiers and still keep them straight — the work moved from a central JSON tree into per-component files rather than disappearing.
·Declarations are locale-inline by default: t({ en, fr, es }) puts every language into a TypeScript file developers own. That reads well at three locales and becomes a merge-conflict surface at twelve, with translator content living in source files.
·The configuration surface spans dictionary declaration, locale layout, framework adapters and selectable message formats; migrations must account for those project-level choices.
04 — Proof in code
The same string, and the work each one asks for.
Intlayer
// checkout.content.ts
import { t, type Dictionary } from "intlayer"
export default {
key: "checkout",
content: {
buy: t({ en: "Buy seats", fr: "Acheter des places" }),
},
} satisfies Dictionary
// Checkout.tsx
const content = useIntlayer("checkout")
content.buy
Palamedes
import { t } from "@palamedes/core/macro"
function buyLabel(seats) {
return t`Buy ${seats} seats`
}
Intlayer's version is two files, one invented key and one locale map that grows sideways with every language you add. Neither project makes you maintain a central namespace — but only one of them makes you maintain anything at all.
05 — Side by side
The comparison, without the adjectives.
Where a row cites a measurement, it comes from the checked benchmark report in the repository. Where nothing was measured, the row says so.
Criteria
Intlayer
Palamedes
How messages get into catalogs
You declare them in a per-component file
Extracted from your source by a native compiler
Work per new message
Sentence plus a keyed declaration entry
The sentence
Message identity
Explicit dictionary keys
The source string plus optional context
ICU MessageFormat
Selectable — default is Intlayer's own DSL
Native throughout, with a checked proof
Locale layout
Locale-inline by default, all languages in one file
One .po per locale, the format translators expect
Framework coverage
The widest here — ~19 first-party adapters
React and Solid across Next.js, TanStack Start, SolidStart, Waku, React Router and Remix v3, with examples smoke-checked in CI
Extract + update speed
Not applicable — there is nothing to extract
Checked report covers Lingui, React Intl, fbtee, i18next and General Translation
06 — The trade-off
Intlayer covers far more UI frameworks than Palamedes, and removing the scanner eliminates a category of failure — nothing can drift out of a catalog you wrote by hand. The technical tradeoff is repeated keyed dictionary work for every message versus one extraction compiler in the build. Which model fits depends on the team's authoring workflow.
07 — Evaluate
Test one-edit authoring on a representative feature
There is no documented Intlayer migration playbook yet. Keep the decision reversible by modelling one feature on a branch, reviewing the generated PO catalog and checking the source-to-runtime proof before committing to a catalog conversion.
The migration, framework, catalog, runtime and trade-off boundaries are stated here and included in the page's structured data.
01Can we evaluate Palamedes without replacing Intlayer everywhere?+−
There is no documented Intlayer migration playbook yet. Keep the decision reversible by modelling one feature on a branch, reviewing the generated PO catalog and checking the source-to-runtime proof before committing to a catalog conversion.
02Does Palamedes make sense if we use only one framework?+−
Yes, if the source-string, catalog and runtime model is the fit. Framework breadth is proof that the supported adapters share one model, not a requirement that one application use several frameworks. Check the supported host and its documented boundary before adopting.
03Who owns the catalogs?+−
Palamedes keeps source-string-first PO catalogs in the repository. Extraction, catalog updates, audits, merging and compilation are local workflow steps; it does not provide a hosted TMS or machine translation service.
04What runtime code reaches the application?+−
Transformed code reaches the active Palamedes instance through getI18n(). The exact adapter and compiled artifact depend on the supported host; inspect the framework documentation and proof rather than treating this comparison as a bundle-size claim.
05What do we give up by choosing Palamedes over Intlayer?+−
Intlayer covers far more UI frameworks than Palamedes, and removing the scanner eliminates a category of failure — nothing can drift out of a catalog you wrote by hand. The technical tradeoff is repeated keyed dictionary work for every message versus one extraction compiler in the build. Which model fits depends on the team's authoring workflow.
Make the next step match the decision you still need to make.