@tandem-language-exchange/content-store 1.2.7 → 1.2.9
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 +22 -3
- package/dist/{chunk-4DE47ZJD.js → chunk-75UTJIZQ.js} +2 -1
- package/dist/chunk-75UTJIZQ.js.map +1 -0
- package/dist/{chunk-R3E6ZZJE.js → chunk-JVI4MEN5.js} +28 -6
- package/dist/chunk-JVI4MEN5.js.map +1 -0
- package/dist/{chunk-5WQPK6GZ.js → chunk-KNUP5JWR.js} +13 -8
- package/dist/chunk-KNUP5JWR.js.map +1 -0
- package/dist/{chunk-6BNCYF5W.js → chunk-PSTICAT4.js} +47 -15
- package/dist/chunk-PSTICAT4.js.map +1 -0
- package/dist/chunk-SMGEK5VP.js +183 -0
- package/dist/chunk-SMGEK5VP.js.map +1 -0
- package/dist/{chunk-UPIQFNCR.js → chunk-UMWRIKPS.js} +2 -2
- package/dist/client/fetch-content-bundles.js +4 -4
- package/dist/client/fetch-merged-translation-bundles.js +4 -4
- package/dist/client/fetch-translation-bundles.js +4 -4
- package/dist/client/query-cms.js +2 -2
- package/dist/{index-PQ7XN47c.d.ts → index-DqDNlXSE.d.ts} +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +38 -11
- package/dist/node.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4DE47ZJD.js.map +0 -1
- package/dist/chunk-5WQPK6GZ.js.map +0 -1
- package/dist/chunk-6BNCYF5W.js.map +0 -1
- package/dist/chunk-R3E6ZZJE.js.map +0 -1
- /package/dist/{chunk-UPIQFNCR.js.map → chunk-UMWRIKPS.js.map} +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
config,
|
|
4
|
+
summariseCmsEntries,
|
|
5
|
+
summariseCmsErrors,
|
|
6
|
+
summariseTranslationEntries,
|
|
7
|
+
syncCmsContent,
|
|
8
|
+
syncTranslations
|
|
9
|
+
} from "./chunk-PSTICAT4.js";
|
|
10
|
+
import {
|
|
11
|
+
allProjects
|
|
12
|
+
} from "./chunk-SF7FCBR2.js";
|
|
13
|
+
|
|
14
|
+
// src/server/slack.ts
|
|
15
|
+
import { App } from "@slack/bolt";
|
|
16
|
+
var VALID_CMS = ["contentful", "sanity"];
|
|
17
|
+
var app = null;
|
|
18
|
+
async function notifySlack(text) {
|
|
19
|
+
const { notifyChannel } = config.slack;
|
|
20
|
+
if (!app || !notifyChannel) {
|
|
21
|
+
console.log("[slack] Notification skipped (no app or channel configured)");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
await app.client.chat.postMessage({ channel: notifyChannel, text: `[${config.environment}] ${text}` });
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error("[slack] Failed to send notification:", err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function startSlackBot() {
|
|
31
|
+
const { slack } = config;
|
|
32
|
+
if (!slack.enabled) {
|
|
33
|
+
console.log("[slack] Disabled (SLACK_BOT_TOKEN not set)");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
app = new App({
|
|
37
|
+
token: slack.botToken,
|
|
38
|
+
signingSecret: slack.signingSecret,
|
|
39
|
+
appToken: slack.appToken,
|
|
40
|
+
socketMode: true
|
|
41
|
+
});
|
|
42
|
+
app.command(slack.cmdSyncContent, async ({ command, ack, respond }) => {
|
|
43
|
+
await ack();
|
|
44
|
+
const args = command.text.trim().split(/\s+/).filter(Boolean);
|
|
45
|
+
const cms = args[0] ?? "contentful";
|
|
46
|
+
const contentTypes = args.length > 1 ? args.slice(1) : void 0;
|
|
47
|
+
if (!VALID_CMS.includes(cms)) {
|
|
48
|
+
await respond({
|
|
49
|
+
response_type: "ephemeral",
|
|
50
|
+
text: `Invalid CMS provider \`${cms}\`. Use \`contentful\` or \`sanity\`.`
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const typesHint = contentTypes?.length ? ` (types: ${contentTypes.join(", ")})` : " (all types)";
|
|
55
|
+
await respond({
|
|
56
|
+
response_type: "in_channel",
|
|
57
|
+
text: `:hourglass_flowing_sand: Sync started for *${cms}*${typesHint}\u2026`
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
const result = await syncCmsContent(cms, contentTypes);
|
|
61
|
+
const blocks = [
|
|
62
|
+
result.errors.length === 0 ? `:white_check_mark: *Sync complete* \u2014 ${result.entries.length} content types synced` : `:warning: *Sync complete with errors* \u2014 ${result.entries.length} succeeded, ${result.errors.length} failed`,
|
|
63
|
+
"",
|
|
64
|
+
...summariseCmsEntries(result)
|
|
65
|
+
];
|
|
66
|
+
if (result.errors.length > 0) {
|
|
67
|
+
blocks.push("", "*Errors:*", ...summariseCmsErrors(result));
|
|
68
|
+
}
|
|
69
|
+
await respond({ response_type: "in_channel", text: blocks.join("\n") });
|
|
70
|
+
} catch (err) {
|
|
71
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
72
|
+
console.error("[slack] Sync failed:", message);
|
|
73
|
+
await respond({
|
|
74
|
+
response_type: "ephemeral",
|
|
75
|
+
text: `:x: Sync failed: ${message}`
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
app.command(slack.cmdSyncTranslations, async ({ command, ack, respond }) => {
|
|
80
|
+
await ack();
|
|
81
|
+
const raw = command.text.trim();
|
|
82
|
+
if (!raw) {
|
|
83
|
+
await respond({
|
|
84
|
+
response_type: "ephemeral",
|
|
85
|
+
text: "Usage: `/sync-translations <comma-separated projects> [<comma-separated locales>]`\nExample: `/sync-translations tandem,tandem-(website) en,de,fr`\nOmit locales to use the default locale list."
|
|
86
|
+
});
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const spaceIdx = raw.indexOf(" ");
|
|
90
|
+
const projectsPart = spaceIdx === -1 ? raw : raw.slice(0, spaceIdx).trim();
|
|
91
|
+
const localesPart = spaceIdx === -1 ? "" : raw.slice(spaceIdx + 1).trim();
|
|
92
|
+
const projects = projectsPart.split(",").map((s) => s.trim()).filter(Boolean);
|
|
93
|
+
if (projects.length === 0) {
|
|
94
|
+
await respond({
|
|
95
|
+
response_type: "ephemeral",
|
|
96
|
+
text: "No valid projects. Pass a comma-separated list as the first argument, e.g. `tandem,tandem-(website)`."
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
let locales = localesPart.length ? localesPart.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
101
|
+
if (locales && locales.length === 0) {
|
|
102
|
+
locales = void 0;
|
|
103
|
+
}
|
|
104
|
+
const localesHint = locales?.length ? ` \u2014 locales: ${locales?.join(", ")}` : " \u2014 default locales";
|
|
105
|
+
await respond({
|
|
106
|
+
response_type: "in_channel",
|
|
107
|
+
text: `:hourglass_flowing_sand: Translation sync started for projects: *${projects.join(", ")}*${localesHint}\u2026`
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const result = await syncTranslations(projects, locales);
|
|
111
|
+
const lines = summariseTranslationEntries(result.entries);
|
|
112
|
+
const errorLines = result.errors.map(
|
|
113
|
+
(e) => `\u2022 \`${e.project}\` / \`${e.resource}\` / \`${e.locale}\` \u2014 ${e.error}`
|
|
114
|
+
);
|
|
115
|
+
const blocks = [
|
|
116
|
+
result.errors.length === 0 ? `:white_check_mark: *Translation sync complete* \u2014 ${result.entries.length} resource \xD7 locale uploads` : `:warning: *Translation sync complete with errors* \u2014 ${result.entries.length} succeeded, ${result.errors.length} failed`,
|
|
117
|
+
"",
|
|
118
|
+
...lines
|
|
119
|
+
];
|
|
120
|
+
if (errorLines.length > 0) {
|
|
121
|
+
blocks.push("", "*Errors:*", ...errorLines);
|
|
122
|
+
}
|
|
123
|
+
await respond({ response_type: "in_channel", text: blocks.join("\n") });
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
126
|
+
console.error("[slack] Sync failed:", message);
|
|
127
|
+
await respond({
|
|
128
|
+
response_type: "ephemeral",
|
|
129
|
+
text: `:x: Sync failed: ${message}`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
app.command("/list-projects", async ({ ack }) => {
|
|
134
|
+
const projectNames = Object.keys(allProjects);
|
|
135
|
+
const lines = projectNames.map((name, i) => `${i + 1}. ${name}`);
|
|
136
|
+
await ack({
|
|
137
|
+
response_type: "ephemeral",
|
|
138
|
+
text: `*Available Lingohub projects:*
|
|
139
|
+
${lines.join("\n")}`
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
app.command("/list-resources", async ({ command, ack }) => {
|
|
143
|
+
const project = command.text.trim();
|
|
144
|
+
if (!project) {
|
|
145
|
+
const projectNames = Object.keys(allProjects);
|
|
146
|
+
const lines2 = projectNames.map((name, i) => `${i + 1}. ${name}`);
|
|
147
|
+
await ack({
|
|
148
|
+
response_type: "ephemeral",
|
|
149
|
+
text: `Usage: \`/list-resources <project-name>\`
|
|
150
|
+
|
|
151
|
+
*Available projects:*
|
|
152
|
+
${lines2.join("\n")}`
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const resources = allProjects[project];
|
|
157
|
+
if (!resources) {
|
|
158
|
+
const available = Object.keys(allProjects).join(", ");
|
|
159
|
+
await ack({
|
|
160
|
+
response_type: "ephemeral",
|
|
161
|
+
text: `Unknown project \`${project}\`.
|
|
162
|
+
Available projects: ${available}`
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const lines = resources.map(
|
|
167
|
+
(r, i) => `${i + 1}. \`${r.resource}\` (${r.fileName})`
|
|
168
|
+
);
|
|
169
|
+
await ack({
|
|
170
|
+
response_type: "ephemeral",
|
|
171
|
+
text: `*Resources for "${project}":*
|
|
172
|
+
${lines.join("\n")}`
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
await app.start();
|
|
176
|
+
console.log("[slack] Bot connected via Socket Mode");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
notifySlack,
|
|
181
|
+
startSlackBot
|
|
182
|
+
};
|
|
183
|
+
//# sourceMappingURL=chunk-SMGEK5VP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/slack.ts"],"sourcesContent":["import { App } from '@slack/bolt';\nimport { config, type CMSProvider } from './config';\nimport { syncCmsContent, syncTranslations, summariseTranslationEntries, summariseCmsEntries, summariseCmsErrors } from './sync/engine';\nimport { allProjects } from '../shared/lingohub';\n\nconst VALID_CMS: CMSProvider[] = ['contentful', 'sanity'];\n\nlet app: App | null = null;\n\n/**\n * Post a message to the configured Slack notify channel.\n * Silently logs and returns if Slack is not configured or no channel is set.\n */\nexport async function notifySlack(text: string): Promise<void> {\n const { notifyChannel } = config.slack;\n if (!app || !notifyChannel) {\n console.log('[slack] Notification skipped (no app or channel configured)');\n return;\n }\n try {\n await app.client.chat.postMessage({ channel: notifyChannel, text: `[${config.environment}] ${text}` });\n } catch (err) {\n console.error('[slack] Failed to send notification:', err);\n }\n}\n\nexport async function startSlackBot(): Promise<void> {\n const { slack } = config;\n if (!slack.enabled) {\n console.log('[slack] Disabled (SLACK_BOT_TOKEN not set)');\n return;\n }\n\n app = new App({\n token: slack.botToken,\n signingSecret: slack.signingSecret,\n appToken: slack.appToken,\n socketMode: true,\n });\n\n app.command(slack.cmdSyncContent, async ({ command, ack, respond }) => {\n await ack();\n\n const args = command.text.trim().split(/\\s+/).filter(Boolean);\n const cms = (args[0] ?? 'contentful') as CMSProvider;\n const contentTypes = args.length > 1 ? args.slice(1) : undefined;\n\n if (!VALID_CMS.includes(cms)) {\n await respond({\n response_type: 'ephemeral',\n text: `Invalid CMS provider \\`${cms}\\`. Use \\`contentful\\` or \\`sanity\\`.`,\n });\n return;\n }\n\n const typesHint = contentTypes?.length ? ` (types: ${contentTypes.join(', ')})` : ' (all types)';\n await respond({\n response_type: 'in_channel',\n text: `:hourglass_flowing_sand: Sync started for *${cms}*${typesHint}…`,\n });\n\n try {\n const result = await syncCmsContent(cms, contentTypes);\n\n const blocks: string[] = [\n result.errors.length === 0\n ? `:white_check_mark: *Sync complete* — ${result.entries.length} content types synced`\n : `:warning: *Sync complete with errors* — ${result.entries.length} succeeded, ${result.errors.length} failed`,\n '',\n ...summariseCmsEntries(result),\n ];\n\n if (result.errors.length > 0) {\n blocks.push('', '*Errors:*', ...summariseCmsErrors(result));\n }\n\n await respond({ response_type: 'in_channel', text: blocks.join('\\n') });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error('[slack] Sync failed:', message);\n await respond({\n response_type: 'ephemeral',\n text: `:x: Sync failed: ${message}`,\n });\n }\n });\n\n app.command(slack.cmdSyncTranslations, async ({ command, ack, respond }) => {\n await ack();\n\n const raw = command.text.trim();\n if (!raw) {\n await respond({\n response_type: 'ephemeral',\n text:\n 'Usage: `/sync-translations <comma-separated projects> [<comma-separated locales>]`\\n' +\n 'Example: `/sync-translations tandem,tandem-(website) en,de,fr`\\n' +\n 'Omit locales to use the default locale list.',\n });\n return;\n }\n\n const spaceIdx = raw.indexOf(' ');\n const projectsPart = spaceIdx === -1 ? raw : raw.slice(0, spaceIdx).trim();\n const localesPart = spaceIdx === -1 ? '' : raw.slice(spaceIdx + 1).trim();\n\n const projects = projectsPart\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n if (projects.length === 0) {\n await respond({\n response_type: 'ephemeral',\n text:\n 'No valid projects. Pass a comma-separated list as the first argument, e.g. `tandem,tandem-(website)`.',\n });\n return;\n }\n\n let locales: string[] | undefined = localesPart.length\n ? localesPart\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : undefined;\n if (locales && locales.length === 0) {\n locales = undefined;\n }\n\n const localesHint =\n locales?.length ? ` — locales: ${locales?.join(', ')}` : ' — default locales';\n await respond({\n response_type: 'in_channel',\n text: `:hourglass_flowing_sand: Translation sync started for projects: *${projects.join(', ')}*${localesHint}…`,\n });\n\n try {\n const result = await syncTranslations(projects, locales);\n\n const lines = summariseTranslationEntries(result.entries);\n const errorLines = result.errors.map(\n (e) =>\n `• \\`${e.project}\\` / \\`${e.resource}\\` / \\`${e.locale}\\` — ${e.error}`,\n );\n\n const blocks: string[] = [\n result.errors.length === 0\n ? `:white_check_mark: *Translation sync complete* — ${result.entries.length} resource × locale uploads`\n : `:warning: *Translation sync complete with errors* — ${result.entries.length} succeeded, ${result.errors.length} failed`,\n '',\n ...lines,\n ];\n\n if (errorLines.length > 0) {\n blocks.push('', '*Errors:*', ...errorLines);\n }\n\n await respond({ response_type: 'in_channel', text: blocks.join('\\n') });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error('[slack] Sync failed:', message);\n await respond({\n response_type: 'ephemeral',\n text: `:x: Sync failed: ${message}`,\n });\n }\n });\n\n app.command('/list-projects', async ({ ack }) => {\n const projectNames = Object.keys(allProjects);\n const lines = projectNames.map((name, i) => `${i + 1}. ${name}`);\n await ack({\n response_type: 'ephemeral',\n text: `*Available Lingohub projects:*\\n${lines.join('\\n')}`,\n });\n });\n\n app.command('/list-resources', async ({ command, ack }) => {\n const project = command.text.trim();\n if (!project) {\n const projectNames = Object.keys(allProjects);\n const lines = projectNames.map((name, i) => `${i + 1}. ${name}`);\n await ack({\n response_type: 'ephemeral',\n text: `Usage: \\`/list-resources <project-name>\\`\\n\\n*Available projects:*\\n${lines.join('\\n')}`,\n });\n return;\n }\n\n const resources = allProjects[project];\n if (!resources) {\n const available = Object.keys(allProjects).join(', ');\n await ack({\n response_type: 'ephemeral',\n text: `Unknown project \\`${project}\\`.\\nAvailable projects: ${available}`,\n });\n return;\n }\n\n const lines = resources.map(\n (r, i) => `${i + 1}. \\`${r.resource}\\` (${r.fileName})`,\n );\n await ack({\n response_type: 'ephemeral',\n text: `*Resources for \"${project}\":*\\n${lines.join('\\n')}`,\n });\n });\n\n await app.start();\n console.log('[slack] Bot connected via Socket Mode');\n}\n"],"mappings":"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","names":["lines"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
config
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-75UTJIZQ.js";
|
|
5
5
|
|
|
6
6
|
// src/client/config.ts
|
|
7
7
|
import dotenv from "dotenv";
|
|
@@ -14,4 +14,4 @@ var config2 = {
|
|
|
14
14
|
export {
|
|
15
15
|
config2 as config
|
|
16
16
|
};
|
|
17
|
-
//# sourceMappingURL=chunk-
|
|
17
|
+
//# sourceMappingURL=chunk-UMWRIKPS.js.map
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
config
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-UMWRIKPS.js";
|
|
5
5
|
import {
|
|
6
6
|
fetchCmsBundles
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-JVI4MEN5.js";
|
|
8
8
|
import "../chunk-EQ3DSPTJ.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-75UTJIZQ.js";
|
|
10
10
|
import {
|
|
11
11
|
ContentStore
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-KNUP5JWR.js";
|
|
13
13
|
import "../chunk-SF7FCBR2.js";
|
|
14
14
|
|
|
15
15
|
// src/client/fetch-content-bundles.ts
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
config
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-UMWRIKPS.js";
|
|
5
5
|
import {
|
|
6
6
|
fetchMergedTranslationBundles
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-JVI4MEN5.js";
|
|
8
8
|
import "../chunk-EQ3DSPTJ.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-75UTJIZQ.js";
|
|
10
10
|
import {
|
|
11
11
|
ContentStore
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-KNUP5JWR.js";
|
|
13
13
|
import {
|
|
14
14
|
allProjects
|
|
15
15
|
} from "../chunk-SF7FCBR2.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
config
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-UMWRIKPS.js";
|
|
5
5
|
import {
|
|
6
6
|
fetchTranslationBundles
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-JVI4MEN5.js";
|
|
8
8
|
import "../chunk-EQ3DSPTJ.js";
|
|
9
|
-
import "../chunk-
|
|
9
|
+
import "../chunk-75UTJIZQ.js";
|
|
10
10
|
import {
|
|
11
11
|
ContentStore
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-KNUP5JWR.js";
|
|
13
13
|
import {
|
|
14
14
|
allProjects
|
|
15
15
|
} from "../chunk-SF7FCBR2.js";
|
package/dist/client/query-cms.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
queryCmsBundle
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-JVI4MEN5.js";
|
|
5
5
|
import "../chunk-EQ3DSPTJ.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-KNUP5JWR.js";
|
|
7
7
|
import "../chunk-SF7FCBR2.js";
|
|
8
8
|
|
|
9
9
|
// src/client/query-cms.ts
|
|
@@ -65,6 +65,8 @@ interface FetchCmsBundlesOptions {
|
|
|
65
65
|
interface TranslationFilterConfig {
|
|
66
66
|
projects: Record<string, string[]>;
|
|
67
67
|
locales?: string[];
|
|
68
|
+
/** Output key structure. `"flat"` (default) keeps dotted keys as-is; `"nested"` expands dots into nested objects. */
|
|
69
|
+
structure?: 'flat' | 'nested';
|
|
68
70
|
}
|
|
69
71
|
interface FetchTranslationBundlesOptions extends TranslationFilterConfig {
|
|
70
72
|
/** S3 GET retry; defaults via {@link getDefaultS3RetryConfig}. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { B as BundleItem, C as CMSProvider, c as CmsBundleInfo, F as FetchCmsBundlesOptions, b as FetchMergedTranslationBundlesOptions, a as FetchTranslationBundlesOptions, Q as QueryOptions, e as S3Config, f as S3RetryConfig, S as SDKConfig, T as TranslationBundleInfo, g as TranslationFilterConfig } from './index-
|
|
1
|
+
export { B as BundleItem, C as CMSProvider, c as CmsBundleInfo, F as FetchCmsBundlesOptions, b as FetchMergedTranslationBundlesOptions, a as FetchTranslationBundlesOptions, Q as QueryOptions, e as S3Config, f as S3RetryConfig, S as SDKConfig, T as TranslationBundleInfo, g as TranslationFilterConfig } from './index-DqDNlXSE.js';
|
package/dist/node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SDKConfig, F as FetchCmsBundlesOptions, a as FetchTranslationBundlesOptions, T as TranslationBundleInfo, b as FetchMergedTranslationBundlesOptions, C as CMSProvider, Q as QueryOptions } from './index-
|
|
2
|
-
export { B as BundleItem, c as CmsBundleInfo, d as ContentStore, e as S3Config, f as S3RetryConfig, g as TranslationFilterConfig, h as fetchCmsBundles, i as fetchMergedTranslationBundles, j as fetchTranslationBundles, k as getDefaultS3RetryConfig, q as queryCmsBundle } from './index-
|
|
1
|
+
import { S as SDKConfig, F as FetchCmsBundlesOptions, a as FetchTranslationBundlesOptions, T as TranslationBundleInfo, b as FetchMergedTranslationBundlesOptions, C as CMSProvider, Q as QueryOptions } from './index-DqDNlXSE.js';
|
|
2
|
+
export { B as BundleItem, c as CmsBundleInfo, d as ContentStore, e as S3Config, f as S3RetryConfig, g as TranslationFilterConfig, h as fetchCmsBundles, i as fetchMergedTranslationBundles, j as fetchTranslationBundles, k as getDefaultS3RetryConfig, q as queryCmsBundle } from './index-DqDNlXSE.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Trims nested object depth.
|
package/dist/node.js
CHANGED
|
@@ -61,8 +61,9 @@ var ContentStore = class {
|
|
|
61
61
|
var buildCmsObjectKey = (cms, contentType) => {
|
|
62
62
|
return `${cms}-${contentType}.json`;
|
|
63
63
|
};
|
|
64
|
+
var sanitiseKeySegment = (s) => s.replace(/[()]/g, "");
|
|
64
65
|
var buildTranslationObjectKey = (project, fileName, locale) => {
|
|
65
|
-
return `lingohub-${project}.${fileName.replaceAll("[locale]", locale)}`;
|
|
66
|
+
return `lingohub-${sanitiseKeySegment(project)}.${fileName.replaceAll("[locale]", locale)}`;
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
// src/shared/bundles.ts
|
|
@@ -425,7 +426,7 @@ var transformObjectToFlat = (data) => {
|
|
|
425
426
|
const result = {};
|
|
426
427
|
const flatten = (obj, path3 = []) => {
|
|
427
428
|
Object.entries(obj).forEach(([key, value]) => {
|
|
428
|
-
if (typeof value === "object") {
|
|
429
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
429
430
|
flatten(value, path3.concat(key));
|
|
430
431
|
} else {
|
|
431
432
|
result[path3.concat(key).join(".")] = value;
|
|
@@ -443,7 +444,10 @@ var convertXMLToJS = (xml) => {
|
|
|
443
444
|
compact: true
|
|
444
445
|
});
|
|
445
446
|
let mapped = {};
|
|
446
|
-
converted.resources.string
|
|
447
|
+
const strings = converted.resources.string;
|
|
448
|
+
const items = Array.isArray(strings) ? strings : [strings];
|
|
449
|
+
items.forEach((item) => {
|
|
450
|
+
if (!item?._attributes?.name) return;
|
|
447
451
|
mapped = {
|
|
448
452
|
...mapped,
|
|
449
453
|
[item._attributes.name]: item._text
|
|
@@ -454,10 +458,11 @@ var convertXMLToJS = (xml) => {
|
|
|
454
458
|
var parseIOSStrings = (strings) => {
|
|
455
459
|
const parsedObj = {};
|
|
456
460
|
strings.split("\n").filter((line) => line.startsWith('"') && line.endsWith(";")).map((line) => line.trim().slice(0, -1)).forEach((line) => {
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
key =
|
|
460
|
-
value =
|
|
461
|
+
const eqIdx = line.indexOf(" = ");
|
|
462
|
+
if (eqIdx === -1) return;
|
|
463
|
+
const key = line.slice(1, eqIdx - 1);
|
|
464
|
+
const value = line.slice(eqIdx + 3 + 1, -1);
|
|
465
|
+
if (!key) return;
|
|
461
466
|
parsedObj[key] = value;
|
|
462
467
|
});
|
|
463
468
|
return parsedObj;
|
|
@@ -508,6 +513,24 @@ function translationJsonOutputPath(outputDir, objectKey) {
|
|
|
508
513
|
}
|
|
509
514
|
|
|
510
515
|
// src/shared/bundles.ts
|
|
516
|
+
function nestKeys(flat) {
|
|
517
|
+
if (typeof flat !== "object" || flat === null || Array.isArray(flat)) return flat;
|
|
518
|
+
const src = flat;
|
|
519
|
+
const result = {};
|
|
520
|
+
for (const [key, value] of Object.entries(src)) {
|
|
521
|
+
const parts = key.split(".");
|
|
522
|
+
let cur = result;
|
|
523
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
524
|
+
const seg = parts[i];
|
|
525
|
+
if (!(seg in cur) || typeof cur[seg] !== "object" || cur[seg] === null) {
|
|
526
|
+
cur[seg] = {};
|
|
527
|
+
}
|
|
528
|
+
cur = cur[seg];
|
|
529
|
+
}
|
|
530
|
+
cur[parts[parts.length - 1]] = value;
|
|
531
|
+
}
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
511
534
|
function getAtPath(obj, dottedPath) {
|
|
512
535
|
const parts = dottedPath.split(".").filter((p) => p.length > 0);
|
|
513
536
|
if (parts.length === 0) return { found: false };
|
|
@@ -585,7 +608,8 @@ function filterResources(projectResources, resourceFilter) {
|
|
|
585
608
|
return projectResources.filter((r) => resourceFilter.includes(r.resource));
|
|
586
609
|
}
|
|
587
610
|
async function fetchTranslationBundles(store, outputDir, options) {
|
|
588
|
-
const { projects, locales } = options;
|
|
611
|
+
const { projects, locales, structure } = options;
|
|
612
|
+
const nested = structure === "nested";
|
|
589
613
|
const retry = options.retry ?? getDefaultS3RetryConfig();
|
|
590
614
|
await fs.mkdir(outputDir, { recursive: true });
|
|
591
615
|
const result = {};
|
|
@@ -612,11 +636,12 @@ async function fetchTranslationBundles(store, outputDir, options) {
|
|
|
612
636
|
resourceTasks.map(async ({ objectKey, resource }) => {
|
|
613
637
|
const raw = await downloadRawWithRetry(store, objectKey, retry);
|
|
614
638
|
const parsed = parseTranslationResourceRaw(raw, resource);
|
|
639
|
+
const output = nested ? nestKeys(parsed) : parsed;
|
|
615
640
|
const filePath = translationJsonOutputPath(outputDir, objectKey);
|
|
616
641
|
await fs.mkdir(path2.dirname(filePath), { recursive: true });
|
|
617
642
|
await fs.writeFile(
|
|
618
643
|
filePath,
|
|
619
|
-
JSON.stringify(
|
|
644
|
+
JSON.stringify(output, null, 2),
|
|
620
645
|
"utf-8"
|
|
621
646
|
);
|
|
622
647
|
if (!result[project]) {
|
|
@@ -630,7 +655,8 @@ async function fetchTranslationBundles(store, outputDir, options) {
|
|
|
630
655
|
return result;
|
|
631
656
|
}
|
|
632
657
|
async function fetchMergedTranslationBundles(store, outputDir, options) {
|
|
633
|
-
const { projects, locales } = options;
|
|
658
|
+
const { projects, locales, structure } = options;
|
|
659
|
+
const nested = structure === "nested";
|
|
634
660
|
const retry = options.retry ?? getDefaultS3RetryConfig();
|
|
635
661
|
await fs.mkdir(outputDir, { recursive: true });
|
|
636
662
|
const localesToSync = locales && locales.length ? locales : defaultLocales;
|
|
@@ -670,9 +696,10 @@ async function fetchMergedTranslationBundles(store, outputDir, options) {
|
|
|
670
696
|
const out = {};
|
|
671
697
|
for (const loc of localesToSync) {
|
|
672
698
|
const filePath = path2.resolve(outputDir, `${loc}.json`);
|
|
699
|
+
const output = nested ? nestKeys(merged[loc]) : merged[loc];
|
|
673
700
|
await fs.writeFile(
|
|
674
701
|
filePath,
|
|
675
|
-
JSON.stringify(
|
|
702
|
+
JSON.stringify(output, null, 2),
|
|
676
703
|
"utf-8"
|
|
677
704
|
);
|
|
678
705
|
out[loc] = filePath;
|