@tandem-language-exchange/content-store 1.2.0 → 1.2.2
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/README.md +81 -13
- package/dist/chunk-4DE47ZJD.js +19 -0
- package/dist/chunk-4DE47ZJD.js.map +1 -0
- package/dist/{chunk-YZSLCPN6.js → chunk-6FXNAJSI.js} +160 -14
- package/dist/chunk-6FXNAJSI.js.map +1 -0
- package/dist/{chunk-UCBZUEUP.js → chunk-BAJT7RMQ.js} +31 -69
- package/dist/chunk-BAJT7RMQ.js.map +1 -0
- package/dist/{chunk-XP3USUQC.js → chunk-EQ3DSPTJ.js} +6 -2
- package/dist/{chunk-XP3USUQC.js.map → chunk-EQ3DSPTJ.js.map} +1 -1
- package/dist/chunk-UPIQFNCR.js +17 -0
- package/dist/chunk-UPIQFNCR.js.map +1 -0
- package/dist/{chunk-VRWRAFDK.js → chunk-VKXN2SE6.js} +79 -24
- package/dist/chunk-VKXN2SE6.js.map +1 -0
- package/dist/client/fetch-content-bundles.js +7 -4
- package/dist/client/fetch-content-bundles.js.map +1 -1
- package/dist/client/fetch-merged-translation-bundles.js +46 -0
- package/dist/client/fetch-merged-translation-bundles.js.map +1 -0
- package/dist/client/fetch-translation-bundles.js +22 -11
- package/dist/client/fetch-translation-bundles.js.map +1 -1
- package/dist/client/query-cms.js +33 -0
- package/dist/client/query-cms.js.map +1 -0
- package/dist/{index-Db97SUTy.d.ts → index-PQ7XN47c.d.ts} +36 -7
- package/dist/index.d.ts +1 -1
- package/dist/node.browser.js +7 -0
- package/dist/node.browser.js.map +1 -1
- package/dist/node.d.ts +9 -3
- package/dist/node.js +237 -15
- package/dist/node.js.map +1 -1
- package/package.json +8 -5
- package/dist/chunk-UCBZUEUP.js.map +0 -1
- package/dist/chunk-VRWRAFDK.js.map +0 -1
- package/dist/chunk-YZSLCPN6.js.map +0 -1
- package/dist/client/cli.js +0 -82
- package/dist/client/cli.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ For the Express API, server CLI, scheduling, and deployment, see the [Server & C
|
|
|
7
7
|
### Package entry points
|
|
8
8
|
|
|
9
9
|
- **`@tandem-language-exchange/content-store`** (default) — **types only** at runtime. Safe to import from shared code that Next.js, Vite, or Turbopack may bundle for the browser.
|
|
10
|
-
- **`@tandem-language-exchange/content-store/node`** — `ContentStoreSDK`, `fetchCmsBundles`, `fetchTranslationBundles`, `queryCmsBundle`, `ContentStore`, `getDefaultS3RetryConfig`, and `trimDepth`. Real implementations use the filesystem and S3 and run only under the Node (`node`) export condition (Route Handlers, `getServerSideProps`, CLI, etc.).
|
|
10
|
+
- **`@tandem-language-exchange/content-store/node`** — `ContentStoreSDK`, `fetchCmsBundles`, `fetchTranslationBundles`, `fetchMergedTranslationBundles`, `queryCmsBundle`, `ContentStore`, `getDefaultS3RetryConfig`, and `trimDepth`. Real implementations use the filesystem and S3 and run only under the Node (`node`) export condition (Route Handlers, `getServerSideProps`, CLI, etc.).
|
|
11
11
|
|
|
12
12
|
For **browser** bundles (including anything imported from `_app.tsx`, client components, or shared modules that reach the client graph), bundlers should resolve the `browser` / `edge-light` conditions to a **stub** that does not import `fs`. That stub throws if you call server-only APIs; **`trimDepth` is fully implemented** and safe on the client.
|
|
13
13
|
|
|
@@ -107,20 +107,23 @@ Files are written to `outputDir` with the naming pattern `{cms}-{contentType}.js
|
|
|
107
107
|
|
|
108
108
|
## `fetchTranslationBundles(options)`
|
|
109
109
|
|
|
110
|
-
Downloads translation
|
|
110
|
+
Downloads translation objects from S3. The **sync** stores each Lingohub file **verbatim** (raw UTF-8); this call **parses** each file (JSON / `.strings` / Android XML per `src/shared/lingohub.ts`) and writes **normalized JSON** under `outputDir` (see file naming below).
|
|
111
111
|
|
|
112
112
|
```typescript
|
|
113
113
|
const files = await sdk.fetchTranslationBundles({
|
|
114
|
-
projects:
|
|
114
|
+
projects: {
|
|
115
|
+
'tandem': [], // all resources
|
|
116
|
+
'tandem-(website)': ['main', 'ai'], // only "main" and "ai" resources
|
|
117
|
+
},
|
|
115
118
|
locales: ['en', 'de'], // omit or leave empty to use the package default locale list
|
|
116
119
|
});
|
|
117
120
|
```
|
|
118
121
|
|
|
119
|
-
**Parameters:**
|
|
122
|
+
**Parameters (extends `TranslationFilterConfig`):**
|
|
120
123
|
|
|
121
124
|
| Field | Type | Description |
|
|
122
125
|
| --- | --- | --- |
|
|
123
|
-
| `projects` | `string[]
|
|
126
|
+
| `projects` | `Record<string, string[]>` | Map of Lingohub project id to resource keys. An empty array fetches **all** resources for that project; a populated array fetches only the listed resources (matched against the `resource` field in `src/shared/lingohub.ts`). |
|
|
124
127
|
| `locales` | `string[]` | Optional. Locale codes to fetch (e.g. `pt-br`, `zh-hans`). If omitted or empty, a built-in default list is used. |
|
|
125
128
|
| `retry` | `S3RetryConfig` | Optional. Overrides [S3 download retries](#s3-download-retries). |
|
|
126
129
|
|
|
@@ -135,15 +138,34 @@ const files = await sdk.fetchTranslationBundles({
|
|
|
135
138
|
}
|
|
136
139
|
```
|
|
137
140
|
|
|
138
|
-
**S3 object keys** follow `lingohub-{project}.{fileName}` where `{fileName}` is the Lingohub resource template with `[locale]` replaced by the **mapped** locale when a resource defines `localeMapping` (same rules as the server sync).
|
|
141
|
+
**S3 object keys** follow `lingohub-{project}.{fileName}` where `{fileName}` is the Lingohub resource template with `[locale]` replaced by the **mapped** locale when a resource defines `localeMapping` (same rules as the server sync). On disk, non-`.json` keys gain a trailing `.json` (e.g. `…en.strings` → `…en.strings.json`) containing the parsed structure as JSON.
|
|
139
142
|
|
|
140
143
|
Downloads are run **in parallel** (per project); [S3 download retries](#s3-download-retries) apply by default.
|
|
141
144
|
|
|
142
145
|
---
|
|
143
146
|
|
|
147
|
+
## `fetchMergedTranslationBundles(options)`
|
|
148
|
+
|
|
149
|
+
Same **`projects`**, **`locales`**, and **`retry`** as `fetchTranslationBundles`. Downloads and parses every matching resource, flattens each file to string key/value pairs, then **merges** all pairs per **catalog locale** into a single file **`{locale}.json`** in `outputDir` (e.g. `en.json`). Duplicate keys across resources or projects: **last wins** (order: projects → resources → locales loop).
|
|
150
|
+
|
|
151
|
+
**Returns:** `Record<string, string>` — locale code → absolute path of the merged file.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const mergedPaths = await sdk.fetchMergedTranslationBundles({
|
|
155
|
+
projects: {
|
|
156
|
+
'tandem-(new-website)': [],
|
|
157
|
+
'tandem-(website)': ['main'],
|
|
158
|
+
},
|
|
159
|
+
locales: ['en'],
|
|
160
|
+
});
|
|
161
|
+
// mergedPaths.en → path to one big en.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
144
166
|
## S3 download retries
|
|
145
167
|
|
|
146
|
-
`fetchCmsBundles` and `
|
|
168
|
+
`fetchCmsBundles`, `fetchTranslationBundles`, and `fetchMergedTranslationBundles` use retry + exponential backoff on **transient** S3/network failures (for example HTTP 503 “Slow Down”, throttling, timeouts). They do **not** retry clear client errors such as **404** (missing key).
|
|
147
169
|
|
|
148
170
|
Default limits are read from the environment (highest precedence first):
|
|
149
171
|
|
|
@@ -294,6 +316,7 @@ The same operations are available as standalone imports (no `ContentStoreSDK` wr
|
|
|
294
316
|
import {
|
|
295
317
|
fetchCmsBundles,
|
|
296
318
|
fetchTranslationBundles,
|
|
319
|
+
fetchMergedTranslationBundles,
|
|
297
320
|
queryCmsBundle,
|
|
298
321
|
getDefaultS3RetryConfig,
|
|
299
322
|
ContentStore,
|
|
@@ -312,11 +335,16 @@ await fetchCmsBundles(store, './content-cache', {
|
|
|
312
335
|
});
|
|
313
336
|
|
|
314
337
|
await fetchTranslationBundles(store, './content-cache', {
|
|
315
|
-
projects:
|
|
338
|
+
projects: { 'tandem-(website)': [] },
|
|
316
339
|
locales: ['en', 'fr'],
|
|
317
340
|
retry: getDefaultS3RetryConfig(),
|
|
318
341
|
});
|
|
319
342
|
|
|
343
|
+
await fetchMergedTranslationBundles(store, './content-cache/merged', {
|
|
344
|
+
projects: { 'tandem-(new-website)': [], 'tandem-(website)': ['main'] },
|
|
345
|
+
locales: ['en'],
|
|
346
|
+
});
|
|
347
|
+
|
|
320
348
|
const results = await queryCmsBundle('./content-cache', 'contentful', 'gridLayout', {
|
|
321
349
|
fields: { columns: '2' },
|
|
322
350
|
limit: 5,
|
|
@@ -346,7 +374,7 @@ await sdk.fetchCmsBundles({
|
|
|
346
374
|
|
|
347
375
|
// Optional: pull translation bundles written by the Lingohub → S3 sync
|
|
348
376
|
await sdk.fetchTranslationBundles({
|
|
349
|
-
projects:
|
|
377
|
+
projects: { 'tandem-(website)': [] },
|
|
350
378
|
});
|
|
351
379
|
|
|
352
380
|
// 2. Query CMS bundle locally — no further network calls
|
|
@@ -361,7 +389,7 @@ console.log(grids);
|
|
|
361
389
|
```
|
|
362
390
|
## CLI
|
|
363
391
|
|
|
364
|
-
The
|
|
392
|
+
The package ships **`fetch-content-bundles`**, **`fetch-translation-bundles`**, and **`fetch-merged-translation-bundles`** as **`bin`** commands. The **`query-cms`** command lives at **`dist/client/query-cms.js`**; run with **`node node_modules/@tandem-language-exchange/content-store/dist/client/query-cms.js`** or via npm scripts.
|
|
365
393
|
|
|
366
394
|
### `fetch-content-bundles` — download bundles from S3
|
|
367
395
|
|
|
@@ -387,14 +415,54 @@ Files are written as `{cms}-{contentType}.json` inside the output directory.
|
|
|
387
415
|
}
|
|
388
416
|
```
|
|
389
417
|
|
|
390
|
-
|
|
418
|
+
All **`bin`** tools read S3 settings from [S3 config via environment variables](#s3-config-via-environment-variables).
|
|
419
|
+
|
|
420
|
+
### `fetch-translation-bundles`
|
|
421
|
+
|
|
422
|
+
Both translation CLI commands accept a **`--config`** flag pointing to a JSON config file that defines which projects, resources, and locales to fetch.
|
|
423
|
+
|
|
424
|
+
**Config file format (`TranslationFilterConfig`):**
|
|
425
|
+
|
|
426
|
+
```json
|
|
427
|
+
{
|
|
428
|
+
"projects": {
|
|
429
|
+
"tandem": [],
|
|
430
|
+
"tandem-(website)": ["main", "ai"]
|
|
431
|
+
},
|
|
432
|
+
"locales": ["en", "de", "it"]
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- **`projects`** — map of Lingohub project id to resource keys. An empty array (`[]`) fetches **all** resources for that project. A populated array fetches only the listed resources (matched against the `resource` field in `src/shared/lingohub.ts`).
|
|
437
|
+
- **`locales`** — optional. Omit or leave empty to use the built-in default locale list.
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
npx fetch-translation-bundles --config ./translation-config.json --output ./content-cache
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
| Flag | Required | Default | Description |
|
|
444
|
+
| --- | --- | --- | --- |
|
|
445
|
+
| `--config <path>` | Yes | | Path to a JSON config file (`TranslationFilterConfig`) |
|
|
446
|
+
| `--output <dir>` | No | `./content-cache` | Output directory |
|
|
447
|
+
|
|
448
|
+
### `fetch-merged-translation-bundles`
|
|
449
|
+
|
|
450
|
+
Writes merged **`{locale}.json`** files (string key/value map; duplicate keys: last wins).
|
|
451
|
+
|
|
452
|
+
Uses the same config file format as `fetch-translation-bundles`.
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
npx fetch-merged-translation-bundles --config ./translation-config.json --output ./content-cache/merged
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Alternatively call **`fetchTranslationBundles`** / **`fetchMergedTranslationBundles`** from a Node script, or use the server’s **`POST /getTranslationBundles`** API (see [Server & CLI README](src/server/README.md)).
|
|
391
459
|
|
|
392
460
|
### `query-cms` — query a local bundle
|
|
393
461
|
|
|
394
462
|
Reads a previously fetched bundle from disk and prints JSON to stdout. This command is **not** a separate `bin`; run the built client CLI (after `npm install` of this package):
|
|
395
463
|
|
|
396
464
|
```bash
|
|
397
|
-
node node_modules/@tandem-language-exchange/content-store/dist/client/
|
|
465
|
+
node node_modules/@tandem-language-exchange/content-store/dist/client/query-cms.js \
|
|
398
466
|
--cms contentful --type gridLayout \
|
|
399
467
|
--fields '{"columns":"2"}' \
|
|
400
468
|
--select title,bodyBefore \
|
|
@@ -406,7 +474,7 @@ node node_modules/@tandem-language-exchange/content-store/dist/client/cli.js que
|
|
|
406
474
|
|
|
407
475
|
```json
|
|
408
476
|
"scripts": {
|
|
409
|
-
"query:cms": "node ./node_modules/@tandem-language-exchange/content-store/dist/client/
|
|
477
|
+
"query:cms": "node ./node_modules/@tandem-language-exchange/content-store/dist/client/query-cms.js"
|
|
410
478
|
}
|
|
411
479
|
```
|
|
412
480
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/shared/config.ts
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
dotenv.config({ path: ".env.local" });
|
|
6
|
+
dotenv.config();
|
|
7
|
+
var config = {
|
|
8
|
+
s3: {
|
|
9
|
+
bucket: process.env.CONTENT_STORE_S3_BUCKET ?? "",
|
|
10
|
+
region: process.env.CONTENT_STORE_S3_REGION ?? "eu-central-1",
|
|
11
|
+
accessKeyId: process.env.AWS_ACCESS_KEY ?? "",
|
|
12
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? ""
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
config
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=chunk-4DE47ZJD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/config.ts"],"sourcesContent":["import dotenv from 'dotenv';\nimport type { S3Config, CMSProvider } from './types';\n\ndotenv.config({ path: '.env.local' });\ndotenv.config();\n\nexport type { CMSProvider, S3Config };\n\nexport interface SharedConfig {\n s3: S3Config;\n}\n\nexport const config: SharedConfig = {\n s3: {\n bucket: process.env.CONTENT_STORE_S3_BUCKET ?? '',\n region: process.env.CONTENT_STORE_S3_REGION ?? 'eu-central-1',\n accessKeyId: process.env.AWS_ACCESS_KEY ?? '',\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '',\n }\n};\n"],"mappings":";;;AAAA,OAAO,YAAY;AAGnB,OAAO,OAAO,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,OAAO;AAQP,IAAM,SAAuB;AAAA,EAChC,IAAI;AAAA,IACA,QAAQ,QAAQ,IAAI,2BAA2B;AAAA,IAC/C,QAAQ,QAAQ,IAAI,2BAA2B;AAAA,IAC/C,aAAa,QAAQ,IAAI,kBAAkB;AAAA,IAC3C,iBAAiB,QAAQ,IAAI,yBAAyB;AAAA,EAC1D;AACJ;","names":[]}
|
|
@@ -30,6 +30,18 @@ var ContentStore = class {
|
|
|
30
30
|
);
|
|
31
31
|
return key;
|
|
32
32
|
}
|
|
33
|
+
/** Raw UTF-8 body (e.g. Lingohub file bytes as text). */
|
|
34
|
+
async uploadRaw(key, body, contentType) {
|
|
35
|
+
await this.client.send(
|
|
36
|
+
new PutObjectCommand({
|
|
37
|
+
Bucket: this.bucket,
|
|
38
|
+
Key: key,
|
|
39
|
+
Body: body,
|
|
40
|
+
ContentType: contentType
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
return key;
|
|
44
|
+
}
|
|
33
45
|
async download(key) {
|
|
34
46
|
const response = await this.client.send(
|
|
35
47
|
new GetObjectCommand({ Bucket: this.bucket, Key: key })
|
|
@@ -38,6 +50,15 @@ var ContentStore = class {
|
|
|
38
50
|
if (!body) throw new Error(`Empty response for key: ${key}`);
|
|
39
51
|
return JSON.parse(body);
|
|
40
52
|
}
|
|
53
|
+
/** Raw UTF-8 body from S3 (no JSON.parse). */
|
|
54
|
+
async downloadRaw(key) {
|
|
55
|
+
const response = await this.client.send(
|
|
56
|
+
new GetObjectCommand({ Bucket: this.bucket, Key: key })
|
|
57
|
+
);
|
|
58
|
+
const body = await response.Body?.transformToString();
|
|
59
|
+
if (!body) throw new Error(`Empty response for key: ${key}`);
|
|
60
|
+
return body;
|
|
61
|
+
}
|
|
41
62
|
};
|
|
42
63
|
var buildCmsObjectKey = (cms, contentType) => {
|
|
43
64
|
return `${cms}-${contentType}.json`;
|
|
@@ -63,41 +84,49 @@ var localeMapping = {
|
|
|
63
84
|
var allProjects = {
|
|
64
85
|
"tandem-(new-website)": [
|
|
65
86
|
{
|
|
87
|
+
resource: "main",
|
|
66
88
|
fileName: "Website.[locale].json",
|
|
67
89
|
type: "json"
|
|
68
90
|
}
|
|
69
91
|
],
|
|
70
92
|
"tandem-(website)": [
|
|
71
93
|
{
|
|
94
|
+
resource: "main",
|
|
72
95
|
fileName: "[locale].json",
|
|
73
96
|
type: "json"
|
|
74
97
|
},
|
|
75
98
|
{
|
|
99
|
+
resource: "ai",
|
|
76
100
|
fileName: "AI.[locale].json",
|
|
77
101
|
type: "json"
|
|
78
102
|
},
|
|
79
103
|
{
|
|
104
|
+
resource: "languages",
|
|
80
105
|
fileName: "languages.[locale].json",
|
|
81
106
|
type: "json"
|
|
82
107
|
}
|
|
83
108
|
],
|
|
84
109
|
"tandem": [
|
|
85
110
|
{
|
|
111
|
+
resource: "infoplist",
|
|
86
112
|
fileName: "InfoPlist.[locale].strings",
|
|
87
113
|
type: "strings",
|
|
88
114
|
localeMapping: localeMapping.ios
|
|
89
115
|
},
|
|
90
116
|
{
|
|
117
|
+
resource: "localizable",
|
|
91
118
|
fileName: "Localizable.[locale].strings",
|
|
92
119
|
type: "strings",
|
|
93
120
|
localeMapping: localeMapping.ios
|
|
94
121
|
},
|
|
95
122
|
{
|
|
123
|
+
resource: "ipad",
|
|
96
124
|
fileName: "MainiPad.[locale].strings",
|
|
97
125
|
type: "strings",
|
|
98
126
|
localeMapping: localeMapping.ios
|
|
99
127
|
},
|
|
100
128
|
{
|
|
129
|
+
resource: "main",
|
|
101
130
|
fileName: "Main.[locale].strings",
|
|
102
131
|
type: "strings",
|
|
103
132
|
localeMapping: localeMapping.ios
|
|
@@ -105,136 +134,163 @@ var allProjects = {
|
|
|
105
134
|
],
|
|
106
135
|
"tandem-(android)": [
|
|
107
136
|
{
|
|
137
|
+
resource: "accessibility",
|
|
108
138
|
fileName: "accessibility_localizable.[locale].xml",
|
|
109
139
|
type: "xml",
|
|
110
140
|
localeMapping: localeMapping.android
|
|
111
141
|
},
|
|
112
142
|
{
|
|
143
|
+
resource: "call",
|
|
113
144
|
fileName: "call_localizable.[locale].xml",
|
|
114
145
|
type: "xml",
|
|
115
146
|
localeMapping: localeMapping.android
|
|
116
147
|
},
|
|
117
148
|
{
|
|
149
|
+
resource: "cert",
|
|
118
150
|
fileName: "cert_localizable.[locale].xml",
|
|
119
151
|
type: "xml",
|
|
120
152
|
localeMapping: localeMapping.android
|
|
121
153
|
},
|
|
122
154
|
{
|
|
155
|
+
resource: "chat",
|
|
123
156
|
fileName: "chat_localizable.[locale].xml",
|
|
124
157
|
type: "xml",
|
|
125
158
|
localeMapping: localeMapping.android
|
|
126
159
|
},
|
|
127
160
|
{
|
|
161
|
+
resource: "checklist",
|
|
128
162
|
fileName: "checklist_localizable.[locale].xml",
|
|
129
163
|
type: "xml",
|
|
130
164
|
localeMapping: localeMapping.android
|
|
131
165
|
},
|
|
132
166
|
{
|
|
167
|
+
resource: "clubs",
|
|
133
168
|
fileName: "clubs_localizable.[locale].xml",
|
|
134
169
|
type: "xml",
|
|
135
170
|
localeMapping: localeMapping.android
|
|
136
171
|
},
|
|
137
172
|
{
|
|
173
|
+
resource: "common",
|
|
138
174
|
fileName: "common_localizable.[locale].xml",
|
|
139
175
|
type: "xml",
|
|
140
176
|
localeMapping: localeMapping.android
|
|
141
177
|
},
|
|
142
178
|
{
|
|
179
|
+
resource: "community",
|
|
143
180
|
fileName: "community_localizable.[locale].xml",
|
|
144
181
|
type: "xml",
|
|
145
182
|
localeMapping: localeMapping.android
|
|
146
183
|
},
|
|
147
184
|
{
|
|
185
|
+
resource: "correction",
|
|
148
186
|
fileName: "correction_localizable.[locale].xml",
|
|
149
187
|
type: "xml",
|
|
150
188
|
localeMapping: localeMapping.android
|
|
151
189
|
},
|
|
152
190
|
{
|
|
191
|
+
resource: "country_names",
|
|
153
192
|
fileName: "country_names.[locale].xml",
|
|
154
193
|
type: "xml",
|
|
155
194
|
localeMapping: localeMapping.android
|
|
156
195
|
},
|
|
157
196
|
{
|
|
197
|
+
resource: "emoji",
|
|
158
198
|
fileName: "emoji_localizable.[locale].xml",
|
|
159
199
|
type: "xml",
|
|
160
200
|
localeMapping: localeMapping.android
|
|
161
201
|
},
|
|
162
202
|
{
|
|
203
|
+
resource: "errors",
|
|
163
204
|
fileName: "errors_localizable.[locale].xml",
|
|
164
205
|
type: "xml",
|
|
165
206
|
localeMapping: localeMapping.android
|
|
166
207
|
},
|
|
167
208
|
{
|
|
209
|
+
resource: "expressions",
|
|
168
210
|
fileName: "expressions_localizable.[locale].xml",
|
|
169
211
|
type: "xml",
|
|
170
212
|
localeMapping: localeMapping.android
|
|
171
213
|
},
|
|
172
214
|
{
|
|
215
|
+
resource: "gif",
|
|
173
216
|
fileName: "gif_localizable.[locale].xml",
|
|
174
217
|
type: "xml",
|
|
175
218
|
localeMapping: localeMapping.android
|
|
176
219
|
},
|
|
177
220
|
{
|
|
221
|
+
resource: "guidelines",
|
|
178
222
|
fileName: "guidelines_localizable.[locale].xml",
|
|
179
223
|
type: "xml",
|
|
180
224
|
localeMapping: localeMapping.android
|
|
181
225
|
},
|
|
182
226
|
{
|
|
227
|
+
resource: "lanuguages",
|
|
183
228
|
fileName: "languages_localizable.[locale].xml",
|
|
184
229
|
type: "xml",
|
|
185
230
|
localeMapping: localeMapping.android
|
|
186
231
|
},
|
|
187
232
|
{
|
|
233
|
+
resource: "localizable",
|
|
188
234
|
fileName: "localizable.[locale].xml",
|
|
189
235
|
type: "xml",
|
|
190
236
|
localeMapping: localeMapping.android
|
|
191
237
|
},
|
|
192
238
|
{
|
|
239
|
+
resource: "localizable2",
|
|
193
240
|
fileName: "localizable2.[locale].xml",
|
|
194
241
|
type: "xml",
|
|
195
242
|
localeMapping: localeMapping.android
|
|
196
243
|
},
|
|
197
244
|
{
|
|
245
|
+
resource: "login",
|
|
198
246
|
fileName: "login_localizable.[locale].xml",
|
|
199
247
|
type: "xml",
|
|
200
248
|
localeMapping: localeMapping.android
|
|
201
249
|
},
|
|
202
250
|
{
|
|
251
|
+
resource: "myprofile",
|
|
203
252
|
fileName: "myprofile_localizable.[locale].xml",
|
|
204
253
|
type: "xml",
|
|
205
254
|
localeMapping: localeMapping.android
|
|
206
255
|
},
|
|
207
256
|
{
|
|
257
|
+
resource: "onb",
|
|
208
258
|
fileName: "onb_localizable.[locale].xml",
|
|
209
259
|
type: "xml",
|
|
210
260
|
localeMapping: localeMapping.android
|
|
211
261
|
},
|
|
212
262
|
{
|
|
263
|
+
resource: "parties",
|
|
213
264
|
fileName: "parties_localizable.[locale].xml",
|
|
214
265
|
type: "xml",
|
|
215
266
|
localeMapping: localeMapping.android
|
|
216
267
|
},
|
|
217
268
|
{
|
|
269
|
+
resource: "pro",
|
|
218
270
|
fileName: "pro_localizable.[locale].xml",
|
|
219
271
|
type: "xml",
|
|
220
272
|
localeMapping: localeMapping.android
|
|
221
273
|
},
|
|
222
274
|
{
|
|
275
|
+
resource: "pro_screen",
|
|
223
276
|
fileName: "pro_screen_localizable.[locale].xml",
|
|
224
277
|
type: "xml",
|
|
225
278
|
localeMapping: localeMapping.android
|
|
226
279
|
},
|
|
227
280
|
{
|
|
281
|
+
resource: "push_notification",
|
|
228
282
|
fileName: "push_notification_localizable.[locale].xml",
|
|
229
283
|
type: "xml",
|
|
230
284
|
localeMapping: localeMapping.android
|
|
231
285
|
},
|
|
232
286
|
{
|
|
287
|
+
resource: "reporting",
|
|
233
288
|
fileName: "reporting_localizable.[locale].xml",
|
|
234
289
|
type: "xml",
|
|
235
290
|
localeMapping: localeMapping.android
|
|
236
291
|
},
|
|
237
292
|
{
|
|
293
|
+
resource: "translation",
|
|
238
294
|
fileName: "translation_localizable.[locale].xml",
|
|
239
295
|
type: "xml",
|
|
240
296
|
localeMapping: localeMapping.android
|
|
@@ -242,31 +298,121 @@ var allProjects = {
|
|
|
242
298
|
],
|
|
243
299
|
"tandem-(web-invites)": [
|
|
244
300
|
{
|
|
301
|
+
resource: "main",
|
|
245
302
|
fileName: "[locale].json",
|
|
246
303
|
type: "json"
|
|
247
304
|
}
|
|
248
305
|
]
|
|
249
306
|
};
|
|
250
307
|
|
|
251
|
-
// src/shared/
|
|
252
|
-
import
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
308
|
+
// src/shared/translationResource.ts
|
|
309
|
+
import path from "path";
|
|
310
|
+
|
|
311
|
+
// src/shared/utils.ts
|
|
312
|
+
import convert from "xml-js";
|
|
313
|
+
import set from "lodash.set";
|
|
314
|
+
import merge from "lodash.merge";
|
|
315
|
+
var transformObjectToFlat = (data) => {
|
|
316
|
+
const result = {};
|
|
317
|
+
const flatten = (obj, path2 = []) => {
|
|
318
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
319
|
+
if (typeof value === "object") {
|
|
320
|
+
flatten(value, path2.concat(key));
|
|
321
|
+
} else {
|
|
322
|
+
result[path2.concat(key).join(".")] = value;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
flatten(data);
|
|
327
|
+
return result;
|
|
328
|
+
};
|
|
329
|
+
var convertXMLToJS = (xml) => {
|
|
330
|
+
const converted = convert.xml2js(xml, {
|
|
331
|
+
ignoreComment: true,
|
|
332
|
+
ignoreDeclaration: true,
|
|
333
|
+
ignoreInstruction: true,
|
|
334
|
+
compact: true
|
|
335
|
+
});
|
|
336
|
+
let mapped = {};
|
|
337
|
+
converted.resources.string.forEach((item) => {
|
|
338
|
+
mapped = {
|
|
339
|
+
...mapped,
|
|
340
|
+
[item._attributes.name]: item._text
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
return mapped;
|
|
262
344
|
};
|
|
345
|
+
var parseIOSStrings = (strings) => {
|
|
346
|
+
const parsedObj = {};
|
|
347
|
+
strings.split("\n").filter((line) => line.startsWith('"') && line.endsWith(";")).map((line) => line.trim().slice(0, -1)).forEach((line) => {
|
|
348
|
+
let [key, value] = line.split(" = ");
|
|
349
|
+
if (!key || !value) return;
|
|
350
|
+
key = key.slice(1, -1);
|
|
351
|
+
value = value.slice(1, -1);
|
|
352
|
+
parsedObj[key] = value;
|
|
353
|
+
});
|
|
354
|
+
return parsedObj;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// src/shared/translationResource.ts
|
|
358
|
+
function contentTypeForTranslationKey(objectKey) {
|
|
359
|
+
if (objectKey.endsWith(".json")) return "application/json; charset=utf-8";
|
|
360
|
+
if (objectKey.endsWith(".xml")) return "application/xml; charset=utf-8";
|
|
361
|
+
if (objectKey.endsWith(".strings")) return "text/plain; charset=utf-8";
|
|
362
|
+
return "application/octet-stream";
|
|
363
|
+
}
|
|
364
|
+
function parseTranslationResourceRaw(raw, resource) {
|
|
365
|
+
if (resource.type === "json") {
|
|
366
|
+
return JSON.parse(raw);
|
|
367
|
+
}
|
|
368
|
+
if (resource.type === "strings") {
|
|
369
|
+
return parseIOSStrings(raw);
|
|
370
|
+
}
|
|
371
|
+
if (resource.type === "xml") {
|
|
372
|
+
return convertXMLToJS(raw);
|
|
373
|
+
}
|
|
374
|
+
throw new Error(`Unsupported resource type: ${resource.type}`);
|
|
375
|
+
}
|
|
376
|
+
function toFlatStringMap(parsed) {
|
|
377
|
+
if (parsed === null || parsed === void 0) return {};
|
|
378
|
+
if (typeof parsed === "string") return { value: parsed };
|
|
379
|
+
if (typeof parsed !== "object") return { value: String(parsed) };
|
|
380
|
+
if (Array.isArray(parsed)) {
|
|
381
|
+
const out2 = {};
|
|
382
|
+
parsed.forEach((v, i) => {
|
|
383
|
+
out2[String(i)] = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
|
|
384
|
+
});
|
|
385
|
+
return out2;
|
|
386
|
+
}
|
|
387
|
+
const flat = transformObjectToFlat(parsed);
|
|
388
|
+
const out = {};
|
|
389
|
+
for (const [k, v] of Object.entries(flat)) {
|
|
390
|
+
if (v === null || v === void 0) {
|
|
391
|
+
out[k] = "";
|
|
392
|
+
} else if (typeof v === "object") {
|
|
393
|
+
out[k] = JSON.stringify(v);
|
|
394
|
+
} else {
|
|
395
|
+
out[k] = String(v);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
function translationJsonOutputPath(outputDir, objectKey) {
|
|
401
|
+
if (objectKey.endsWith(".json")) {
|
|
402
|
+
return path.resolve(outputDir, objectKey);
|
|
403
|
+
}
|
|
404
|
+
return path.resolve(outputDir, `${objectKey}.json`);
|
|
405
|
+
}
|
|
263
406
|
|
|
264
407
|
export {
|
|
265
|
-
config,
|
|
266
408
|
ContentStore,
|
|
267
409
|
buildCmsObjectKey,
|
|
268
410
|
buildTranslationObjectKey,
|
|
269
411
|
defaultLocales,
|
|
270
|
-
allProjects
|
|
412
|
+
allProjects,
|
|
413
|
+
contentTypeForTranslationKey,
|
|
414
|
+
parseTranslationResourceRaw,
|
|
415
|
+
toFlatStringMap,
|
|
416
|
+
translationJsonOutputPath
|
|
271
417
|
};
|
|
272
|
-
//# sourceMappingURL=chunk-
|
|
418
|
+
//# sourceMappingURL=chunk-6FXNAJSI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/s3.ts","../src/shared/lingohub.ts","../src/shared/translationResource.ts","../src/shared/utils.ts"],"sourcesContent":["import {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n} from '@aws-sdk/client-s3';\nimport type { S3Config } from './types';\n\nexport class ContentStore {\n private client: S3Client;\n private bucket: string;\n\n constructor(cfg: S3Config) {\n this.client = new S3Client({\n region: cfg.region,\n credentials: {\n accessKeyId: cfg.accessKeyId,\n secretAccessKey: cfg.secretAccessKey,\n },\n });\n this.bucket = cfg.bucket;\n }\n\n async upload(key: string, data: unknown): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: JSON.stringify(data, null, 2),\n ContentType: 'application/json',\n }),\n );\n return key;\n }\n\n /** Raw UTF-8 body (e.g. Lingohub file bytes as text). */\n async uploadRaw(\n key: string,\n body: string,\n contentType: string,\n ): Promise<string> {\n await this.client.send(\n new PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: contentType,\n }),\n );\n return key;\n }\n\n async download(key: string): Promise<unknown> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n const body = await response.Body?.transformToString();\n if (!body) throw new Error(`Empty response for key: ${key}`);\n return JSON.parse(body);\n }\n\n /** Raw UTF-8 body from S3 (no JSON.parse). */\n async downloadRaw(key: string): Promise<string> {\n const response = await this.client.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: key }),\n );\n const body = await response.Body?.transformToString();\n if (!body) throw new Error(`Empty response for key: ${key}`);\n return body;\n }\n}\n\n/** {cms}-{contentType}.json (always points at the latest version) */\nexport const buildCmsObjectKey = (cms: string, contentType: string): string => {\n return `${cms}-${contentType}.json`;\n}\n\nexport const buildTranslationObjectKey = (project:string, fileName: string, locale: string): string => {\n return `lingohub-${project}.${fileName.replaceAll('[locale]', locale)}`;\n}\n","export const defaultLocales = ['en', 'fr', 'de', 'es', 'it', 'pt-br', 'ru', 'zh-hans', 'zh-hant', 'ko', 'ja'];\n\nconst localeMapping = {\n ios: {\n 'pt-br': 'pt',\n 'zh-hans': 'zh-Hans',\n 'zh-hant': 'zh-Hant',\n },\n android: {\n 'pt-br': 'pt',\n 'zh-hans': 'zh-Hans-CN',\n 'zh-hant': 'zh-TW',\n },\n};\n\nexport type LingohubResource = {\n resource: string,\n fileName: string;\n type: 'json'|'strings'|'xml';\n localeMapping?: Record<string, string>;\n}\n\nexport const allProjects: Record<string, LingohubResource[]> = {\n 'tandem-(new-website)': [\n {\n resource: 'main',\n fileName: 'Website.[locale].json',\n type: 'json'\n }\n ],\n 'tandem-(website)': [\n {\n resource: 'main',\n fileName: '[locale].json',\n type: 'json'\n },\n {\n resource: 'ai',\n fileName: 'AI.[locale].json',\n type: 'json'\n },\n {\n resource: 'languages',\n fileName: 'languages.[locale].json',\n type: 'json'\n }\n ],\n 'tandem': [\n {\n resource: 'infoplist',\n fileName: 'InfoPlist.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'localizable',\n fileName: 'Localizable.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'ipad',\n fileName: 'MainiPad.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n },\n {\n resource: 'main',\n fileName: 'Main.[locale].strings',\n type: 'strings',\n localeMapping: localeMapping.ios,\n }\n ],\n 'tandem-(android)': [\n {\n resource: 'accessibility',\n fileName: 'accessibility_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'call',\n fileName: 'call_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'cert',\n fileName: 'cert_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'chat',\n fileName: 'chat_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'checklist',\n fileName: 'checklist_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'clubs',\n fileName: 'clubs_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'common',\n fileName: 'common_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'community',\n fileName: 'community_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'correction',\n fileName: 'correction_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'country_names',\n fileName: 'country_names.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'emoji',\n fileName: 'emoji_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'errors',\n fileName: 'errors_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'expressions',\n fileName: 'expressions_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'gif',\n fileName: 'gif_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'guidelines',\n fileName: 'guidelines_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'lanuguages',\n fileName: 'languages_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'localizable',\n fileName: 'localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'localizable2',\n fileName: 'localizable2.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'login',\n fileName: 'login_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'myprofile',\n fileName: 'myprofile_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'onb',\n fileName: 'onb_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'parties',\n fileName: 'parties_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'pro',\n fileName: 'pro_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'pro_screen',\n fileName: 'pro_screen_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'push_notification',\n fileName: 'push_notification_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'reporting',\n fileName: 'reporting_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n },\n {\n resource: 'translation',\n fileName: 'translation_localizable.[locale].xml',\n type: 'xml',\n localeMapping: localeMapping.android,\n }\n\n ],\n 'tandem-(web-invites)': [\n {\n resource: 'main',\n fileName: '[locale].json',\n type: 'json'\n }\n ]\n};\n","import path from 'node:path';\nimport { convertXMLToJS, parseIOSStrings, transformObjectToFlat } from './utils';\nimport type { LingohubResource } from './lingohub';\n\n/** Content-Type for raw Lingohub bodies stored in S3. */\nexport function contentTypeForTranslationKey(objectKey: string): string {\n if (objectKey.endsWith('.json')) return 'application/json; charset=utf-8';\n if (objectKey.endsWith('.xml')) return 'application/xml; charset=utf-8';\n if (objectKey.endsWith('.strings')) return 'text/plain; charset=utf-8';\n return 'application/octet-stream';\n}\n\n/**\n * Parses a raw Lingohub file body from S3 into structured data.\n */\nexport function parseTranslationResourceRaw(\n raw: string,\n resource: LingohubResource,\n): unknown {\n if (resource.type === 'json') {\n return JSON.parse(raw) as unknown;\n }\n\n if (resource.type === 'strings') {\n return parseIOSStrings(raw);\n }\n\n if (resource.type === 'xml') {\n return convertXMLToJS(raw);\n }\n\n throw new Error(`Unsupported resource type: ${resource.type}`);\n}\n\n/**\n * Normalizes parsed translation data to a flat string map for merging (duplicate keys: last wins).\n */\nexport function toFlatStringMap(parsed: unknown): Record<string, string> {\n if (parsed === null || parsed === undefined) return {};\n if (typeof parsed === 'string') return { value: parsed };\n if (typeof parsed !== 'object') return { value: String(parsed) };\n if (Array.isArray(parsed)) {\n const out: Record<string, string> = {};\n parsed.forEach((v, i) => {\n out[String(i)] =\n typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v);\n });\n return out;\n }\n\n const flat = transformObjectToFlat(parsed as Record<string, unknown>);\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(flat)) {\n if (v === null || v === undefined) {\n out[k] = '';\n } else if (typeof v === 'object') {\n out[k] = JSON.stringify(v);\n } else {\n out[k] = String(v);\n }\n }\n return out;\n}\n\n/** Where to write normalized JSON for one translation object key (avoids `.json.json`). */\nexport function translationJsonOutputPath(\n outputDir: string,\n objectKey: string,\n): string {\n if (objectKey.endsWith('.json')) {\n return path.resolve(outputDir, objectKey);\n }\n return path.resolve(outputDir, `${objectKey}.json`);\n}\n","import convert from 'xml-js';\nimport set from 'lodash.set';\nimport merge from 'lodash.merge';\n\nexport const transformObjectToNested = (data: Record<string, unknown>): Record<string, unknown> => {\n const result: Record<string, unknown> = {};\n\n Object.entries(data).forEach(([key, value]) => {\n const tempObject = {};\n set(tempObject, key, value);\n merge(result, tempObject);\n });\n\n return result;\n};\n\nexport const transformObjectToFlat = (data: Record<string, any>): Record<string, any> => { // eslint-disable-line @typescript-eslint/no-explicit-any\n const result: Record<string, unknown> = {};\n\n const flatten = (obj: Record<string, any>, path: string[] = []) => { // eslint-disable-line @typescript-eslint/no-explicit-any\n Object.entries(obj).forEach(([key, value]) => {\n if (typeof value === 'object') {\n flatten(value, path.concat(key));\n } else {\n result[path.concat(key).join('.')] = value;\n }\n });\n };\n\n flatten(data);\n\n return result;\n}\n\nexport const convertXMLToJS = (xml: string): Record<string, string> => {\n const converted = convert.xml2js(xml, {\n ignoreComment: true,\n ignoreDeclaration: true,\n ignoreInstruction: true,\n compact: true,\n }) as {\n resources: {\n string: {\n _attributes: { name: string },\n _text: 'User does not exist'\n }[];\n }\n };\n\n let mapped = {};\n converted.resources.string.forEach((item) => {\n mapped = {\n ...mapped,\n [item._attributes.name]: item._text,\n };\n });\n\n return mapped;\n};\n\nexport const parseIOSStrings = (strings: string): Record<string, string> => {\n const parsedObj: Record<string, string> = {};\n strings\n .split('\\n')\n .filter((line) => line.startsWith('\"') && line.endsWith(';'))\n .map((line) => line.trim().slice(0, -1))\n .forEach((line) => {\n let [key, value] = line.split(' = ');\n if (!key || !value) return;\n key = key.slice(1, -1);\n value = value.slice(1, -1);\n parsedObj[key] = value;\n });\n\n return parsedObj;\n};"],"mappings":"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","names":["path","out"]}
|