@saykit/format-json 0.0.0-beta-20260720035025 → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,46 +1,4 @@
1
- {
2
- "name": "@saykit/format-json",
3
- "version": "0.0.0-beta-20260720035025",
4
- "description": "JSON file formatter for saykit",
5
- "keywords": [
6
- "formatter",
7
- "i18n",
8
- "json",
9
- "saykit"
10
- ],
11
- "homepage": "https://github.com/k0d13/saykit#readme",
12
- "bugs": {
13
- "url": "https://github.com/k0d13/saykit/issues"
14
- },
15
- "license": "MIT",
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/k0d13/saykit.git",
19
- "directory": "packages/format-json"
20
- },
21
- "files": [
22
- "dist",
23
- "!dist/**/*.map"
24
- ],
25
- "type": "module",
26
- "exports": {
27
- ".": {
28
- "types": "./dist/formatter.d.mts",
29
- "default": "./dist/formatter.mjs"
30
- }
31
- },
32
- "publishConfig": {
33
- "access": "public",
34
- "provenance": true
35
- },
36
- "devDependencies": {
37
- "@saykit/config": "^0.0.0-beta-20260720035025"
38
- },
39
- "peerDependencies": {
40
- "@saykit/config": "0.0.0-beta-20260720035025"
41
- },
42
- "scripts": {
43
- "check": "tsc --noEmit",
44
- "build": "tsdown"
45
- }
1
+ {
2
+ "name": "@saykit/format-json",
3
+ "version": "0.0.0"
46
4
  }
package/README.md DELETED
@@ -1,80 +0,0 @@
1
- # @saykit/format-json
2
-
3
- > JSON file formatter for [SayKit](https://saykit.js.org).
4
-
5
- [![Coverage](https://codecov.io/gh/k0d13/saykit/graph/badge.svg?flag=format-json)](https://codecov.io/gh/k0d13/saykit?flags%5B0%5D=format-json)
6
-
7
- Writes one JSON catalogue per locale — the de facto format for web i18n
8
- (react-intl, FormatJS, i18next, …). Reach for this instead of
9
- [`@saykit/format-po`](https://github.com/k0d13/saykit/tree/main/packages/format-po)
10
- when you want plain JSON bundles with no Gettext tooling.
11
-
12
- ## Install
13
-
14
- ```sh
15
- pnpm add -D @saykit/format-json
16
- ```
17
-
18
- ## Usage
19
-
20
- ```ts title="saykit.config.ts"
21
- import json from '@saykit/format-json';
22
-
23
- // inside a bucket:
24
- formatter: json();
25
- ```
26
-
27
- Each catalogue is a pretty-printed, flat map keyed by the message id (falling
28
- back to a stable content hash when a message has no id — the same key the
29
- runtime resolves), with the translation as the value:
30
-
31
- ```json
32
- {
33
- "greeting": "Hello"
34
- }
35
- ```
36
-
37
- ICU MessageFormat strings are stored verbatim as JSON string values — JSON
38
- escaping is handled for you.
39
-
40
- ### Dialects
41
-
42
- The plain layout above is just `{ key: value }`, so it drops comments, context,
43
- and source references. Pass `dialect` to keep them using a richer — but still
44
- standard — JSON layout:
45
-
46
- - `dialect: 'arb'` — [Application Resource Bundle](https://github.com/google/app-resource-bundle)
47
- (Flutter/Dart `intl`). Strings stay flat; each key's metadata lives in a
48
- sibling `@key` object.
49
- - `dialect: 'webextension'` — the Chrome/WebExtension `messages.json` shape,
50
- where each key maps to a `{ message, description }` object.
51
-
52
- ```ts
53
- formatter: json({ dialect: 'arb' });
54
- ```
55
-
56
- ```json title="ARB"
57
- {
58
- "@@locale": "fr",
59
- "greeting": "Bonjour",
60
- "@greeting": {
61
- "description": "A friendly hello",
62
- "x-saykit-context": "formal",
63
- "x-saykit-references": ["src/app.ts:10"]
64
- }
65
- }
66
- ```
67
-
68
- Both formats carry translator comments in their native `description` field.
69
- Context and source references have no standard slot, so SayKit round-trips them
70
- through `x-saykit-context` / `x-saykit-references` extension fields — other
71
- tooling reads the `description` and safely ignores the rest.
72
-
73
- > [!NOTE]
74
- > JSON catalogues are keyed by message id, so give your messages explicit ids to
75
- > get stable, human-readable keys. Unlike the PO formatter, JSON does not carry
76
- > source references, comments, or contexts — it is a lean runtime format.
77
-
78
- ## Documentation
79
-
80
- See [saykit.js.org/core-concepts/formats](https://saykit.js.org/core-concepts/formats).
@@ -1,26 +0,0 @@
1
- import { Formatter } from "@saykit/config";
2
-
3
- //#region src/formatter.d.ts
4
- type Dialect = 'arb' | 'webextension';
5
- interface FormatterOptions {
6
- /**
7
- * The JSON dialect to write. A dialect other than the default plain
8
- * `{ key: value }` map carries message metadata (comments, context, source
9
- * references) using a richer — but still standard — layout.
10
- *
11
- * - `'arb'` — Application Resource Bundle (Flutter/Dart `intl`). Strings stay
12
- * flat and each key's metadata lives in a sibling `@key` object.
13
- * - `'webextension'` — the Chrome/WebExtension `messages.json` shape, where
14
- * each key maps to a `{ message, description }` object.
15
- *
16
- * Both carry translator comments in their native `description` field. Context
17
- * and source references have no standard slot, so they round-trip through
18
- * `x-saykit-context` / `x-saykit-references` extension fields that other
19
- * tooling safely ignores. Omit for a plain flat catalogue, which carries no
20
- * metadata.
21
- */
22
- dialect?: Dialect;
23
- }
24
- declare function createJsonFormatter(options?: FormatterOptions): Formatter;
25
- //#endregion
26
- export { type Dialect, type FormatterOptions, createJsonFormatter as default };
@@ -1,79 +0,0 @@
1
- import { generateHash } from "@saykit/config/features/messages";
2
- //#region src/formatter.ts
3
- /**
4
- * Derive a catalogue key the same way the runtime and the rest of SayKit do:
5
- * an explicit id when present, otherwise a stable hash of the message and its
6
- * context. Keying by the raw message text instead would not match the hashed
7
- * key the runtime looks up, and would collide for same-text/different-context
8
- * messages.
9
- */
10
- function messageKey(message) {
11
- return message.id ?? generateHash(message.message, message.context);
12
- }
13
- const CONTEXT_FIELD = "x-saykit-context";
14
- const REFERENCES_FIELD = "x-saykit-references";
15
- function isRecord(value) {
16
- return typeof value === "object" && value !== null && !Array.isArray(value);
17
- }
18
- /** Build the metadata attributes shared by the ARB and WebExtension layouts. */
19
- function toAttributes(message) {
20
- const attributes = {};
21
- if (message.comments.length) attributes.description = message.comments.join("\n");
22
- if (message.context) attributes[CONTEXT_FIELD] = message.context;
23
- if (message.references.length) attributes[REFERENCES_FIELD] = message.references;
24
- return attributes;
25
- }
26
- /** Reconstruct a {@link Message} from a key, its value, and optional metadata. */
27
- function toMessage(key, value, attributes) {
28
- const description = attributes && typeof attributes.description === "string" ? attributes.description : "";
29
- const context = attributes && typeof attributes[CONTEXT_FIELD] === "string" ? attributes[CONTEXT_FIELD] : void 0;
30
- const references = attributes && Array.isArray(attributes[REFERENCES_FIELD]) ? attributes[REFERENCES_FIELD].filter((r) => typeof r === "string") : [];
31
- return {
32
- id: key,
33
- message: value,
34
- translation: value,
35
- context,
36
- comments: description ? description.split("\n") : [],
37
- references
38
- };
39
- }
40
- function createJsonFormatter(options = {}) {
41
- return {
42
- extension: ".json",
43
- parse(content) {
44
- const data = JSON.parse(content);
45
- if (options.dialect === "arb") return Object.entries(data).filter(([key, value]) => !key.startsWith("@") && typeof value === "string").map(([key, value]) => toMessage(key, value, data[`@${key}`]));
46
- if (options.dialect === "webextension") return Object.entries(data).filter(([, value]) => isRecord(value) && typeof value.message === "string").map(([key, value]) => {
47
- const entry = value;
48
- return toMessage(key, entry.message, entry);
49
- });
50
- return Object.entries(data).filter(([, value]) => typeof value === "string").map(([key, value]) => toMessage(key, value));
51
- },
52
- stringify(messages, { locale }) {
53
- const sorted = [...messages].sort((a, b) => messageKey(a).localeCompare(messageKey(b)));
54
- if (options.dialect === "arb") {
55
- const catalogue = { "@@locale": locale };
56
- for (const message of sorted) {
57
- const key = messageKey(message);
58
- catalogue[key] = message.translation ?? "";
59
- const attributes = toAttributes(message);
60
- if (Object.keys(attributes).length) catalogue[`@${key}`] = attributes;
61
- }
62
- return `${JSON.stringify(catalogue, null, 2)}\n`;
63
- }
64
- if (options.dialect === "webextension") {
65
- const catalogue = {};
66
- for (const message of sorted) catalogue[messageKey(message)] = {
67
- message: message.translation ?? "",
68
- ...toAttributes(message)
69
- };
70
- return `${JSON.stringify(catalogue, null, 2)}\n`;
71
- }
72
- const catalogue = {};
73
- for (const message of sorted) catalogue[messageKey(message)] = message.translation ?? "";
74
- return `${JSON.stringify(catalogue, null, 2)}\n`;
75
- }
76
- };
77
- }
78
- //#endregion
79
- export { createJsonFormatter as default };