Library / SDK
opral/inlang avatar
opral/inlang

inlang: a translation model that Git and CI already own

Open format TMS (translation management system)

2,007 stars179 forksTypeScriptLicense varies

At a glance

What is it?
inlang moves translations out of a hosted TMS database and into the repository, then wires an editor, a CLI and any custom tool to one shared project model through @inlang/sdk. The trade is explicit: versioning and review come from Git, while translation memory, glossaries and vendor workflows stay outside the format.
Who is it for?
inlang fits teams that already keep locale JSON under version control and want CI to fail fast when a project setup breaks, and it fits tool builders who would rather share one model than write a parser per app. It does not fit shops that need a translation memory, glossaries or a vendor approval flow, since the format carries none of those.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The repository is the message store, not a TMS database

Locale files stay in the same Git repository as the application code. Beside the source tree sits a directory named `project.inlang`, and a configured plugin is what reads and writes the message files. Editors, agents and automation then work against one shared localization model, so a hosted TMS never has to be the source of truth. The README compresses the whole workflow into one line: change the source text, update translations, run CI, review the pull request, ship. Because Git tracks both the translation files and the project settings, a wording change in a German string and a refactor in a component meet the same branch rules and the same reviewer. What that project directory describes is narrow and explicit: messages, variants, locales and settings. Those four things are the contract every plugin parses and every tool reads, and nothing in the format records where a translation came from or who signed it off.

One SDK layer under Fink, Sherlock, CLI and Paraglide

The stack in the README runs top to bottom: named tools, then the inlang SDK, then a project directory, then plugins, then the translation files in Git. Four tool names sit in that top row, Fink, Sherlock, CLI and Paraglide, plus a slot for your own tool. What the layer buys a tool author is a shared project model, so an editor does not need its own parser for every file format an app happens to use. The npm download badge at the top of the README points at `@inlang/sdk`, which is the published entry point for that layer, and the tool catalogue at inlang.com/c/apps is where ready made tools are listed instead of one blessed editor. Not one of those four names comes with a feature list. The README treats them as labels in a diagram, so what Fink, Sherlock and Paraglide actually do has to be read off the catalogue site, and the CLI is the only tool in the repository with documented behaviour.

Plugins bind the project model to JSON and i18next resources

A plugin is the only piece that knows your file format. JSON and i18next resources are the two formats named, and everything above them works on the abstract model instead. Configuration lives in `settings.json` inside the project directory, and the top level `package.json` carries a `test:plugin-packages` script pointing at `scripts/test-plugin-packages.mjs`, which shows that plugin packages get a dedicated test pass inside the monorepo. Here is the layout the README puts next to an app:

text
my-app/
├── src/
├── messages/
│   ├── en.json
│   └── de.json
└── project.inlang/
    └── settings.json

A close read turns up a naming wobble inside that design. The diagram calls the layer a `.inlang project`, the tree names the directory `project.inlang`, and both commands below take `--project ./project.inlang`. The README never says which of the two names is canonical, so anyone scripting the path has to read the file layout instead of the prose.

Two npx commands cover the whole documented workflow

The README documents no install step and no scaffolding command. Its Get started list is four links: one to the documentation, one to the CLI page, one to the SDK package and one to the tool catalogue. What you can copy and paste is short. First, the check that belongs in CI:

bash
npx @inlang/cli validate --project ./project.inlang

That loads the project at `./project.inlang` and reports whether the setup works. Both commands assume that directory already exists, so the first real task for a new user is producing it, and the documentation link rather than the repository is where that gets explained. Then, optionally, draft translations from a provider you have configured:

bash
npx @inlang/cli machine translate --project ./project.inlang

Provider setup lives in the CLI documentation, not in the repository. Output from that command is draft text written into files Git already tracks, which is exactly why the documented next step is a pull request rather than a deploy.

The monorepo sets Node 22 and pnpm 10 floors

Contributors clone a private workspace named `monorepo`, not the single package an app would install. Its packageManager field pins pnpm 10.23.0, engines asks for node 22 and pnpm 10 or newer but below 11, and `pnpm-workspace.yaml` plus `nx.json` coordinate the packages under `packages/`. Build, test, lint and format each fan out through nx run-many in parallel, and the ci script chains three of them:

json
"ci": "pnpm build && pnpm lint && pnpm test"

The devDependencies behind that are vitest 3.1.1 with @vitest/coverage-v8 3.1.1, nx 21.0.0 with nx-cloud 19.1.0, and @changesets/cli 2.29.7 next to a `.changeset/` directory at the root. A change can therefore cross several packages and land in one coordinated publish, which is convenient for maintainers and invisible to consumers who pull `@inlang/sdk` or `@inlang/cli` from npm. The last push was on 2026-10-06, and because no GitHub releases exist, version pinning happens through the npm packages rather than through Git tags.

Validate checks project setup, not translation quality

The CI command is a setup check, and the README is explicit about its limits: `validate` checks project setup, and it does not check translation completeness or placeholder quality. A green run tells you the project loads, the settings parse and the plugin chain resolves. It cannot tell you that a key exists in `en.json` but not in `de.json`, that a placeholder was dropped during a machine translation round trip, or that a locale drifted out of the source list. Those are the defects a translation pipeline gets bought for, and none of them turns the command above red. Machine translation makes that gap concrete, because a provider writes drafts straight into tracked files, generated translations need review before shipping, and the README gives no threshold, no report format and no exit code for review findings. Read a passing `validate` as proof that the plumbing works, not as proof that the product is translated.

Where the Git-native workflow wins against a hosted TMS

The trade is where the bytes live. A hosted TMS as source of truth keeps translations in its own database, and translation memory, glossaries, reviewer permissions and vendor billing all hang off that database. inlang puts the files in Git instead, which is what the README means by giving editors, agents and automation a shared model without making a hosted TMS the source of truth. None of those four database side features is named anywhere in the repository, so none of them can be judged from the code. What takes their place is ordinary Git: a branch, a CI job, a review, and the README's own sequence of change source text, update translations, run CI, review the pull request, ship. For teams with outside language vendors, the vendor then works through the same repository and the same review queue as the engineers, and no workflow for approving or billing a vendor is documented. The row of logos under "Used by teams at" names Kraft Heinz, Bose, Disney, ETH Zurich, Brave, Michelin and idealista, which are claims of use rather than descriptions of the tooling.

Root files the README leaves unexplained

Several entries at the repository root carry no explanation in the README. `.infisical.json` sits beside `.prettierignore`, `.gitattributes`, `.vscode/`, `bin/` and `assets/`, and a reader cannot tell from the repository what the Infisical file configures or whether it matters outside the maintainers' own setup. `rfcs/`, `blog/` and `docs/` at least signal where design arguments and user documentation are meant to live, which makes this a documentation-first repository rather than a code-only one. The licence sits in the same category of unverified detail: GitHub's file listing for the repository root shows no LICENSE file, and the licence field for the project comes back unknown, so anyone shipping inlang output inside a product has to resolve the terms from the published package pages first. No server component is documented either, and neither the ci script nor the two `npx` commands hints at a self hosted deployment, which matches the pull request workflow the format is built around.

Editorial conclusion

inlang fits teams that already keep locale JSON under version control and want CI to fail fast when a project setup breaks, and it fits tool builders who would rather share one model than write a parser per app. It does not fit shops that need a translation memory, glossaries or a vendor approval flow, since the format carries none of those. Before adopting, resolve the terms from the published package pages, confirm a plugin exists for your file format, and read the provider setup in the CLI docs before running `machine translate`.

Frequently asked questions

How do I set up an inlang project in an existing app?

The README ships no scaffolding command. Its Get started list points to inlang.com/documentation for setup, and the target shape is the tree it shows: a `project.inlang/` directory holding `settings.json` next to your `messages/` folder, with a plugin reading and writing the files.

What does npx @inlang/cli validate actually check?

It checks that the project loads from the path passed to `--project`. The README states that it does not check translation completeness or placeholder quality, so a green CI run says nothing about missing keys or dropped placeholders.

Do I need a hosted TMS account to use inlang?

No. Translation files stay in your Git repository and a shared project model serves editors, agents and automation, so no hosted TMS has to be the source of truth. Machine translated drafts do need a configured provider, and provider setup lives in the CLI documentation.

Can I build my own localization tool on top of inlang?

Yes, through the `@inlang/sdk` package on npm, which exposes the project model so tools do not each write their own parser. The README stack puts the SDK between tools and the project directory, with Fink, Sherlock, CLI and Paraglide named as consumers of it.

What Node and pnpm versions are needed to work on inlang itself?

The root package.json sets `engines` to node `>=22` and pnpm `>=10 <11`, and pins `packageManager` to `[email protected]`. Those floors apply to the monorepo, while app users invoke the CLI through `npx` against a project directory.

Official sources

  1. Issues
  2. opral/inlang on GitHub
  3. Project website
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/opral-inlang.svg)](https://hysenlabs.com/projects/opral-inlang)

Was this useful?