diff --git a/.changeset/clear-inlang-sdk-positioning.md b/.changeset/clear-inlang-sdk-positioning.md new file mode 100644 index 0000000000..086948c3bc --- /dev/null +++ b/.changeset/clear-inlang-sdk-positioning.md @@ -0,0 +1,5 @@ +--- +"@inlang/sdk": patch +--- + +Clarify the SDK README and generated project README positioning for `.inlang` as the canonical localization file format with version control via lix. diff --git a/README.md b/README.md index d730fc52fa..6c507a05ea 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

inlang icon inlang

- The open file format for localizing software (i18n). + The open project file format for localization (i18n).

@@ -40,24 +40,45 @@ --- -Inlang is an open project format and SDK for localization tooling. +Inlang is an open project file format for localization. -It is not a new message syntax or a SaaS translation backend. It gives editors, CLIs, IDE extensions, and runtimes a shared, queryable source of truth for localization data. +An `.inlang` project is canonically a single binary file: a SQLite database with version control via [lix](https://github.com/opral/lix). Like `.sqlite` for relational data, `.inlang` packages localization data into one file that tools can share. -You can keep using your existing translation files and message syntax. Plugins connect inlang to formats like JSON, ICU MessageFormat v1, i18next, and XLIFF. +It is not another i18n library, message syntax, translation app, or SaaS backend. It gives editors, CLIs, IDE extensions, runtimes, and coding agents one shared place to read and write localization data. + +The `@inlang/sdk` is the reference implementation for reading and writing `.inlang` projects. + +`.inlang` is the canonical project format. Plugins import and export formats like JSON, ICU MessageFormat v1, i18next, and XLIFF for compatibility with existing translation files and runtimes. Version control via lix adds file-level history, merging, and change proposals to `.inlang` projects. + +Messages, variants, and locale data live in the `.inlang` database. External translation files such as `messages/en.json` are compatibility files outside `project.inlang/`, connected through plugins. + +```text +project.inlang # canonical single binary file +``` + +For Git repositories, the binary file can be unpacked into a directory of plain files so changes can be reviewed alongside code. The packed file is the canonical format; the unpacked directory is the Git-friendly representation. + +```text +project.inlang/ +├── settings.json # locales, plugins, file patterns; kept in Git +├── .gitignore # generated; ignores everything except settings.json +├── README.md # generated; explains this folder to coding agents +├── .meta.json # generated SDK metadata +└── cache/ # plugin cache, created when plugins are loaded +``` ## The problem -Common translation files like JSON, YAML, ICU, or XLIFF are good at serializing messages. But they are not databases. +Common translation files like JSON, YAML, ICU, or XLIFF are good at storing messages. But they do not describe the whole localization project. -Once multiple tools need to read and write the same project, missing database semantics become the bottleneck: +Once multiple tools need to read and write the same project, plain translation files start to miss important information: -- Structured CRUD operations instead of ad-hoc parsing -- Queries across locales, variants, and metadata -- Transactions, history, merging, and collaboration -- One source of truth that editors, CI, and runtimes can all share +- CRUD operations instead of custom parsing +- Search and reports across locales, variants, and metadata +- Version control via [lix](https://github.com/opral/lix) +- One shared file that editors, CI, and runtimes can all use -Without a common substrate, every tool invents its own format, sync, and collaboration model. +Without one shared format, every tool invents its own file structure, sync logic, and collaboration workflow. Even basic import/export for translation file formats gets duplicated across tools instead of being shared. @@ -78,12 +99,18 @@ Every tool has its own format, its own sync, its own collaboration layer. Cross- ## The solution -Inlang adds those database semantics in a shared project format while keeping your external file formats. It provides: +Inlang puts the missing project information into a shared file format while keeping your external translation files. It provides: -- A message-first data model and SDK for structured reads and writes -- Queryable storage for translations, settings, and edits +- A message-first structure and SDK for CRUD operations +- Storage that tools can search, update, and report on - Plugins to import/export formats like JSON, ICU1, i18next, and XLIFF so that file-format support can be shared instead of reimplemented in every tool -- Versioning and collaboration primitives via [lix](https://github.com/opral/lix) +- Version control via [lix](https://github.com/opral/lix) + +Core data model: + +- **Bundle** — one translatable unit across locales +- **Message** — locale-specific translation for a bundle +- **Variant** — text pattern plus selector matches ``` ┌──────────┐ ┌───────────┐ ┌────────────┐ @@ -102,29 +129,57 @@ The result: - Switch tools without migrations — they all use the same file - Cross-team work without hand-offs — developers, translators, and designers all edit the same source -- Automation just works — one source of truth, no glue code +- Automation just works — the same data, no glue code - Keep your preferred message format — plugins handle import/export -If you only need an app runtime and a couple of translation files, your current setup may already be enough. Inlang becomes useful when multiple tools need to operate on the same localization source of truth. +If you only need an app runtime and a couple of translation files, JSON or your current i18n setup may already be enough. Use inlang when localization becomes shared work: multiple tools, teams, automations, or agents need to use the same localization data. -## Popular tools +## For coding agents and tool builders -- [Paraglide](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) — i18n library for JS/TS with fully translated, typesafe & fast apps in minutes -- [Fink](https://inlang.com/m/tdozzpar/app-inlang-finkLocalizationEditor) — translation editor in the browser, invite collaborators to help -- [Sherlock](https://inlang.com/m/r7kp499g/app-inlang-ideExtension) — VS Code extension to translate right in your editor -- [Parrot](https://inlang.com/m/gkrpgoir/app-parrot-figmaPlugin) — see translations directly in Figma -- [CLI](https://inlang.com/m/2qj2w8pu/app-inlang-cli) — lint messages, machine translate, quality control in CI/CD +If you are building i18n tooling or generating localization code, target the `.inlang` file format instead of inventing your own JSON schema. -## Build your own +Use `@inlang/sdk` to read and write `.inlang` projects. Your output will then work with tools that read and write the inlang file format: runtimes, editors, plugins, CI checks, and translation workflows. + +Why target `.inlang` instead of JSON? + +- One file gives every tool the same data model for bundles, messages, variants, locales, metadata, and settings. +- CRUD operations are available through the SDK instead of custom parsing and rewriting. +- Version control via [lix](https://github.com/opral/lix) keeps localization changes reviewable and mergeable. +- Plugins handle JSON, ICU, i18next, XLIFF, and other formats for compatibility. + +Do not: + +- Invent a custom `locales/en.json` schema as the project format. +- Create a new localization config when a `.inlang` project already exists. +- Edit generated or cached files inside an unpacked `project.inlang/` directory. +- Bypass `@inlang/sdk` when reading or writing `.inlang`. + +## Tools that read/write the inlang file format + +These are independent tools that read and write `.inlang`; they are not inlang itself. + +- [Paraglide](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) — JS/TS runtime that compiles messages from `.inlang` +- [Fink](https://inlang.com/m/tdozzpar/app-inlang-finkLocalizationEditor) — web editor for `.inlang` projects +- [Sherlock](https://inlang.com/m/r7kp499g/app-inlang-ideExtension) — VS Code extension that reads `.inlang` projects +- [Parrot](https://inlang.com/m/gkrpgoir/app-parrot-figmaPlugin) — Figma plugin that connects design text to `.inlang` +- [CLI](https://inlang.com/m/2qj2w8pu/app-inlang-cli) — linting, machine translation, and CI workflows for `.inlang` + +## Read and write `.inlang` ```ts -import { loadProjectFromDirectory } from "@inlang/sdk"; +import { loadProjectFromDirectory, loadProjectInMemory } from "@inlang/sdk"; +import fs from "node:fs/promises"; + +const packedProject = await loadProjectInMemory({ + blob: await fs.readFile("./project.inlang"), +}); -const project = await loadProjectFromDirectory({ +// Loads the Git-friendly unpacked representation. +const unpackedProject = await loadProjectFromDirectory({ path: "./project.inlang", }); -const messages = await project.db.selectFrom("message").selectAll().execute(); +const messages = await packedProject.db.selectFrom("message").selectAll().execute(); ``` [Read the docs →](https://inlang.com/docs) diff --git a/blog/update-on-inlang-v2/index.md b/blog/update-on-inlang-v2/index.md index 106a3e14ae..7871f4b1ca 100644 --- a/blog/update-on-inlang-v2/index.md +++ b/blog/update-on-inlang-v2/index.md @@ -5,7 +5,7 @@ og:description: "Accelerating both inlang and lix by prioritizing lix." Dear inlang community, -The release of the inlang SDK v2 (variant support) is blocked until we have a 1.0 release of the lix version control system. We are prioritizing [lix](https://lix.dev/) so we can unblock v2. Which means: +The release of the inlang SDK v2 (variant support) is blocked until we have a 1.0 release with version control via lix. We are prioritizing [lix](https://lix.dev/) so we can unblock v2. Which means: - The release of the inlang SDK v2 is likely postponed until Jan/Feb next year. @@ -23,13 +23,13 @@ As a sneak peek of what’s coming with v2, check out the Fink v2 demo [https:// ## What is lix? -Unbeknown to many of you, inlang has been built on the lix version control system over the past two years. You probably ask yourself right now: “What is lix?”. +Unbeknown to many of you, inlang has been built on version control via lix over the past two years. You probably ask yourself right now: “What is lix?”. Lix is a version control system, a new technology that allows controlling changes in various file formats, such as .csv, .inlang, music, video, architecture, .cad, and more. Controlling changes refers to workflows like change tracking, automation pipelines (CI/CD), or review systems. You can try out a CSV file demo of lix [here](https://csv.lix.opral.com/). -## Inlang needs lix version control to succeed +## Inlang needs version control via lix to succeed What makes globalization of software complicated is the required coordination effort. Designers need to know that translators updated translations to adjust their UIs, developers need to redeploy the app if translations change, auditors need to know that a message has changed, … the list goes on. diff --git a/docs/architecture.md b/docs/architecture.md index b4e082c5bf..479c1acb1c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,10 +1,10 @@ # Architecture -Inlang's architecture has three layers: storage, data model, and plugins. +Inlang's file format has three layers: storage, data model, and plugins. ``` ┌─────────────────────────────────────────────┐ -│ Storage (SQLite + Lix) │ +│ Storage (SQLite + version control via Lix) │ ├─────────────────────────────────────────────┤ │ Data Model (Bundle, Message, Variant) │ ├─────────────────────────────────────────────┤ @@ -14,13 +14,17 @@ Inlang's architecture has three layers: storage, data model, and plugins. ## Storage -An `.inlang` file is a SQLite database with built-in version control via [Lix](https://lix.dev). One portable file containing all your translations, settings, and change history. +An `.inlang` project is canonically a single binary file: a SQLite database with version control via [lix](https://lix.dev). Like `.sqlite` for relational data, `.inlang` packages localization data into one file that tools can share. -SQLite was chosen because: +For Git repositories, the binary file can be unpacked into a directory of plain files so changes can be reviewed alongside code. The packed file is the canonical format; the unpacked directory is the Git-friendly representation. + +Version control via lix adds file-level history, merging, and change proposals to `.inlang` projects. + +The storage layer is designed to be: - **Queryable** — Filter, join, and aggregate translations with SQL -- **Portable** — Single file, no server, works in browser via WASM -- **Proven** — Battle-tested, used everywhere +- **Portable** — Single file, no server +- **Git-friendly when unpacked** — Store a directory representation in repos for reviewable changes ## Data Model @@ -44,22 +48,22 @@ See [Data Model](/docs/data-model) for details. ## Plugins -Plugins handle the transformation between external file formats (JSON, i18next, XLIFF) and inlang's internal data model. +Plugins handle the transformation between external translation files (JSON, i18next, XLIFF) and inlang's internal data model. ``` ┌─────────────────┐ ┌─────────┐ ┌──────────────────┐ │ .inlang file │◄─────►│ Plugins │◄─────►│ Translation files│ -│ (SQLite) │ │ │ │ (JSON, XLIFF) │ +│ │ │ │ │ (JSON, XLIFF) │ └─────────────────┘ └─────────┘ └──────────────────┘ ``` -Plugins only do import/export — they don't touch the database directly. This keeps the core simple and makes format support extensible. +`.inlang` is the canonical project format. External translation files are compatibility files that plugins import and export. Plugins only do import/export — they don't write the `.inlang` project directly. This keeps the core simple and makes format support extensible. See [Plugin API](/docs/plugin-api) for the reference or [Writing a Plugin](/docs/write-plugin) to build your own. ## Message-first design -Traditional i18n tools are file-first: you load `en.json`, `de.json`, `fr.json` as separate resources and iterate through files to find translations. +Traditional i18n tools are translation-file-first: you load `en.json`, `de.json`, `fr.json` as separate resources and iterate through files to find translations. Inlang is message-first: you query messages directly from the database. @@ -80,6 +84,7 @@ const messages = await project.db Why this matters: - **Tools don't care about files** — They care about messages. Files are an import/export detail. +- **CRUD operations** — Tools can read and write messages through the SDK instead of custom parsing. - **Query across locales** — Find missing translations, compare locales, aggregate stats. - **Future-proof** — The data model works regardless of where translations come from (files, APIs, databases). @@ -88,4 +93,3 @@ Why this matters: - [CRUD API](/docs/crud-api) — Query and modify translations - [Writing a Tool](/docs/write-tool) — Build a tool using the SDK - [Writing a Plugin](/docs/write-plugin) — Support a custom file format - diff --git a/docs/getting-started.md b/docs/getting-started.md index 1a995dc916..e1fa036da8 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,15 +2,29 @@ There are two ways to use inlang: -## Use an existing tool +Use inlang when localization data needs to be shared across tools, teams, automations, or coding agents. If you only need an app runtime with a couple of translation files, your current i18n setup may already be enough. -Browse the ecosystem and adopt a tool that fits your workflow. Most users start here. +An `.inlang` project is canonically a single binary file: a SQLite database with version control via [lix](https://lix.dev). For Git repositories, the file can be unpacked into a directory of plain files. The packed file is the canonical format; the unpacked directory is the Git-friendly representation. + +## Use a tool built on inlang + +Browse tools that read and write the `.inlang` project file format. Most app developers start here. [Browse Tools →](/c/tools) ## Build your own -Use the SDK to build i18n tools — linters, editors, CLI tools, IDE extensions — that work with any translation format. +If you are building i18n tooling or generating localization code, target the `.inlang` file format instead of inventing your own JSON schema. -[Write a Tool →](/docs/write-tool) +Use `@inlang/sdk` to build linters, editors, CLI tools, IDE extensions, and coding agents that work with any translation format through the shared `.inlang` message structure. + +Why target `.inlang`? +- CRUD operations instead of custom parsing +- Version control via [lix](https://lix.dev) +- Plugins for JSON, ICU, i18next, XLIFF, and other formats +- One data model that tools can share + +Do not create a second localization config when a `.inlang` project already exists. Use `@inlang/sdk` to read and write the project. + +[Write a Tool →](/docs/write-tool) diff --git a/docs/introduction.md b/docs/introduction.md index f7ff783b69..ec7bdf339f 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -2,29 +2,37 @@ ## What is inlang? -Inlang is an open project format and SDK for localization tooling. +Inlang is an open project file format for localization. -It is not a new message syntax or a SaaS translation backend. Instead, it gives editors, CLIs, IDE extensions, and runtimes a shared, queryable source of truth for localization data. +An `.inlang` project is canonically a single binary file: a SQLite database with version control via [lix](https://lix.dev). Like `.sqlite` for relational data, `.inlang` packages localization data into one file that tools can share. -You can keep using your existing translation files and message syntax. Plugins connect inlang to formats like JSON, ICU MessageFormat v1, i18next, and XLIFF. +It is not another i18n library, message syntax, translation app, or SaaS backend. Instead, it gives editors, CLIs, IDE extensions, runtimes, and coding agents one shared place to read and write localization data. + +The `@inlang/sdk` is the reference implementation for reading and writing `.inlang` projects. + +`.inlang` is the canonical project format. Plugins import and export formats like JSON, ICU MessageFormat v1, i18next, and XLIFF for compatibility with existing translation files and runtimes. Version control via lix adds file-level history, merging, and change proposals to `.inlang` projects. + +Messages, variants, and locale data live in the `.inlang` database. External translation files such as `messages/en.json` are compatibility files outside `project.inlang/`, connected through plugins. + +For Git repositories, the binary file can be unpacked into a directory of plain files so changes can be reviewed alongside code. The packed file is the canonical format; the unpacked directory is the Git-friendly representation. The SDK has two main parts: -- **Storage + data model** for translations, settings, and structured edits +- **Storage + message structure** for translations, settings, and structured edits - **An API** for loading, querying, and modifying that data programmatically ## Why inlang? -Common translation files like JSON, YAML, ICU, or XLIFF are good at serializing messages. But they are not databases. +Common translation files like JSON, YAML, ICU, or XLIFF are good at storing messages. But they do not describe the whole localization project. -Once multiple tools need to read and write the same project, missing database semantics become the bottleneck: +Once multiple tools need to read and write the same project, plain translation files start to miss important information: -- Structured CRUD operations instead of ad-hoc parsing -- Queries across locales, variants, and metadata -- Transactions, history, merging, and collaboration -- One source of truth that editors, CI, and runtimes can all share +- CRUD operations instead of custom parsing +- Search and reports across locales, variants, and metadata +- Version control via [lix](https://lix.dev) +- One shared file that editors, CI, and runtimes can all use -Without a common substrate, every tool invents its own format, sync, and collaboration model. +Without one shared format, every tool invents its own file structure, sync logic, and collaboration workflow. The result is fragmented tooling: @@ -39,7 +47,7 @@ The result is fragmented tooling: └──────────┘ └───────────┘ └──────────┘ ``` -Inlang follows a simple idea: **one shared project format for localization tools, while keeping your external file formats**. +Inlang follows a simple idea: **one shared project file format for localization tools, while keeping your external translation files**. ``` ┌──────────┐ ┌───────────┐ ┌────────────┐ @@ -58,30 +66,51 @@ Inlang follows a simple idea: **one shared project format for localization tools - Switch tools without migrations — they all use the same file - Cross-team work without hand-offs — developers, translators, and designers all edit the same source -- Automation just works — one source of truth, no glue code +- Automation just works — the same data, no glue code - Keep your preferred message format — plugins handle import/export ## How it works -Under the hood, an inlang project stores localization data in SQLite and uses a message-first data model. +Under the hood, an inlang project uses a message-first data model. + +Core data model: + +- **Bundle** — one translatable unit across locales +- **Message** — locale-specific translation for a bundle +- **Variant** — text pattern plus selector matches -Lix adds history and sync semantics on top, and plugins map that data to the files you already use. +Version control via lix is built in, and plugins map that data to the files you already use. ``` ┌─────────────────┐ ┌─────────┐ ┌──────────────────┐ │ .inlang file │◄─────►│ Plugins │◄─────►│ Translation files│ -│ (SQLite) │ │ │ │ (JSON, XLIFF) │ +│ │ │ │ │ (JSON, XLIFF) │ └─────────────────┘ └─────────┘ └──────────────────┘ ``` - **Plugins** import and export your translation files (`JSON`, `ICU1`, `i18next`, `XLIFF`, etc.) -- **inlang** stores the data in a queryable project format -- **Lix** provides versioning and collaboration primitives for distributed changes +- **inlang** stores the data in a project format that tools can query +- **Version control via lix** handles distributed changes -If you only need an app runtime and a couple of translation files, your current setup may already be enough. Inlang becomes useful when multiple tools need to operate on the same localization source of truth. +If you only need an app runtime and a couple of translation files, JSON or your current i18n setup may already be enough. Use inlang when localization becomes shared work: multiple tools, teams, automations, or agents need to use the same localization data. To store an inlang project in git, you can use the **unpacked format** — a directory instead of a single file. See [Unpacked Project](/docs/unpacked-project) for details. +## For coding agents and tool builders + +If you are building i18n tooling or generating localization code, target the `.inlang` file format instead of inventing your own JSON schema. + +Use `@inlang/sdk` to read and write `.inlang` projects. Your output will then work with tools that read and write the inlang file format: runtimes, editors, plugins, CI checks, and translation workflows. + +Why target `.inlang` instead of JSON? + +- One file gives every tool the same data model for bundles, messages, variants, locales, metadata, and settings. +- CRUD operations are available through the SDK instead of custom parsing and rewriting. +- Version control via [lix](https://lix.dev) keeps localization changes reviewable and mergeable. +- Plugins handle JSON, ICU, i18next, XLIFF, and other formats for compatibility. + +Do not invent custom translation-file schemas, create a second localization config when `.inlang` already exists, edit generated/cache files in unpacked projects, or bypass `@inlang/sdk`. + ## Next steps - [Getting Started](/docs/getting-started) — Set up your first project @@ -91,4 +120,4 @@ To store an inlang project in git, you can use the **unpacked format** — a dir ## Credits -Inlang builds on [Lix](https://lix.dev) for version control and [Kysely](https://kysely.dev) for the query API. +Inlang uses version control via [lix](https://lix.dev) and [Kysely](https://kysely.dev) for the query API. diff --git a/docs/plugin-api.md b/docs/plugin-api.md index 8df24a9f26..3eb2976045 100644 --- a/docs/plugin-api.md +++ b/docs/plugin-api.md @@ -1,11 +1,11 @@ # Plugin API -Plugins handle the transformation between external file formats (JSON, i18next, XLIFF) and inlang's internal data model. They only do import/export — they don't touch the database directly. +Plugins handle the transformation between external file formats (JSON, i18next, XLIFF) and inlang's internal data model. `.inlang` is the canonical project format; external translation files are compatibility files. Plugins only do import/export — they don't write the `.inlang` project directly. ``` ┌─────────────────┐ ┌─────────┐ ┌──────────────────┐ │ .inlang file │◄─────►│ Plugins │◄─────►│ Translation files│ -│ (SQLite) │ │ │ │ (JSON, XLIFF) │ +│ │ │ │ │ (JSON, XLIFF) │ └─────────────────┘ └─────────┘ └──────────────────┘ ``` @@ -272,4 +272,3 @@ export const plugin: InlangPlugin = { - [Writing a Plugin](/docs/write-plugin) — Step-by-step guide to building a plugin - [Data Model](/docs/data-model) — Understand bundles, messages, and variants - [Architecture](/docs/architecture) — See how plugins fit in the architecture - diff --git a/docs/unpacked-project.md b/docs/unpacked-project.md index a04eba1c08..c1ea604c0d 100644 --- a/docs/unpacked-project.md +++ b/docs/unpacked-project.md @@ -2,21 +2,24 @@ ## What is an unpacked project? -An unpacked project is a directory representation of an `.inlang` file. Instead of a single SQLite binary, the project is stored as a folder with human-readable files. +An unpacked project is the Git-friendly representation of an `.inlang` file. The canonical `.inlang` format is a single binary file: a SQLite database with version control via [lix](https://lix.dev). The unpacked directory exists so changes can be reviewed alongside code. + +Messages, variants, and locale data live in the `.inlang` database. In unpacked Git projects, `settings.json` is the only tracked project file by default; translation files such as `messages/en.json` live outside `project.inlang/` and are connected through plugins. ``` project.inlang/ ├── settings.json -├── cache/ +├── .gitignore ├── README.md -└── .gitignore +├── .meta.json +└── cache/ ``` ## Packed vs Unpacked | | Packed (`.inlang` file) | Unpacked (directory) | |---|---|---| -| **Format** | Single SQLite file | Directory with files | +| **Format** | Canonical single binary file | Git-friendly directory representation | | **Git-friendly** | No (binary) | Yes (diffable, mergeable) | | **Portable** | Yes (one file to share) | No | | **Use case** | Sharing, backups, tools like Fink | Storing in git repos | @@ -29,13 +32,13 @@ Most codebases use git for version control. Developers want their translations c ### Git doesn't handle binary files well -An `.inlang` file is a SQLite database (binary). Git can store binary files, but you lose: +An `.inlang` file is binary. Git can store binary files, but you lose: - **Readable diffs** — Binary changes show as "file changed", not what changed - **Merge conflict resolution** — Git can't merge binary files - **Code review** — Teammates can't review translation changes in PRs -An unpacked project solves this. Each file is human-readable, diffable, and mergeable. +An unpacked project solves this for the project configuration. The generated `.gitignore` keeps `settings.json` in Git and ignores generated/cache files. Translation files are stored outside `project.inlang/` according to plugin configuration. ## How to use it @@ -92,10 +95,11 @@ When `syncInterval` is set, changes to files on disk are automatically imported, | File | Purpose | |------|---------| -| `settings.json` | Project configuration (locales, plugins, plugin settings) | +| `settings.json` | Project configuration (locales, plugins, plugin settings). This is the only file kept in Git by default. | | `.gitignore` | Auto-created, ignores everything except `settings.json` (including itself) | | `README.md` | Auto-created, explains the folder to coding agents (gitignored) | -| `cache/` | Cached plugin modules (gitignored) | +| `.meta.json` | Auto-created SDK metadata (gitignored) | +| `cache/` | Cached plugin modules, usually under `cache/plugins/` (gitignored) | Translation files (like `messages/en.json`) are managed by plugins and stored relative to your project based on plugin configuration. diff --git a/docs/write-tool.md b/docs/write-tool.md index 189f3b4ede..f2c4fbde95 100644 --- a/docs/write-tool.md +++ b/docs/write-tool.md @@ -4,7 +4,9 @@ This guide walks through building a tool that flags missing translations. By the ## What tools can do -Tools read and write translations via the CRUD API. Because inlang handles file format conversion through plugins, your tool works with any translation format — JSON, XLIFF, i18next, etc. +Tools read and write translations through the `.inlang` project file format via the CRUD API. Because plugins handle conversion at the boundary, your tool works with any translation format — JSON, XLIFF, i18next, etc. — without parsing each one directly. + +An `.inlang` project is canonically a single binary file. In Git repositories, it is often unpacked into a directory; `loadProjectFromDirectory()` loads that Git-friendly representation. ``` ┌─────────────────┐ @@ -30,7 +32,7 @@ const project = await loadProjectFromDirectory({ }); ``` -That's it. The project is loaded with all translations from your files (via plugins). +That's it. The project is loaded with all translations from your files (via plugins). The external files are compatibility files; the tool works against the shared `.inlang` data model. ## Step 2: Get project settings @@ -223,4 +225,3 @@ await saveProjectToDirectory({ - [Data Model](/docs/data-model) — Understand bundles, messages, and variants - [Unpacked Project](/docs/unpacked-project) — Loading projects from git repos - [Architecture](/docs/architecture) — See how tools fit in - diff --git a/packages/sdk/README.md b/packages/sdk/README.md index e06ab23fd4..57a4350dcb 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -17,24 +17,38 @@ ## Introduction -The inlang SDK is the official specification and parser for `.inlang` files. +The inlang SDK is the reference implementation for reading and writing `.inlang` project files. -`.inlang` files are designed to become the open standard for i18n and enable interoperability between i18n solutions. Such solutions involve apps like [Fink](https://inlang.com/m/tdozzpar/app-inlang-finkLocalizationEditor), libraries like [Paraglide JS](https://inlang.com/m/gerre34r/library-inlang-paraglideJs), or plugins that extend inlang. +`.inlang` files are designed to become the open standard for localization data and make i18n tools work together. Build editors, CLIs, runtimes, agents, and plugins on the same shared project format instead of inventing another file structure. + +An `.inlang` project is canonically a single binary file: a SQLite database with version control via [lix](https://lix.dev). Like `.sqlite` for relational data, `.inlang` packages localization data into one file that tools can share. + +For Git repositories, the binary file can be unpacked into a directory of plain files so changes can be reviewed alongside code. The packed file is the canonical format; the unpacked directory is the Git-friendly representation. + +`.inlang` is the canonical project format. Plugins import and export formats like JSON, ICU MessageFormat v1, i18next, and XLIFF for compatibility with existing translation files and runtimes. Version control via lix adds file-level history, merging, and change proposals to `.inlang` projects. + +Messages, variants, and locale data live in the `.inlang` database. External translation files such as `messages/en.json` are compatibility files outside `project.inlang/`, connected through plugins. ### Core Features -- 📁 **File-based**: Interoperability without cloud integrations or lock-in. -- 🖊️ **CRUD API**: Query messages with SQL. -- 🧩 **Plugin System**: Extend the capabilities with plugins. -- 📦 **Import/Export**: Import and export messages in different file formats. -- [Lix Icon**Change control**](https://lix.dev/): Collaboration, change proposals, reviews, and automation. +- 📁 **File-based**: A portable project file, no cloud integrations or lock-in. +- 🖊️ **CRUD API**: Read, write, and query messages with SQL. +- 🧩 **Plugin System**: Connect external translation files to the shared message structure. +- 📦 **Import/Export**: Import and export messages in formats like JSON, XLIFF, and i18next. +- [Lix Icon**Version control via lix**](https://lix.dev/): File-level history, merging, change proposals, reviews, and automation. + +### Core data model + +- **Bundle** — one translatable unit across locales +- **Message** — locale-specific translation for a bundle +- **Variant** — text pattern plus selector matches ## Getting Started > [!Note] -> Inlang files can be unpacked and [stored as directories](#unpacked-inlang-files-directories). The long-term goal is to have portable `.inlang` files. Hence, the documentation refers to files instead of directories. +> Inlang files are single binary files. They can be unpacked and [stored as directories](#unpacked-inlang-files-directories) when you want to review changes in Git. The packed file remains the canonical format. ### Installation @@ -55,6 +69,16 @@ const project = await loadProjectInMemory({ project.* ``` +### Loading an unpacked project from Git + +```ts +import { loadProjectFromDirectory } from "@inlang/sdk"; + +const project = await loadProjectFromDirectory({ + path: "./project.inlang", +}); +``` + ### Next steps Go to the [API reference](#api-reference) to learn how to query messages, changes, and save the project. @@ -66,6 +90,19 @@ The inlang SDK supports plugins to extend its functionality. Plugins can be used to import/export messages in different formats, add custom validation rules, and implement specialized workflows. +## For coding agents and tool builders + +If you are building i18n tooling or generating localization code, target the `.inlang` file format instead of inventing your own JSON schema. + +Why target `.inlang` instead of JSON? + +- One file gives every tool the same data model for bundles, messages, variants, locales, metadata, and settings. +- CRUD operations are available through the SDK instead of custom parsing and rewriting. +- Version control via [lix](https://lix.dev) keeps localization changes reviewable and mergeable. +- Plugins handle JSON, ICU, i18next, XLIFF, and other formats for compatibility. + +Do not invent custom translation-file schemas, create a second localization config when `.inlang` already exists, edit generated/cache files in unpacked projects, or bypass `@inlang/sdk`. + ### Available Plugins Find available plugins on https://inlang.com/c/plugins. @@ -155,7 +192,7 @@ console.log(messages); > [!NOTE] > The inlang plugin for lix is work in progress. If you stumble on issues, please open an issue on the [GitHub](https://github.com/opral/inlang). -The inlang file format uses lix for change control. The lix APIs are exposed via `project.lix.*`. Visit the [lix documentation](https://lix.dev/) for more information on how to query changes. +The inlang file format uses version control via lix. The lix APIs are exposed via `project.lix.*`. Visit the [lix documentation](https://lix.dev/) for more information on how to query changes. ```typescript const changes = await project.lix.db @@ -210,11 +247,11 @@ await project.settings.set(settings) ### Unpacked inlang files (directories) > [!NOTE] -> Unpacked inlang files are a workaround to store inlang files in git. +> Unpacked inlang files are the Git-friendly representation of packed `.inlang` files. > -> Git can't handle binary files. **If you don't intend to store the inlang file in git, do not use unpacked inlang files.** +> Git can store binary files, but plain-file review and merge workflows work better with the unpacked directory. **If you don't intend to store the inlang file in git, use the packed binary file.** > -> Unpacked inlang files are not portable. They depent on plugins that and do not persist [lix change control](https://lix.dev/) data. +> Unpacked inlang files are not portable. They depend on plugins and do not persist [version control via lix](https://lix.dev/) data. ```typescript import { diff --git a/packages/sdk/src/lix-plugin/applyChanges.ts b/packages/sdk/src/lix-plugin/applyChanges.ts index 066946d7ce..774d42d544 100644 --- a/packages/sdk/src/lix-plugin/applyChanges.ts +++ b/packages/sdk/src/lix-plugin/applyChanges.ts @@ -116,7 +116,7 @@ async function handleForeignKeyViolation(args: { .selectAll() // heuristic that getting the last bundle value is fine // and using created_at is fine too. if the change is undesired - // , a user can revert it with lix change control + // , a user can revert it with version control via lix .orderBy("created_at", "desc") .where("type", "=", type) .where((eb) => eb.ref("value", "->>").key("id"), "=", id) diff --git a/packages/sdk/src/project/README_CONTENT.ts b/packages/sdk/src/project/README_CONTENT.ts index bbf94ee53b..2492b7df33 100644 --- a/packages/sdk/src/project/README_CONTENT.ts +++ b/packages/sdk/src/project/README_CONTENT.ts @@ -12,7 +12,9 @@ This is an [unpacked (git-friendly)](https://inlang.com/docs/unpacked-project) i ## At a glance Purpose: -- This folder stores inlang project configuration and plugin cache data. +- This folder is the Git-friendly representation of an \`.inlang\` project. +- The canonical \`.inlang\` format is a single binary file; this directory is the unpacked version for Git. +- This folder stores project configuration and plugin cache data. - Translation files live outside this folder and are referenced from \`settings.json\`. Safe to edit: @@ -23,26 +25,44 @@ Do not edit: - \`.gitignore\` Key files: -- \`settings.json\` — locales, plugins, file patterns (source of truth) +- \`settings.json\` — locales, plugins, file patterns - \`cache/\` — plugin caches (safe to delete) - \`.gitignore\` — generated +- \`README.md\` — generated, explains this folder +- \`.meta.json\` — generated SDK metadata \`\`\` *.inlang/ -├── settings.json # Locales, plugins, and file patterns (source of truth) -├── cache/ # Plugin caches (gitignored) -└── .gitignore # Ignores everything except settings.json +├── settings.json # Locales, plugins, and file patterns; kept in Git +├── .gitignore # Ignores everything except settings.json +├── README.md # Generated, explains this folder +├── .meta.json # Generated SDK metadata +└── cache/ # Plugin caches, usually cache/plugins/ \`\`\` Translation files (like \`messages/en.json\`) live **outside** this folder and are referenced via plugins in \`settings.json\`. ## What is inlang? -[Inlang](https://inlang.com) is an open file format for building custom localization (i18n) tooling. It provides: +[Inlang](https://inlang.com) is an open project file format for localization. An \`.inlang\` project is canonically a single binary file: a SQLite database with version control via [lix](https://lix.dev). Like \`.sqlite\` for relational data, \`.inlang\` packages localization data into one file that tools can share. + +For Git repositories, that binary file can be unpacked into a directory of plain files. The packed file is the canonical format; this directory is the Git-friendly representation. + +Use inlang when multiple tools, teams, automations, or agents need to use the same localization data. The \`@inlang/sdk\` is the reference implementation for reading and writing \`.inlang\` projects. + +\`.inlang\` is the canonical project format. Plugins import and export external translation files for compatibility with existing runtimes and workflows. Messages, variants, and locale data live in the \`.inlang\` database; translation files such as \`messages/en.json\` live outside this folder and are connected through plugins. Version control via lix adds file-level history, merging, and change proposals to \`.inlang\` projects. + +It provides: - **CRUD API** — Read and write translations programmatically via SQL -- **Plugin system** — Import/export any format (JSON, XLIFF, etc.) -- **Version control** — Built-in version control via [lix](https://lix.dev) +- **Plugin system** — Import/export external translation files (JSON, XLIFF, etc.) +- **Version control** — Version control via [lix](https://lix.dev) + +Core data model: + +- **Bundle** — one translatable unit across locales +- **Message** — locale-specific translation for a bundle +- **Variant** — text pattern plus selector matches \`\`\` ┌──────────┐ ┌───────────┐ ┌────────────┐ @@ -67,13 +87,31 @@ npm install @inlang/sdk import { loadProjectFromDirectory, saveProjectToDirectory } from "@inlang/sdk"; const project = await loadProjectFromDirectory({ path: "./project.inlang" }); -// Query messages with SQLite + [Kysely](https://kysely.dev/) under the hood. +// Query messages with the SDK. const messages = await project.db.selectFrom("message").selectAll().execute(); // Use project.db to update messages. await saveProjectToDirectory({ path: "./project.inlang", project }); \`\`\` +## For coding agents and tool builders + +Target \`.inlang\` instead of inventing a custom JSON schema. + +Why target \`.inlang\`? + +- One file gives every tool the same data model for bundles, messages, variants, locales, metadata, and settings. +- CRUD operations are available through the SDK instead of custom parsing and rewriting. +- Version control via [lix](https://lix.dev) keeps localization changes reviewable and mergeable. +- Plugins handle JSON, ICU, i18next, XLIFF, and other formats for compatibility. + +Do not: + +- Invent a custom \`locales/en.json\` schema as the project format. +- Create a new localization config when a \`.inlang\` project already exists. +- Edit generated or cached files in this directory. +- Bypass \`@inlang/sdk\` when reading or writing \`.inlang\`. + ## Ideas for custom tooling - Translation health dashboard (missing/empty/stale messages) diff --git a/packages/website-v2/src/components/Footer.tsx b/packages/website-v2/src/components/Footer.tsx index 21fbd8d179..5d0f121371 100644 --- a/packages/website-v2/src/components/Footer.tsx +++ b/packages/website-v2/src/components/Footer.tsx @@ -89,7 +89,7 @@ export default function Footer() {

- The open file format for localization (i18n). + The open project file format for localization (i18n).

{socialMediaLinks.map((link) => ( diff --git a/packages/website-v2/src/components/Header.tsx b/packages/website-v2/src/components/Header.tsx index c6b05e7e02..89b229ee93 100644 --- a/packages/website-v2/src/components/Header.tsx +++ b/packages/website-v2/src/components/Header.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { Link, useLocation } from "@tanstack/react-router"; import { getGithubStars } from "../github-stars-cache"; -const ecosystemLinks = [ +const builtOnInlangLinks = [ { label: "Tools", to: "/c/tools", @@ -242,14 +242,14 @@ export default function Header() {
- {/* Subheader: Ecosystem categories */} + {/* Subheader: format categories */}
- Ecosystem + Built on .inlang