PageSourceSearch

https://inlang.com/assets/README-BDk5ZM8U.js

js inlang.com collected 2026-09-25 20:52:52 UTC 12,809 bytes, 417 lines download raw bytes

1const n=`---
2og:title: Inlang Message Format Plugin
3og:description: A storage plugin for inlang that stores messages in JSON files per language. Supports variables, pluralization, and nested structures.
4---
5
6# Inlang Message Format Plugin
7
8![inlang message format](https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/assets/banner.svg)
9
10The Inlang Message Format is a storage plugin for inlang. It stores messages in a JSON file per language.
11It is designed after inlang's data models, which enables all features of inlang.
12
13The syntax is inspired by the upcoming [MessageFormat 2.0](https://messageformat.unicode.org/) draft to keep migration friction low as the standard matures.
14
15The message files contain key-value pairs of the message ID and the translation. You can add variables in your message by using curly braces. To include literal curly braces in your text, escape them with a backslash (\`\\{\` and \`\\}\`). Nested objects are flattened using dot notation on import and unflattened on export.
16
17\`\`\`json
18//messages/en.json
19{
20  "hello_world": "Hello World!",
21  "greeting": "Good morning {name}!",
22  "code_example": "Use \\\\{variable\\\\} syntax for variables"
23}
24
25//messages/de.json
26{
27  "$schema": "https://inlang.com/schema/inlang-message-format",
28  "hello_world": "Hallo Welt!",
29  "greeting": "Guten Tag {name}!"
30}
31\`\`\`
32
33> [!NOTE]
34> The \`$schema\` property is optional and provides IDE autocompletion for message syntax. It is automatically added when exporting files and ignored on import. The schema is for editor support only and does not perform runtime validation.
35
36## Installation
37
38Install the plugin in your Inlang Project by adding it to your \`"modules"\` in \`project.inlang/settings.json\`. You will also need to provide a \`pathPattern\` for the plugin.
39
40\`\`\`diff
41// project.inlang/settings.json
42{
43  "modules" : [
44+    "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js"
45  ],
46+ "plugin.inlang.messageFormat": {
47+   "pathPattern": "./messages/{locale}.json"
48+ }
49}
50\`\`\`
51
52## Version Compatibility
53
54| Plugin Version | SDK Requirement       | Breaking Changes                                                     |
55| -------------- | --------------------- | -------------------------------------------------------------------- |
56| v4.x           | @inlang/sdk v2+       | Complex messages must be wrapped in an array. Nesting support added. |
57| v3.x           | @inlang/sdk v2 (beta) | Upgraded to SDK v2 data model.                                       |
58| v2.x           | @inlang/sdk v1        | New human-readable format. Auto-migrates from v1 on first load.      |
59
60> [!NOTE]
61> When upgrading the plugin, ensure your SDK version meets the minimum requirement. Mismatched versions may cause silent failures or import errors.
62
63## Configuration
64
65Configuration happens in \`project.inlang/settings.json\` under the \`"plugin.inlang.messageFormat"\` key.
66
67### \`pathPattern\`
68
69You can define a single \`pathPattern\` or provide an array of patterns to split your messages across multiple JSON files. Messages from **all matching files will be merged**, and if the same message key appears in multiple files, the **value from the last file in the array will override** earlier ones. The placeholder should be \`{locale}\` (preferred) or \`{languageTag}\` (legacy, still supported).
70
71This allows for patterns like having a shared base file and extending or overriding it with domain- or customer-specific files.
72
73#### Single path pattern example
74
75\`\`\`json
76{
77	"plugin.inlang.messageFormat": {
78		"pathPattern": "./messages/{locale}.json"
79	}
80}
81\`\`\`
82
83#### Multiple path patterns example
84
85\`\`\`json
86{
87	"plugin.inlang.messageFormat": {
88		"pathPattern": ["./defaults/{locale}.json", "./clothing/{locale}.json"]
89	}
90}
91\`\`\`
92
93Given the following files:
94
95\`\`\`json
96// ./defaults/en.json
97{
98	"hello": "Hello!",
99	"cart": {
100		"title": "Your cart"
101	},
102	"size": "Size"
103}
104\`\`\`
105
106\`\`\`json
107// ./clothing/en.json
108{
109	"size": "Clothing size",
110	"fit": "Fit"
111}
112\`\`\`
113
114The merged result for locale \`en\` will be:
115
116\`\`\`json
117{
118	"hello": "Hello!",
119	"cart": {
120		"title": "Your cart"
121	},
122	"size": "Clothing size", // Overridden
123	"fit": "Fit" // Added
124}
125\`\`\`
126
127This lets you modularize and override translations while keeping a shared base.
128
129> [!WARNING]
130> When exporting, all messages are written to the **last** path pattern in the array. Messages are not split back across multiple files. This means multiple path patterns are a one-way merge — useful for importing from shared base files, but the original file structure is not preserved on export.
131
132### \`sort\`
133
134Optionally sort keys when writing files to keep git diffs consistent.
135
136\`\`\`json
137{
138	"plugin.inlang.messageFormat": {
139		"pathPattern": "./messages/{locale}.json",
140		"sort": "asc"
141	}
142}
143\`\`\`
144
145## Messages
146
147You can organize your messages in a nested structure for better organization of your translations. There are two types of messages:
148
149### Simple Messages
150
151> [!NOTE]
152> Nesting is supported from v4 of the plugin and requires apps to use the inlang SDK v2 higher.
153
154Simple messages are string values, either directly at the root level or nested within objects:
155
156\`\`\`json
157{
158	"hello": "world",
159	"navigation": {
160		"home": "Home",
161		"about": "About",
162		"contact": {
163			"email": "Email",
164			"phone": "Phone"
165		}
166	}
167}
168\`\`\`
169
170### Complex Messages (with variants, pluralization, etc.)
171
172For complex messages with variants, wrap the message object in an array to differentiate it from nested simple messages:
173
174\`\`\`json
175{
176	"simple": "This is a simple message",
177	"count": [
178		{
179			"declarations": ["input count", "local countPlural = count: plural"],
180			"selectors": ["countPlural"],
181			"match": {
182				"countPlural=one": "There is one item",
183				"countPlural=other": "There are {count} items"
184			}
185		}
186	]
187}
188\`\`\`
189
190When addressing nested messages, use dot notation (e.g. \`navigation.items.count\` for a nested \`navigation.items.count\` entry).
191
192> [!NOTE]
193> The array wrapper is how we distinguish between a nested object containing more messages vs. a complex message object with variants.
194
195### Markup Placeholders (Rich Text)
196
197Simple message patterns can include markup placeholders for rich rendering.
198
199- Open + close markup: \`{#tag}...{/tag}\`
200- Standalone markup: \`{#icon/}\`
201
202\`\`\`json
203{
204	"welcome": "{#b}Hi {name}{/b}{#icon/}"
205}
206\`\`\`
207
208#### Markup options
209
210Markup options are key/value pairs written after the markup name:
211
212- Literal option value: \`key=|literal|\`
213- Variable option value: \`key=$variable\`
214
215\`\`\`json
216{
217	"cta": "{#link to=|/docs| rel=$relationship}Read docs{/link}"
218}
219\`\`\`
220
221In the example above, \`to\` is a literal option and \`rel\` reads from the input variable \`relationship\`.
222
223The same \`$variable\` syntax is also supported in local formatter declarations.
224Whitespace around \`=\` is optional in declaration options.
225
226\`\`\`json
227{
228	"pricing_card_price_display": [
229		{
230			"declarations": [
231				"input amount",
232				"input priceCurrency",
233				"local formattedAmount = amount: number style=currency currency=$priceCurrency notation=compact"
234			],
235			"match": {
236				"formattedAmount=*": "{formattedAmount}"
237			}
238		}
239	]
240}
241\`\`\`
242
243#### Markup attributes
244
245Markup attributes are metadata prefixed with \`@\`:
246
247- Presence attribute (boolean \`true\`): \`@track\`
248- Literal-valued attribute: \`@variant=|hero|\`
249
250\`\`\`json
251{
252	"banner": "{#cta @track @variant=|hero|}Try now{/cta}"
253}
254\`\`\`
255
256#### Quoted literal syntax (\`|...|\`)
257
258Use \`|...|\` when you want an explicit literal value in markup options/attributes.
259Inside quoted literals:
260
261- Escape \`|\` as \`\\|\`
262- Escape \`\\\` as \`\\\\\`
263
264\`\`\`json
265{
266	"icon": "{#icon name=|pipe\\\\|value| path=|C:\\\\\\\\icons\\\\\\\\ok| @decorative/}"
267}
268\`\`\`
269
270### Escaping Special Characters
271
272Since curly braces \`{\` and \`}\` are used to denote variables, you need to escape them if you want to include literal braces in your message text. Use a backslash to escape:
273
274- \`\\{\` → literal \`{\`
275- \`\\}\` → literal \`}\`
276- \`\\\\\` → literal \`\\\`
277
278This is useful when your translations contain code snippets, JSON examples, or other text with curly braces:
279
280\`\`\`json
281{
282	"json_hint": "JSON objects look like \\\\{\\"key\\": \\"value\\"\\\\}",
283	"template_help": "Use \\\\{variable\\\\} to insert dynamic values",
284	"path_example": "Windows paths use \\\\\\\\ as separator"
285}
286\`\`\`
287
288The above messages will render as:
289- \`JSON objects look like {"key": "value"}\`
290- \`Use {variable} to insert dynamic values\`
291- \`Windows paths use \\ as separator\`
292
293> [!NOTE]
294> Escaping is only necessary for \`{\`, \`}\`, and \`\\\` characters. Other special characters can be used directly.
295
296## Variants (pluralization, gendering, A/B testing)
297
298The message below will match the following conditions:
299
300| Platform | User Gender | Message                                                                     |
301| -------- | ----------- | --------------------------------------------------------------------------- |
302| android  | male        | {username} has to download the app on his phone from the Google Play Store. |
303| ios      | female      | {username} has to download the app on her iPhone from the App Store.        |
304| \\*       | \\*          | The person has to download the app.                                         |
305
306\`\`\`json
307{
308	"jojo_mountain_day": [
309		{
310			"match": {
311				"platform=android, userGender=male": "{username} has to download the app on his phone from the Google Play Store.",
312				"platform=ios, userGender=female": "{username} has to download the app on her iPhone from the App Store.",
313				"platform=*, userGender=*": "The person has to download the app."
314			}
315		}
316	]
317}
318\`\`\`
319
320#### Ordinal pluralization (1st, 2nd, 3rd…)
321
322\`plural\` forwards its options to \`Intl.PluralRules\`, so you can request ordinal categories by passing \`type=ordinal\` in your declaration.
323
324\`\`\`json
325{
326	"finished_readout": [
327		{
328			"declarations": [
329				"input placeNumber",
330				"local ordinalCategory = placeNumber: plural type=ordinal"
331			],
332			"selectors": ["ordinalCategory"],
333			"match": {
334				"ordinalCategory=one": "You finished in {placeNumber}st place",
335				"ordinalCategory=two": "You finished in {placeNumber}nd place",
336				"ordinalCategory=few": "You finished in {placeNumber}rd place",
337				"ordinalCategory=*": "You finished in {placeNumber}th place"
338			}
339		}
340	]
341}
342\`\`\`
343
344> [!TIP]
345> Ordinal category names (\`one\`, \`two\`, \`few\`, \`other\`, etc.) follow \`Intl.PluralRules\` for the active locale.
346
347Pluralization is also supported. You can define a variable in your message and then use it in the selector.
348
349| Inputs  | Condition         | Message              |
350| ------- | ----------------- | -------------------- |
351| count=1 | countPlural=one   | There is one cat.    |
352| count>1 | countPlural=other | There are many cats. |
353
354> [!TIP]
355> Read the \`local countPlural = count: plural\` syntax as "create a local variable \`countPlural\` that equals \`plural(count)\`".
356
357\`\`\`json
358{
359	"some_happy_cat": [
360		{
361			"declarations": ["input count", "local countPlural = count: plural"],
362			"selectors": ["countPlural"],
363			"match": {
364				"countPlural=one": "There is one cat.",
365				"countPlural=other": "There are many cats."
366			}
367		}
368	]
369}
370\`\`\`
371
372## Troubleshooting
373
374### Messages not appearing
375
376- **File not found**: Missing translation files are silently ignored. Verify that your \`pathPattern\` matches the actual file paths and that the \`{locale}\` placeholder resolves correctly.
377- **Wrong locale**: Ensure the locale in the filename matches one of the configured \`locales\` in your \`settings.json\`.
378
379### JSON parse errors
380
381- **Trailing commas**: JSON does not allow trailing commas. Remove the comma after the last property in objects and arrays.
382- **Comments**: Standard JSON does not support comments. The examples in this README use \`//\` comments for illustration only — remove them in actual files.
383
384### "Multiple variants for language tag" error
385
386Each message can only have one variant per locale within a single match condition set. If you need multiple variants, use selectors to differentiate them:
387
388\`\`\`json
389{
390	"message": [
391		{
392			"selectors": ["platform"],
393			"match": {
394				"platform=ios": "iOS version",
395				"platform=android": "Android version"
396			}
397		}
398	]
399}
400\`\`\`
401
402### Nested messages not working
403
404- Nesting requires plugin v4+ and SDK v2+. Check the [Version Compatibility](#version-compatibility) table.
405- Maximum nesting depth is 5 levels. Flatten deeper structures using dot notation in the key name.
406
407### Complex messages not recognized
408
409Complex messages (with variants/pluralization) must be wrapped in an array:
410
411\`\`\`json
412{
413	"wrong": { "match": { "count=one": "One" } },
414	"correct": [{ "match": { "count=one": "One" } }]
415}
416\`\`\`
417`;export{n as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.