@lionrapid/cli 0.1.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.
Files changed (130) hide show
  1. package/README.md +159 -0
  2. package/dist/adapters/android-xml.d.ts +20 -0
  3. package/dist/adapters/android-xml.d.ts.map +1 -0
  4. package/dist/adapters/android-xml.js +101 -0
  5. package/dist/adapters/android-xml.js.map +1 -0
  6. package/dist/adapters/arb.d.ts +17 -0
  7. package/dist/adapters/arb.d.ts.map +1 -0
  8. package/dist/adapters/arb.js +45 -0
  9. package/dist/adapters/arb.js.map +1 -0
  10. package/dist/adapters/content.d.ts +13 -0
  11. package/dist/adapters/content.d.ts.map +1 -0
  12. package/dist/adapters/content.js +24 -0
  13. package/dist/adapters/content.js.map +1 -0
  14. package/dist/adapters/index.d.ts +11 -0
  15. package/dist/adapters/index.d.ts.map +1 -0
  16. package/dist/adapters/index.js +34 -0
  17. package/dist/adapters/index.js.map +1 -0
  18. package/dist/adapters/ios-strings.d.ts +15 -0
  19. package/dist/adapters/ios-strings.d.ts.map +1 -0
  20. package/dist/adapters/ios-strings.js +54 -0
  21. package/dist/adapters/ios-strings.js.map +1 -0
  22. package/dist/adapters/json.d.ts +12 -0
  23. package/dist/adapters/json.d.ts.map +1 -0
  24. package/dist/adapters/json.js +31 -0
  25. package/dist/adapters/json.js.map +1 -0
  26. package/dist/adapters/nested.d.ts +9 -0
  27. package/dist/adapters/nested.d.ts.map +1 -0
  28. package/dist/adapters/nested.js +37 -0
  29. package/dist/adapters/nested.js.map +1 -0
  30. package/dist/adapters/po.d.ts +23 -0
  31. package/dist/adapters/po.d.ts.map +1 -0
  32. package/dist/adapters/po.js +197 -0
  33. package/dist/adapters/po.js.map +1 -0
  34. package/dist/adapters/types.d.ts +46 -0
  35. package/dist/adapters/types.d.ts.map +1 -0
  36. package/dist/adapters/types.js +16 -0
  37. package/dist/adapters/types.js.map +1 -0
  38. package/dist/adapters/xcstrings.d.ts +20 -0
  39. package/dist/adapters/xcstrings.d.ts.map +1 -0
  40. package/dist/adapters/xcstrings.js +77 -0
  41. package/dist/adapters/xcstrings.js.map +1 -0
  42. package/dist/adapters/xliff.d.ts +22 -0
  43. package/dist/adapters/xliff.d.ts.map +1 -0
  44. package/dist/adapters/xliff.js +83 -0
  45. package/dist/adapters/xliff.js.map +1 -0
  46. package/dist/adapters/yaml.d.ts +16 -0
  47. package/dist/adapters/yaml.d.ts.map +1 -0
  48. package/dist/adapters/yaml.js +51 -0
  49. package/dist/adapters/yaml.js.map +1 -0
  50. package/dist/api-client.d.ts +123 -0
  51. package/dist/api-client.d.ts.map +1 -0
  52. package/dist/api-client.js +236 -0
  53. package/dist/api-client.js.map +1 -0
  54. package/dist/cli.d.ts +24 -0
  55. package/dist/cli.d.ts.map +1 -0
  56. package/dist/cli.js +365 -0
  57. package/dist/cli.js.map +1 -0
  58. package/dist/commands/context.d.ts +38 -0
  59. package/dist/commands/context.d.ts.map +1 -0
  60. package/dist/commands/context.js +70 -0
  61. package/dist/commands/context.js.map +1 -0
  62. package/dist/commands/doctor.d.ts +27 -0
  63. package/dist/commands/doctor.d.ts.map +1 -0
  64. package/dist/commands/doctor.js +87 -0
  65. package/dist/commands/doctor.js.map +1 -0
  66. package/dist/commands/init.d.ts +36 -0
  67. package/dist/commands/init.d.ts.map +1 -0
  68. package/dist/commands/init.js +111 -0
  69. package/dist/commands/init.js.map +1 -0
  70. package/dist/commands/pull.d.ts +35 -0
  71. package/dist/commands/pull.d.ts.map +1 -0
  72. package/dist/commands/pull.js +123 -0
  73. package/dist/commands/pull.js.map +1 -0
  74. package/dist/commands/push.d.ts +37 -0
  75. package/dist/commands/push.d.ts.map +1 -0
  76. package/dist/commands/push.js +63 -0
  77. package/dist/commands/push.js.map +1 -0
  78. package/dist/commands/scan.d.ts +46 -0
  79. package/dist/commands/scan.d.ts.map +1 -0
  80. package/dist/commands/scan.js +68 -0
  81. package/dist/commands/scan.js.map +1 -0
  82. package/dist/commands/snapshot.d.ts +48 -0
  83. package/dist/commands/snapshot.d.ts.map +1 -0
  84. package/dist/commands/snapshot.js +52 -0
  85. package/dist/commands/snapshot.js.map +1 -0
  86. package/dist/commands/types.d.ts +38 -0
  87. package/dist/commands/types.d.ts.map +1 -0
  88. package/dist/commands/types.js +82 -0
  89. package/dist/commands/types.js.map +1 -0
  90. package/dist/commands/validate.d.ts +28 -0
  91. package/dist/commands/validate.d.ts.map +1 -0
  92. package/dist/commands/validate.js +48 -0
  93. package/dist/commands/validate.js.map +1 -0
  94. package/dist/config.d.ts +80 -0
  95. package/dist/config.d.ts.map +1 -0
  96. package/dist/config.js +279 -0
  97. package/dist/config.js.map +1 -0
  98. package/dist/exit-codes.d.ts +26 -0
  99. package/dist/exit-codes.d.ts.map +1 -0
  100. package/dist/exit-codes.js +42 -0
  101. package/dist/exit-codes.js.map +1 -0
  102. package/dist/index.d.ts +8 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +8 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/output.d.ts +29 -0
  107. package/dist/output.d.ts.map +1 -0
  108. package/dist/output.js +38 -0
  109. package/dist/output.js.map +1 -0
  110. package/dist/scan/canonical.d.ts +33 -0
  111. package/dist/scan/canonical.d.ts.map +1 -0
  112. package/dist/scan/canonical.js +57 -0
  113. package/dist/scan/canonical.js.map +1 -0
  114. package/dist/scan/discover.d.ts +13 -0
  115. package/dist/scan/discover.d.ts.map +1 -0
  116. package/dist/scan/discover.js +95 -0
  117. package/dist/scan/discover.js.map +1 -0
  118. package/dist/scan/scanner.d.ts +32 -0
  119. package/dist/scan/scanner.d.ts.map +1 -0
  120. package/dist/scan/scanner.js +137 -0
  121. package/dist/scan/scanner.js.map +1 -0
  122. package/dist/types-gen/extract.d.ts +22 -0
  123. package/dist/types-gen/extract.d.ts.map +1 -0
  124. package/dist/types-gen/extract.js +148 -0
  125. package/dist/types-gen/extract.js.map +1 -0
  126. package/dist/types-gen/generate.d.ts +10 -0
  127. package/dist/types-gen/generate.d.ts.map +1 -0
  128. package/dist/types-gen/generate.js +39 -0
  129. package/dist/types-gen/generate.js.map +1 -0
  130. package/package.json +71 -0
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # @lionrapid/cli
2
+
3
+ The LionRapid command-line tool. Syncs translation files in many formats
4
+ (iOS `.strings`/`.xcstrings`, Android `strings.xml`, Flutter `.arb`, generic
5
+ JSON, YAML, PO) with the LionRapid backend, and generates type-safe TypeScript
6
+ keys from your translations.
7
+
8
+ This is the **non-web integration wedge**: it reaches platforms the web
9
+ surfaces (WordPress, edge proxy, JS snippet, React SDK) can't — mobile apps,
10
+ backend services, static-site generators, and CI/CD — by speaking the existing
11
+ `/api/integrations/*` REST surface instead of the DOM.
12
+
13
+ ## Status
14
+
15
+ Phase 1 (file-sync dev-loop) is implemented. Command surface:
16
+
17
+ | Command | Status | What it does |
18
+ | ----------------- | ------ | ------------------------------------------------------------------- |
19
+ | `init` | ✅ | Scaffold a `lionrapid.yml` (detects Flutter/iOS/Android/Rails/JS) |
20
+ | `push` | ✅ | Upload source strings to the backend (server handles MT) |
21
+ | `pull [--types]` | ✅ | Download translations into target files; optionally regen types |
22
+ | `validate [--ci]` | ✅ | Check local files vs the backend for missing/orphan/empty keys |
23
+ | `types generate` | ✅ | Generate a typed `.d.ts` from your source files (offline) |
24
+ | `types check` | ✅ | Report ICU issues in your source translations |
25
+ | `scan [--json]` | ✅ | Find user-facing strings in source → inventory keyed by auto_key |
26
+ | `scan --push` | ✅ | Seed the safe `wrap` candidates to the backend under their auto_key |
27
+ | `snapshot` | ✅ | Write offline-resilience bundles (raw ContentUnit) per locale/ns |
28
+ | `doctor` | ✅ | Preflight: config, credentials, server connectivity, source files |
29
+
30
+ Formats: JSON, YAML, PO (incl. plurals + `msgctxt`), Flutter `.arb`, iOS
31
+ `.strings` + `.xcstrings`, Android `strings.xml`, XLIFF (1.2/2.0, CAT-tool
32
+ interchange).
33
+
34
+ **Multiple namespaces:** give any `files` entry its own `namespace` to map
35
+ different file trees to different backend namespaces (defaults to the top-level
36
+ `namespace`). Each is pushed/pulled/validated independently, so the same key name
37
+ in two namespaces never collides:
38
+
39
+ ```yaml
40
+ namespace: app
41
+ files:
42
+ - { format: json, source: common/en.json, target: common/{locale}.json, namespace: common }
43
+ - { format: json, source: app/en.json, target: app/{locale}.json } # → namespace: app
44
+ ```
45
+
46
+ ## Reliability & automation
47
+
48
+ Built for CI and AI agents — every networked command is resilient and scriptable.
49
+
50
+ - **`--json`** (all commands): emit one result envelope `{ ok, command, … }` on
51
+ **stdout**; human prose goes to **stderr**. On failure, a structured error
52
+ envelope `{ ok: false, error: { type, message, status? }, exitCode }` is written
53
+ to stdout. Parse stdout, read stderr.
54
+ - **`--dry-run`** (`push`, `pull`): report exactly what _would_ happen — counts,
55
+ keys, target files — without any network mutation (`push`) or file write
56
+ (`pull`). `pull --dry-run` still does the read-only fetch so the counts are real.
57
+ - **Exit codes** (stable, gate CI on them): `0` ok · `1` unexpected · `2`
58
+ validation/config · `3` auth · `4` network/5xx · `5` partial.
59
+ - **Automatic retries**: transient failures (network error, `5xx`, `429`) are
60
+ retried with exponential backoff + jitter; a `429` honors `Retry-After`. Tune
61
+ with `LIONRAPID_RETRIES` (default `3`; `0` disables). Writes are safe to retry —
62
+ the integration endpoints upsert by composite key.
63
+ - **Concurrent `pull`**: namespace fetches fan out with bounded concurrency
64
+ (`LIONRAPID_CONCURRENCY`, default `6`) and show interactive progress on a TTY.
65
+ Output stays deterministic — only the network fan-out is parallelized.
66
+ - **`doctor`**: one command to verify config validity, credential resolution,
67
+ backend reachability/auth, and that declared source files exist — each checked
68
+ independently so you get an actionable list, not a stack trace.
69
+
70
+ ## Offline resilience (`snapshot`)
71
+
72
+ `lionrapid snapshot` writes the **raw `ContentUnit` bundle** (`{ meta, translations }`)
73
+ per locale/namespace, byte-shaped like the API response — **bypassing the format
74
+ adapters** (which flatten to display strings and drop the `styles` map). Bundle
75
+ these with your app and load them via the runtime so translations keep working
76
+ **with the server down**:
77
+
78
+ ```ts
79
+ import bundle from './snapshots/es.app.json' assert { type: 'json' };
80
+ import {
81
+ LionRapidBuilder,
82
+ MemoryRepository,
83
+ StaticRepository,
84
+ } from '@lionrapid/core';
85
+
86
+ const core = await LionRapidBuilder.init({
87
+ defaultLocale: 'es',
88
+ namespace: 'app',
89
+ })
90
+ .use(new MemoryRepository({ enabled: true }))
91
+ .useStaticBundle({ es: { app: bundle.translations } }) // offline layer (memory → static → network)
92
+ .build();
93
+ core.t('welcome'); // works offline, styles intact
94
+ ```
95
+
96
+ Node apps can read a snapshot directory at runtime via
97
+ `new FileRepository('./snapshots')` from `@lionrapid/storage/node`.
98
+
99
+ ## No-library localization (`scan`)
100
+
101
+ For a codebase with **no i18n library** (hardcoded strings, no `t()`), `scan`
102
+ finds user-facing strings via the TypeScript AST over a finite allowlist of UI
103
+ sinks (JSX text + `placeholder`/`alt`/`aria-label`/…), classifies them, and keys
104
+ each by **content-hash `auto_key`** — the same key the web edge proxy / DOM
105
+ auto-detect produces, so a string already translated on your website is reused
106
+ in your app.
107
+
108
+ - `action: wrap` — static text/attributes (deterministic, safe).
109
+ - `action: review` — interpolation / inline markup / dynamic values (left for a
110
+ human or AI agent to author correctly).
111
+
112
+ `scan --json` emits a machine-readable inventory on stdout (human prose on
113
+ stderr) so an AI coding agent can drive the judgment-heavy wrapping while the CLI
114
+ owns the correctness-critical parts (the `auto_key` hash, file writes, sync).
115
+ `scan --push` seeds the safe candidates into the translation memory.
116
+
117
+ ### Wrapping (done by you / your agent)
118
+
119
+ The CLI intentionally does **not** rewrite your source. It hands you the
120
+ inventory — each candidate carries its `autoKey`, and the result carries the
121
+ `namespace` — and you (or your AI coding agent, e.g. Claude Code / Cursor) wrap
122
+ using the SDK, which also lets the agent handle the `review` cases (interpolation,
123
+ plurals) a deterministic codemod can't:
124
+
125
+ ```tsx
126
+ // inventory: { namespace: "app", autoKey: "auto_2d0f6b8300be", text: "Add to cart", sink: "jsx-text" }
127
+ <Text>Add to cart</Text>
128
+ // →
129
+ <Text><Trans namespace="app" i18nKey="auto_2d0f6b8300be">Add to cart</Trans></Text>
130
+
131
+ // inventory: { autoKey: "auto_13348442cc6a", text: "Search", sink: "jsx-attr:placeholder" }
132
+ placeholder="Search" → placeholder={t('app:auto_13348442cc6a', 'Search')}
133
+ ```
134
+
135
+ Because the key is the same content-hash the web produces, any of these strings
136
+ already translated on your website are translated in the app for free.
137
+
138
+ ## Usage
139
+
140
+ ```bash
141
+ lionrapid init --project <id> --targets es,fr
142
+ lionrapid push
143
+ lionrapid pull --types
144
+ lionrapid validate --ci
145
+
146
+ # no-library localization
147
+ lionrapid scan --json # inventory of discovered strings (auto_key-keyed)
148
+ lionrapid scan --push # seed the safe candidates into the TM
149
+ ```
150
+
151
+ The API key is read from `LIONRAPID_API_KEY` (or `~/.lionrapid/credentials`),
152
+ never from the committed `lionrapid.yml`.
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ npm run build -w packages/cli
158
+ npm test -w packages/cli
159
+ ```
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Android `res/values-<locale>/strings.xml` adapter (one locale per file).
3
+ *
4
+ * Handles `<string>` and `<plurals>` (quantity items flatten to `name[quantity]`
5
+ * keys, e.g. `num_items[other]`). `translatable="false"` strings are skipped.
6
+ * Inner text is XML-entity-decoded and Android-unescaped (`\'`, `\"`, `\n`,
7
+ * `\t`); inline markup (`<b>`, `<i>`, …) is left intact so it flows through
8
+ * core's ContentUnit pipeline.
9
+ *
10
+ * Phase-1 limitations (documented intentionally): styled strings whose markup
11
+ * carries quoted attributes (e.g. `<a href="…">`) are uncommon in Android and
12
+ * not round-trip-guaranteed; CDATA is unwrapped to its literal contents.
13
+ */
14
+ import type { FormatAdapter, SerializeOptions, TranslationEntry } from './types.js';
15
+ export declare class AndroidXmlAdapter implements FormatAdapter {
16
+ readonly format: "android-xml";
17
+ parse(content: string): TranslationEntry[];
18
+ serialize(entries: TranslationEntry[], _options?: SerializeOptions): string;
19
+ }
20
+ //# sourceMappingURL=android-xml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"android-xml.d.ts","sourceRoot":"","sources":["../../src/adapters/android-xml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAmCpF,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,MAAM,gBAA0B;IAEzC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE;IA8B1C,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,MAAM;CAwC5E"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Android `res/values-<locale>/strings.xml` adapter (one locale per file).
3
+ *
4
+ * Handles `<string>` and `<plurals>` (quantity items flatten to `name[quantity]`
5
+ * keys, e.g. `num_items[other]`). `translatable="false"` strings are skipped.
6
+ * Inner text is XML-entity-decoded and Android-unescaped (`\'`, `\"`, `\n`,
7
+ * `\t`); inline markup (`<b>`, `<i>`, …) is left intact so it flows through
8
+ * core's ContentUnit pipeline.
9
+ *
10
+ * Phase-1 limitations (documented intentionally): styled strings whose markup
11
+ * carries quoted attributes (e.g. `<a href="…">`) are uncommon in Android and
12
+ * not round-trip-guaranteed; CDATA is unwrapped to its literal contents.
13
+ */
14
+ import { toContentUnit, fromContentUnit } from './content.js';
15
+ const STRING_RE = /<string\s+name="([^"]+)"([^>]*)>([\s\S]*?)<\/string>/g;
16
+ const PLURALS_RE = /<plurals\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/plurals>/g;
17
+ const ITEM_RE = /<item\s+quantity="([^"]+)"[^>]*>([\s\S]*?)<\/item>/g;
18
+ const PLURAL_KEY_RE = /^(.+)\[(\w+)\]$/;
19
+ function decodeXmlEntities(s) {
20
+ return s
21
+ .replace(/&lt;/g, '<')
22
+ .replace(/&gt;/g, '>')
23
+ .replace(/&quot;/g, '"')
24
+ .replace(/&apos;/g, "'")
25
+ .replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)))
26
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)))
27
+ .replace(/&amp;/g, '&');
28
+ }
29
+ function decodeInner(raw) {
30
+ const noCdata = raw.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1');
31
+ return decodeXmlEntities(noCdata).replace(/\\(.)/g, (_, c) => {
32
+ if (c === 'n')
33
+ return '\n';
34
+ if (c === 't')
35
+ return '\t';
36
+ return c; // \' → ', \" → ", \\ → \
37
+ });
38
+ }
39
+ function encodeInner(text, hasStyles) {
40
+ let s = text.replace(/&(?!(amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);)/g, '&amp;');
41
+ if (!hasStyles)
42
+ s = s.replace(/</g, '&lt;').replace(/>/g, '&gt;');
43
+ s = s.replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\t/g, '\\t');
44
+ return s;
45
+ }
46
+ export class AndroidXmlAdapter {
47
+ format = 'android-xml';
48
+ parse(content) {
49
+ const hits = [];
50
+ for (const m of content.matchAll(STRING_RE)) {
51
+ const [, name, attrs, inner] = m;
52
+ if (/translatable\s*=\s*"false"/.test(attrs))
53
+ continue;
54
+ hits.push({
55
+ index: m.index,
56
+ entries: [{ key: name, value: toContentUnit(decodeInner(inner)) }],
57
+ });
58
+ }
59
+ for (const m of content.matchAll(PLURALS_RE)) {
60
+ const [, name, body] = m;
61
+ const entries = [];
62
+ for (const im of body.matchAll(ITEM_RE)) {
63
+ const [, quantity, inner] = im;
64
+ entries.push({ key: `${name}[${quantity}]`, value: toContentUnit(decodeInner(inner)) });
65
+ }
66
+ hits.push({ index: m.index, entries });
67
+ }
68
+ hits.sort((a, b) => a.index - b.index);
69
+ return hits.flatMap((h) => h.entries);
70
+ }
71
+ serialize(entries, _options) {
72
+ const lines = ['<?xml version="1.0" encoding="utf-8"?>', '<resources>'];
73
+ for (let i = 0; i < entries.length; i += 1) {
74
+ const entry = entries[i];
75
+ const pluralMatch = entry.key.match(PLURAL_KEY_RE);
76
+ if (pluralMatch) {
77
+ const base = pluralMatch[1];
78
+ lines.push(` <plurals name="${base}">`);
79
+ // Consume consecutive items of the same plural base.
80
+ while (i < entries.length) {
81
+ const current = entries[i];
82
+ const cm = current.key.match(PLURAL_KEY_RE);
83
+ if (!cm || cm[1] !== base)
84
+ break;
85
+ const hasStyles = Object.keys(current.value.styles).length > 0;
86
+ lines.push(` <item quantity="${cm[2]}">${encodeInner(fromContentUnit(current.value), hasStyles)}</item>`);
87
+ i += 1;
88
+ }
89
+ i -= 1; // outer loop will increment
90
+ lines.push(' </plurals>');
91
+ }
92
+ else {
93
+ const hasStyles = Object.keys(entry.value.styles).length > 0;
94
+ lines.push(` <string name="${entry.key}">${encodeInner(fromContentUnit(entry.value), hasStyles)}</string>`);
95
+ }
96
+ }
97
+ lines.push('</resources>', '');
98
+ return lines.join('\n');
99
+ }
100
+ }
101
+ //# sourceMappingURL=android-xml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"android-xml.js","sourceRoot":"","sources":["../../src/adapters/android-xml.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Flutter ARB adapter (Application Resource Bundle — JSON).
3
+ *
4
+ * ARB files are JSON where translation keys map to strings and metadata lives
5
+ * under `@`-prefixed keys (`@@locale`, `@key: { description, placeholders }`).
6
+ * Per Flutter's `gen-l10n`, placeholder/description metadata lives in the
7
+ * *template* ARB (the source); target ARBs carry only `@@locale` + translated
8
+ * strings. So we parse real (non-`@`) entries and, on serialize, write
9
+ * `@@locale` followed by key→value pairs.
10
+ */
11
+ import type { FormatAdapter, SerializeOptions, TranslationEntry } from './types.js';
12
+ export declare class ArbAdapter implements FormatAdapter {
13
+ readonly format: "arb";
14
+ parse(content: string): TranslationEntry[];
15
+ serialize(entries: TranslationEntry[], options?: SerializeOptions): string;
16
+ }
17
+ //# sourceMappingURL=arb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arb.d.ts","sourceRoot":"","sources":["../../src/adapters/arb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGpF,qBAAa,UAAW,YAAW,aAAa;IAC9C,QAAQ,CAAC,MAAM,QAAkB;IAEjC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAoB1C,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM;CAM3E"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Flutter ARB adapter (Application Resource Bundle — JSON).
3
+ *
4
+ * ARB files are JSON where translation keys map to strings and metadata lives
5
+ * under `@`-prefixed keys (`@@locale`, `@key: { description, placeholders }`).
6
+ * Per Flutter's `gen-l10n`, placeholder/description metadata lives in the
7
+ * *template* ARB (the source); target ARBs carry only `@@locale` + translated
8
+ * strings. So we parse real (non-`@`) entries and, on serialize, write
9
+ * `@@locale` followed by key→value pairs.
10
+ */
11
+ import { toContentUnit, fromContentUnit } from './content.js';
12
+ export class ArbAdapter {
13
+ format = 'arb';
14
+ parse(content) {
15
+ const trimmed = content.trim();
16
+ if (trimmed === '')
17
+ return [];
18
+ let data;
19
+ try {
20
+ data = JSON.parse(trimmed);
21
+ }
22
+ catch (err) {
23
+ throw new Error(`Invalid ARB (JSON): ${err instanceof Error ? err.message : String(err)}`);
24
+ }
25
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
26
+ throw new Error('Expected an ARB object.');
27
+ }
28
+ const out = [];
29
+ for (const [key, value] of Object.entries(data)) {
30
+ if (key.startsWith('@'))
31
+ continue; // @@locale and @key metadata
32
+ out.push({ key, value: toContentUnit(String(value)) });
33
+ }
34
+ return out;
35
+ }
36
+ serialize(entries, options) {
37
+ const obj = {};
38
+ if (options?.locale)
39
+ obj['@@locale'] = options.locale;
40
+ for (const entry of entries)
41
+ obj[entry.key] = fromContentUnit(entry.value);
42
+ return `${JSON.stringify(obj, null, 2)}\n`;
43
+ }
44
+ }
45
+ //# sourceMappingURL=arb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arb.js","sourceRoot":"","sources":["../../src/adapters/arb.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE9D,MAAM,OAAO,UAAU;IACZ,MAAM,GAAG,KAAc,CAAC;IAEjC,KAAK,CAAC,OAAe;QACnB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;QAC9B,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7F,CAAC;QACD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;QACD,MAAM,GAAG,GAAuB,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC,EAAE,CAAC;YAC3E,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS,CAAC,6BAA6B;YAChE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,SAAS,CAAC,OAA2B,EAAE,OAA0B;QAC/D,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,IAAI,OAAO,EAAE,MAAM;YAAE,GAAG,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;QACtD,KAAK,MAAM,KAAK,IAAI,OAAO;YAAE,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3E,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC;CACF"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Conversion between a format's display string and the universal `ContentUnit`.
3
+ *
4
+ * Plain strings (the overwhelming majority of mobile/file translations) are
5
+ * stored verbatim as `{ text, styles: {} }` — no entity decoding, so they
6
+ * round-trip exactly. Only when a value actually contains inline markup do we
7
+ * run core's `MarkupParser`, so inline `<b>`/`<a>` survive the round-trip and
8
+ * hash to the same `auto_key` as the web stack.
9
+ */
10
+ import { type ContentUnit } from '@lionrapid/core';
11
+ export declare function toContentUnit(text: string): ContentUnit;
12
+ export declare function fromContentUnit(unit: ContentUnit): string;
13
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/adapters/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAMjF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAGvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAGzD"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Conversion between a format's display string and the universal `ContentUnit`.
3
+ *
4
+ * Plain strings (the overwhelming majority of mobile/file translations) are
5
+ * stored verbatim as `{ text, styles: {} }` — no entity decoding, so they
6
+ * round-trip exactly. Only when a value actually contains inline markup do we
7
+ * run core's `MarkupParser`, so inline `<b>`/`<a>` survive the round-trip and
8
+ * hash to the same `auto_key` as the web stack.
9
+ */
10
+ import { MarkupParser, HTMLSerializer } from '@lionrapid/core';
11
+ const parser = new MarkupParser();
12
+ const serializer = new HTMLSerializer();
13
+ const HAS_TAG = /<[^>]+>/;
14
+ export function toContentUnit(text) {
15
+ if (!HAS_TAG.test(text))
16
+ return { text, styles: {} };
17
+ return parser.parse(text);
18
+ }
19
+ export function fromContentUnit(unit) {
20
+ if (!unit.styles || Object.keys(unit.styles).length === 0)
21
+ return unit.text;
22
+ return serializer.serialize(unit);
23
+ }
24
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/adapters/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,YAAY,EAAE,cAAc,EAAoB,MAAM,iBAAiB,CAAC;AAEjF,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;AAClC,MAAM,UAAU,GAAG,IAAI,cAAc,EAAE,CAAC;AACxC,MAAM,OAAO,GAAG,SAAS,CAAC;AAE1B,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACrD,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAiB;IAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC;IAC5E,OAAO,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Format adapter registry. Adapters are added one per increment; asking for a
3
+ * format that isn't wired up yet throws a clear, actionable error rather than
4
+ * failing obscurely.
5
+ */
6
+ import type { FileFormat } from '../config.js';
7
+ import type { FormatAdapter } from './types.js';
8
+ export declare function getAdapter(format: FileFormat): FormatAdapter;
9
+ export declare function supportedFormats(): FileFormat[];
10
+ export type { FormatAdapter, TranslationEntry, SerializeOptions } from './types.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAqBhD,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,CAQ5D;AAED,wBAAgB,gBAAgB,IAAI,UAAU,EAAE,CAE/C;AAED,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Format adapter registry. Adapters are added one per increment; asking for a
3
+ * format that isn't wired up yet throws a clear, actionable error rather than
4
+ * failing obscurely.
5
+ */
6
+ import { JsonAdapter } from './json.js';
7
+ import { ArbAdapter } from './arb.js';
8
+ import { IosStringsAdapter } from './ios-strings.js';
9
+ import { AndroidXmlAdapter } from './android-xml.js';
10
+ import { XcstringsAdapter } from './xcstrings.js';
11
+ import { YamlAdapter } from './yaml.js';
12
+ import { PoAdapter } from './po.js';
13
+ import { XliffAdapter } from './xliff.js';
14
+ const ADAPTERS = new Map([
15
+ ['json', new JsonAdapter()],
16
+ ['arb', new ArbAdapter()],
17
+ ['ios-strings', new IosStringsAdapter()],
18
+ ['android-xml', new AndroidXmlAdapter()],
19
+ ['xcstrings', new XcstringsAdapter()],
20
+ ['yaml', new YamlAdapter()],
21
+ ['po', new PoAdapter()],
22
+ ['xliff', new XliffAdapter()],
23
+ ]);
24
+ export function getAdapter(format) {
25
+ const adapter = ADAPTERS.get(format);
26
+ if (!adapter) {
27
+ throw new Error(`No adapter for format "${format}" yet. Supported: ${[...ADAPTERS.keys()].join(', ')}.`);
28
+ }
29
+ return adapter;
30
+ }
31
+ export function supportedFormats() {
32
+ return [...ADAPTERS.keys()];
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAA4B;IAClD,CAAC,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;IAC3B,CAAC,KAAK,EAAE,IAAI,UAAU,EAAE,CAAC;IACzB,CAAC,aAAa,EAAE,IAAI,iBAAiB,EAAE,CAAC;IACxC,CAAC,aAAa,EAAE,IAAI,iBAAiB,EAAE,CAAC;IACxC,CAAC,WAAW,EAAE,IAAI,gBAAgB,EAAE,CAAC;IACrC,CAAC,MAAM,EAAE,IAAI,WAAW,EAAE,CAAC;IAC3B,CAAC,IAAI,EAAE,IAAI,SAAS,EAAE,CAAC;IACvB,CAAC,OAAO,EAAE,IAAI,YAAY,EAAE,CAAC;CAC9B,CAAC,CAAC;AAEH,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,0BAA0B,MAAM,qBAAqB,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACxF,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Apple `.strings` adapter (legacy iOS/macOS localization).
3
+ *
4
+ * Format: `"key" = "value";` pairs, each optionally preceded by a C-style
5
+ * block comment. Keys and values are double-quoted with C-style escapes
6
+ * (`\"`, `\\`, `\n`, `\t`, `\r`). Each `.strings` file holds a single locale,
7
+ * which fits the source/target per-locale file model directly.
8
+ */
9
+ import type { FormatAdapter, TranslationEntry } from './types.js';
10
+ export declare class IosStringsAdapter implements FormatAdapter {
11
+ readonly format: "ios-strings";
12
+ parse(content: string): TranslationEntry[];
13
+ serialize(entries: TranslationEntry[]): string;
14
+ }
15
+ //# sourceMappingURL=ios-strings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ios-strings.d.ts","sourceRoot":"","sources":["../../src/adapters/ios-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAwBlE,qBAAa,iBAAkB,YAAW,aAAa;IACrD,QAAQ,CAAC,MAAM,gBAA0B;IAEzC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAa1C,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM;CAQ/C"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Apple `.strings` adapter (legacy iOS/macOS localization).
3
+ *
4
+ * Format: `"key" = "value";` pairs, each optionally preceded by a C-style
5
+ * block comment. Keys and values are double-quoted with C-style escapes
6
+ * (`\"`, `\\`, `\n`, `\t`, `\r`). Each `.strings` file holds a single locale,
7
+ * which fits the source/target per-locale file model directly.
8
+ */
9
+ import { toContentUnit, fromContentUnit } from './content.js';
10
+ const ENTRY_RE = /(?:\/\*\s*([\s\S]*?)\s*\*\/\s*)?"((?:\\.|[^"\\])*)"\s*=\s*"((?:\\.|[^"\\])*)"\s*;/g;
11
+ function unescape(s) {
12
+ return s.replace(/\\(.)/g, (_, c) => {
13
+ if (c === 'n')
14
+ return '\n';
15
+ if (c === 't')
16
+ return '\t';
17
+ if (c === 'r')
18
+ return '\r';
19
+ return c; // \" → " , \\ → \
20
+ });
21
+ }
22
+ function escape(s) {
23
+ return s
24
+ .replace(/\\/g, '\\\\')
25
+ .replace(/"/g, '\\"')
26
+ .replace(/\n/g, '\\n')
27
+ .replace(/\t/g, '\\t')
28
+ .replace(/\r/g, '\\r');
29
+ }
30
+ export class IosStringsAdapter {
31
+ format = 'ios-strings';
32
+ parse(content) {
33
+ const out = [];
34
+ for (const match of content.matchAll(ENTRY_RE)) {
35
+ const [, comment, rawKey, rawValue] = match;
36
+ out.push({
37
+ key: unescape(rawKey),
38
+ value: toContentUnit(unescape(rawValue)),
39
+ ...(comment ? { comment: comment.trim() } : {}),
40
+ });
41
+ }
42
+ return out;
43
+ }
44
+ serialize(entries) {
45
+ const lines = [];
46
+ for (const entry of entries) {
47
+ if (entry.comment)
48
+ lines.push(`/* ${entry.comment} */`);
49
+ lines.push(`"${escape(entry.key)}" = "${escape(fromContentUnit(entry.value))}";`);
50
+ }
51
+ return lines.length ? `${lines.join('\n')}\n` : '';
52
+ }
53
+ }
54
+ //# sourceMappingURL=ios-strings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ios-strings.js","sourceRoot":"","sources":["../../src/adapters/ios-strings.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE9D,MAAM,QAAQ,GACZ,oFAAoF,CAAC;AAEvF,SAAS,QAAQ,CAAC,CAAS;IACzB,OAAO,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE;QAC1C,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAC3B,OAAO,CAAC,CAAC,CAAC,kBAAkB;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC;SACL,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,OAAO,iBAAiB;IACnB,MAAM,GAAG,aAAsB,CAAC;IAEzC,KAAK,CAAC,OAAe;QACnB,MAAM,GAAG,GAAuB,EAAE,CAAC;QACnC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC;gBACP,GAAG,EAAE,QAAQ,CAAC,MAAO,CAAC;gBACtB,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,QAAS,CAAC,CAAC;gBACzC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,SAAS,CAAC,OAA2B;QACnC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,OAAO;gBAAE,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;YACxD,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;QACpF,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * JSON adapter — flat or nested i18n JSON (i18next, react-intl, Vue-i18n, RN,
3
+ * node backends). Nested objects flatten to dot-notation keys (`home.cta`) and
4
+ * unflatten back on serialize, preserving insertion order.
5
+ */
6
+ import type { FormatAdapter, TranslationEntry } from './types.js';
7
+ export declare class JsonAdapter implements FormatAdapter {
8
+ readonly format: "json";
9
+ parse(content: string): TranslationEntry[];
10
+ serialize(entries: TranslationEntry[]): string;
11
+ }
12
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/adapters/json.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGlE,qBAAa,WAAY,YAAW,aAAa;IAC/C,QAAQ,CAAC,MAAM,SAAmB;IAElC,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAiB1C,SAAS,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM;CAG/C"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * JSON adapter — flat or nested i18n JSON (i18next, react-intl, Vue-i18n, RN,
3
+ * node backends). Nested objects flatten to dot-notation keys (`home.cta`) and
4
+ * unflatten back on serialize, preserving insertion order.
5
+ */
6
+ import { flattenObject, unflattenEntries } from './nested.js';
7
+ export class JsonAdapter {
8
+ format = 'json';
9
+ parse(content) {
10
+ const trimmed = content.trim();
11
+ if (trimmed === '')
12
+ return [];
13
+ let data;
14
+ try {
15
+ data = JSON.parse(trimmed);
16
+ }
17
+ catch (err) {
18
+ throw new Error(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
19
+ }
20
+ if (data === null || typeof data !== 'object' || Array.isArray(data)) {
21
+ throw new Error('Expected a JSON object of translation keys.');
22
+ }
23
+ const out = [];
24
+ flattenObject(data, '', out);
25
+ return out;
26
+ }
27
+ serialize(entries) {
28
+ return `${JSON.stringify(unflattenEntries(entries), null, 2)}\n`;
29
+ }
30
+ }
31
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/adapters/json.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,OAAO,WAAW;IACb,MAAM,GAAG,MAAe,CAAC;IAElC,KAAK,CAAC,OAAe;QACnB,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;QAC9B,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,GAAG,GAAuB,EAAE,CAAC;QACnC,aAAa,CAAC,IAA+B,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACxD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,SAAS,CAAC,OAA2B;QACnC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IACnE,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared flatten/unflatten for nested key/value formats (JSON, YAML). Nested
3
+ * objects become dot-notation keys (`home.cta`) and rebuild on serialize,
4
+ * preserving insertion order. Values convert through the ContentUnit helpers.
5
+ */
6
+ import type { TranslationEntry } from './types.js';
7
+ export declare function flattenObject(obj: Record<string, unknown>, prefix: string, out: TranslationEntry[]): void;
8
+ export declare function unflattenEntries(entries: TranslationEntry[]): Record<string, unknown>;
9
+ //# sourceMappingURL=nested.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nested.d.ts","sourceRoot":"","sources":["../../src/adapters/nested.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnD,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,gBAAgB,EAAE,GACtB,IAAI,CAYN;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAerF"}