Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-grapes-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inlang/plugin-icu1": major
---

Initial release
92 changes: 92 additions & 0 deletions blog/icu-messageformat-v1-plugin/assets/og-image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions blog/icu-messageformat-v1-plugin/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
og:title: "ICU MessageFormat v1 Plugin Now Available"
og:description: "Full support for ICU MessageFormat v1 in inlang. Drop-in compatibility for plurals, selects, and formatters with your existing JSON files."
og:image: ./assets/og-image.svg
og:type: article
---

![ICU MessageFormat v1 Plugin for Inlang](./assets/og-image.svg)

The ICU MessageFormat v1 plugin is now available for inlang. If your project uses ICU MessageFormat (the format behind `react-intl`, `@formatjs`, and many other i18n libraries), you can now use inlang's full tooling ecosystem.

## What's Supported

The plugin parses and exports ICU1 strings with full fidelity:

- **Plurals and selectordinal** including `offset` and exact matches (`=0`, `=1`)
- **Select expressions** for gender and other categorical choices
- **Formatter functions** like `number`, `date`, `time` with style parameters
- **Octothorpe `#`** substitution inside plural/selectordinal cases

```json
{
"items": "{count, plural, offset:1 =0 {no items} one {# item} other {# items}}",
"rank": "{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}"
}
```

## Get Started

Add the plugin to your `project.inlang/settings.json`:

```json
{
"modules": [
"https://cdn.jsdelivr.net/npm/@inlang/plugin-icu1@latest/dist/index.js"
],
"plugin.inlang.icu-messageformat-1": {
"pathPattern": "./messages/{locale}.json"
}
}
```

That's it. Your existing JSON files work immediately. No migration required.

[Read the full documentation](https://inlang.com/m/p7c8m1d2)
6 changes: 6 additions & 0 deletions blog/table_of_contents.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
[
{
"path": "./icu-messageformat-v1-plugin/index.md",
"slug": "icu-messageformat-v1-plugin",
"date": "2026-02-09",
"authors": ["samuelstroschein"]
},
{
"path": "./human-readable-message-ids/index.md",
"slug": "human-readable-message-ids",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/marketplace-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,7 @@
"pricing": "free",
"publisherName": "inlang",
"publisherIcon": "https://inlang.com/favicon/safari-pinned-tab.svg",
"website": "https://www.npmjs.com/package/@inlang/cli",
"repository": "https://github.com/opral/inlang/tree/main/packages/cli",
"license": "Apache-2.0"
}
1 change: 1 addition & 0 deletions packages/marketplace-registry/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"632iow21": "https://raw.githubusercontent.com/opral/inlang/refs/heads/main/packages/plugins/m-function-matcher/marketplace-manifest.json",
"698iow33": "https://raw.githubusercontent.com/opral/inlang/refs/heads/main/packages/plugins/t-function-matcher/marketplace-manifest.json",
"193hsyds": "https://raw.githubusercontent.com/opral/inlang/refs/heads/main/packages/plugins/next-intl/marketplace-manifest.json",
"p7c8m1d2": "https://raw.githubusercontent.com/opral/inlang/refs/heads/main/packages/plugins/icu1/marketplace-manifest.json",
"neh2d6w7": "https://cdn.jsdelivr.net/npm/inlang-plugin-xcstrings@latest/marketplace-manifest.json",
"3gk8n4n4": "https://raw.githubusercontent.com/opral/inlang/refs/heads/inlang-v1/inlang/source-code/github-lint-action/marketplace-manifest.json",
"y0eo8f66": "https://raw.githubusercontent.com/opral/inlang/refs/heads/inlang-v1/inlang/source-code/message-lint-rules/emptyPattern/marketplace-manifest.json",
Expand Down
3 changes: 2 additions & 1 deletion packages/plugins/i18next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
},
"repository": {
"type": "git",
"url": "https://github.com/opral/inlang"
"url": "https://github.com/opral/inlang",
"directory": "packages/plugins/i18next"
},
"scripts": {
"dev": "node ./build.js",
Expand Down
108 changes: 108 additions & 0 deletions packages/plugins/icu1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
og:title: ICU MessageFormat v1 Plugin
og:description: Storage plugin for inlang that reads and writes ICU MessageFormat v1 JSON files. Supports selects, plurals, selectordinal, offsets, and formatter styles.
---

# ICU MessageFormat v1 Plugin

The ICU MessageFormat v1 plugin is a storage plugin for inlang. It reads and writes ICU1 strings in JSON files per locale.
It targets ICU MessageFormat v1 (a.k.a. ICU MessageFormat) and maps it onto inlang's data model so selectors and variants are preserved.

## Features

- Parses ICU1 messages using `@messageformat/parser`.
- Supports `select`, `plural`, and `selectordinal`, including exact matches (`=n`) and `offset`.
- Supports `#` (octothorpe) inside plural/selectordinal cases.
- Supports formatter functions (e.g. `number`, `date`, `time`, `spellout`, `ordinal`, `duration`) with style parameters.
- Exports ICU1 strings back from inlang data.

## Installation

Install the plugin in your Inlang Project by adding it to your `modules` in `project.inlang/settings.json` and configure `pathPattern`.

```diff
// project.inlang/settings.json
{
"modules": [
+ "https://cdn.jsdelivr.net/npm/@inlang/plugin-icu1@latest/dist/index.js"
],
+ "plugin.inlang.icu-messageformat-1": {
+ "pathPattern": "./messages/{locale}.json"
+ }
}
```

## Configuration

Configuration happens in `project.inlang/settings.json` under `"plugin.inlang.icu-messageformat-1"`.

### `pathPattern`

You can define a single `pathPattern` or provide an array of patterns. The placeholder should be `{locale}`.

#### Single path pattern example

```json
{
"plugin.inlang.icu-messageformat-1": {
"pathPattern": "./messages/{locale}.json"
}
}
```

#### Multiple path patterns example

```json
{
"plugin.inlang.icu-messageformat-1": {
"pathPattern": ["./defaults/{locale}.json", "./product/{locale}.json"]
}
}
```

> [!NOTE]
> When exporting, all messages are written to every path pattern in the array (one file per pattern and locale). Multiple patterns are a one-way merge for import.

## Messages

ICU1 files contain key-value pairs with ICU MessageFormat strings.

```json
// messages/en.json
{
"hello_world": "Hello World!",
"greeting": "Good morning {name}!",
"likes": "You have {count, plural, one {# like} other {# likes}}"
}
```

### Plurals and selectordinal

```json
{
"items": "{count, plural, offset:1 =0 {no items} one {# item} other {# items}}",
"rank": "{place, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}"
}
```

### Select

```json
{
"gender": "{gender, select, male {He} female {She} other {They}}"
}
```

### Escaping

ICU1 uses apostrophes to escape literals. To include literal braces, wrap them in apostrophes:

- `'{'
- `'}'`

If you need a literal apostrophe, use `''` (two single quotes).

## Caveats

- Export is canonicalized. Output is semantically equivalent, but whitespace and formatting may differ from the original source.
- Tags/markup are treated as plain text by the parser.
12 changes: 12 additions & 0 deletions packages/plugins/icu1/assets/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions packages/plugins/icu1/marketplace-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "https://inlang.com/schema/marketplace-manifest",
"id": "plugin.inlang.icu-messageformat-1",
"icon": "./assets/icon.svg",
"displayName": {
"en": "ICU MessageFormat v1"
},
"description": {
"en": "Storage plugin for inlang that reads and writes ICU MessageFormat v1 JSON files with support for selects, plurals, selectordinal, offsets, and formatter styles."
},
"pages": {
"/": "./README.md"
},
"keywords": [
"icu",
"messageformat",
"json",
"storage",
"messages",
"plugin",
"javascript",
"website"
],
"publisherName": "inlang",
"publisherIcon": "https://inlang.com/favicon/safari-pinned-tab.svg",
"license": "Apache-2.0",
"module": "https://cdn.jsdelivr.net/npm/@inlang/plugin-icu1@latest/dist/index.js"
}
24 changes: 15 additions & 9 deletions packages/plugins/icu1/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
"name": "@inlang/plugin-icu1",
"version": "0.0.1",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/opral/inlang",
"directory": "packages/plugins/icu1"
},
"type": "module",
"types": "./dist/index.d.ts",
"exports": {
Expand All @@ -11,21 +16,22 @@
"./dist"
],
"scripts": {
"_dev": "node ./build.js",
"_build": "NODE_ENV=production node ./build.js",
"_test": "tsc --noEmit && vitest run --passWithNoTests",
"_format": "prettier ./src --write",
"_clean": "rm -rf ./dist ./node_modules"
"dev": "node ./build.js",
"build": "NODE_ENV=production node ./build.js",
"test": "tsc --noEmit && vitest run --passWithNoTests",
"format": "prettier ./src --write",
"clean": "rm -rf ./dist ./node_modules"
},
"devDependencies": {
"@inlang/sdk": "workspace:*",
"@inlang/tsconfig": "workspace:*",
"@sinclair/typebox": "^0.31.17",
"typescript": "^5.5.2",
"esbuild": "^0.25.9",
"prettier": "3.3.3",
"vitest": "2.1.8"
"typescript": "^5.5.2",
"vitest": "^4.0.6"
},
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.7.8"
"@messageformat/parser": "^5.1.1"
}
}
}
Loading