@transi-store/cli 1.4.0 → 1.5.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # CHANGELOG
2
+
3
+ ## 1.5.1
4
+
5
+ ### Fixed
6
+
7
+ Fix broken link on common package.
8
+
9
+ ## 1.5.0
10
+
11
+ ### Added
12
+
13
+ - Accept `--branch=@all` in CLI to download translations from all branches.
14
+
15
+ ## 1.4.0
16
+
17
+ ### Added
18
+
19
+ - Skip unchanged files in `upload:config` when running on a git feature branch (compares to main branch).
20
+
21
+ ## 1.3.0
22
+
23
+ ### Added
24
+
25
+ - Added `upload:config` command to CLI to upload translations based on a config file.
26
+ - Added `--branch` option to CLI in "download:config" mode.
27
+
28
+ ## 1.2.0
29
+
30
+ ### Added
31
+
32
+ - Added `--branch` option to CLI to specify branch slug for fetching translations. ([#72](https://github.com/transi-store/transi-store/pull/72))
package/README.md CHANGED
@@ -23,7 +23,7 @@ transi-store --help
23
23
  #### Options
24
24
 
25
25
  - `--config <path>`: Path to the configuration file (default: `transi-store.config.json`).
26
- - `--branch`: The branch to upload the translations to. Defaults to "main".
26
+ - `--branch`: The branch to download the translations from. Defaults to "main". Use `@all` to download all translations across every branch (no branch filtering).
27
27
 
28
28
  #### Configuration file example
29
29
 
@@ -95,6 +95,5 @@ To publish a new version of the CLI package, follow these steps:
95
95
 
96
96
  ```bash
97
97
  yarn build:cli
98
- cd packages/cli
99
- npm publish --access public
98
+ yarn workspace @transi-store/cli npm publish --access public
100
99
  ```
package/dist/cli.js CHANGED
@@ -1,73 +1,267 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, Option } from "@commander-js/extra-typings";
3
- import { DEFAULT_DOMAIN_ROOT, fetchForConfig, fetchTranslations, } from "./fetchTranslations.js";
4
- import { ImportStrategy, uploadForConfig, uploadTranslations, } from "./uploadTranslations.js";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import z from "zod";
7
+ import { simpleGit } from "simple-git";
8
+ //#region ../common/dist/import-strategy.js
9
+ var ImportStrategy;
10
+ (function(ImportStrategy) {
11
+ ImportStrategy["OVERWRITE"] = "overwrite";
12
+ ImportStrategy["SKIP"] = "skip";
13
+ })(ImportStrategy || (ImportStrategy = {}));
14
+ //#endregion
15
+ //#region ../common/dist/constants.js
16
+ const DEFAULT_DOMAIN_ROOT = "https://transi-store.com";
17
+ const ALL_BRANCHES_VALUE = "@all";
18
+ //#endregion
19
+ //#region ../common/dist/config-schema.js
20
+ const configItemSchema = z.object({
21
+ project: z.string().nonempty(),
22
+ langs: z.array(z.string().regex(/^[a-z]{2}(?:-[A-Za-z]{2,})?$/)).min(1).refine((arr) => new Set(arr).size === arr.length, { message: "langs must contain unique items" }),
23
+ format: z.enum([
24
+ "json",
25
+ "yaml",
26
+ "po",
27
+ "xlf",
28
+ "xliff",
29
+ "csv"
30
+ ]),
31
+ output: z.string().nonempty().refine((s) => s.includes("<lang>"), { message: "output must contain '<lang>' placeholder" })
32
+ });
33
+ const schema = z.object({
34
+ $schema: z.url().optional(),
35
+ domainRoot: z.url().optional(),
36
+ org: z.string().nonempty(),
37
+ projects: z.array(configItemSchema).min(1)
38
+ });
39
+ //#endregion
40
+ //#region src/fetchTranslations.ts
41
+ async function fetchTranslations({ domainRoot, apiKey, org, project, format, locale, output, branch }) {
42
+ const params = new URLSearchParams({
43
+ format,
44
+ locale
45
+ });
46
+ if (branch) params.set("branch", branch);
47
+ const url = `${domainRoot}/api/orgs/${org}/projects/${project}/translations?${params.toString()}`;
48
+ try {
49
+ const content = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
50
+ if (!content.ok) {
51
+ const errorData = await content.text();
52
+ console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, errorData);
53
+ process.exit(1);
54
+ }
55
+ const data = await content.json();
56
+ if (!content.ok) {
57
+ console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, data.error);
58
+ process.exit(1);
59
+ }
60
+ const dir = path.dirname(output);
61
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
62
+ fs.writeFileSync(output, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
63
+ console.log(`Translations for project "${project}" and locale "${locale}" "saved to "${output}"`);
64
+ } catch (error) {
65
+ console.error("Error exporting translations:", error);
66
+ process.exit(1);
67
+ }
68
+ }
69
+ async function fetchForConfig(configPath, apiKey, branch) {
70
+ const cwd = process.cwd();
71
+ const fullPath = path.resolve(cwd, configPath);
72
+ if (!fs.existsSync(fullPath)) {
73
+ console.error(`Config file not found: ${configPath}`);
74
+ process.exit(1);
75
+ }
76
+ const config = (await import(pathToFileURL(fullPath).href, { with: { type: "json" } })).default;
77
+ const result = schema.safeParse(config);
78
+ if (!result.success) {
79
+ const pretty = z.prettifyError(result.error);
80
+ console.error("Config validation error:", pretty);
81
+ process.exit(1);
82
+ }
83
+ const domainRoot = result.data.domainRoot ?? "https://transi-store.com";
84
+ console.log(`Fetching translations from domain "${domainRoot}" for org "${result.data.org}"...`);
85
+ for (const configItem of result.data.projects) for (const locale of configItem.langs) fetchTranslations({
86
+ domainRoot,
87
+ apiKey,
88
+ org: result.data.org,
89
+ project: configItem.project,
90
+ format: configItem.format,
91
+ locale,
92
+ output: configItem.output.replace("<lang>", locale).replace("<project>", configItem.project).replace("<format>", configItem.format),
93
+ branch
94
+ });
95
+ }
96
+ //#endregion
97
+ //#region src/git.ts
98
+ let gitInstance;
99
+ function getGit() {
100
+ if (!gitInstance) gitInstance = simpleGit();
101
+ return gitInstance;
102
+ }
103
+ async function isGitRepository() {
104
+ try {
105
+ return await getGit().checkIsRepo();
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+ /**
111
+ * Detect the default branch (main/master) with remote or local ref.
112
+ * Returns null if not found.
113
+ */
114
+ async function getDefaultBranch() {
115
+ const git = getGit();
116
+ try {
117
+ const branches = await git.branch(["-r"]);
118
+ if (branches.all.includes("origin/main")) return "origin/main";
119
+ if (branches.all.includes("origin/master")) return "origin/master";
120
+ } catch {}
121
+ try {
122
+ const branches = await git.branchLocal();
123
+ if (branches.all.includes("main")) return "main";
124
+ if (branches.all.includes("master")) return "master";
125
+ } catch {}
126
+ return null;
127
+ }
128
+ async function getCurrentBranch() {
129
+ try {
130
+ const { current } = await getGit().branchLocal();
131
+ return current || null;
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+ /**
137
+ * Returns the set of absolute paths of files that have been modified
138
+ * compared to the given base branch, including untracked files.
139
+ */
140
+ async function getModifiedFiles(baseBranch) {
141
+ const git = getGit();
142
+ const repoRoot = (await git.revparse(["--show-toplevel"])).trim();
143
+ const diffFiles = (await git.diff(["--name-only", baseBranch])).trim().split("\n").filter(Boolean);
144
+ const status = await git.status();
145
+ const modified = /* @__PURE__ */ new Set();
146
+ for (const f of [...diffFiles, ...status.not_added]) modified.add(`${repoRoot}/${f}`);
147
+ return modified;
148
+ }
149
+ //#endregion
150
+ //#region src/uploadTranslations.ts
151
+ async function uploadTranslations({ domainRoot, apiKey, org, project, locale, input, strategy, format, branch }) {
152
+ const url = `${domainRoot}/api/orgs/${org}/projects/${project}/translations`;
153
+ const filePath = path.resolve(input);
154
+ if (!fs.existsSync(filePath)) {
155
+ console.error(`File not found: ${input}`);
156
+ process.exit(1);
157
+ }
158
+ const fileContent = fs.readFileSync(filePath);
159
+ const fileName = path.basename(filePath);
160
+ const formData = new FormData();
161
+ formData.append("file", new Blob([fileContent]), fileName);
162
+ formData.append("locale", locale);
163
+ formData.append("strategy", strategy);
164
+ if (format) formData.append("format", format);
165
+ if (branch) formData.append("branch", branch);
166
+ try {
167
+ const response = await fetch(url, {
168
+ method: "POST",
169
+ headers: { Authorization: `Bearer ${apiKey}` },
170
+ body: formData
171
+ });
172
+ const data = await response.json();
173
+ if (!response.ok) {
174
+ console.error(`Failed to import translations: ${response.status} ${response.statusText}\n`, data.error, data.details ? `\nDetails: ${data.details}` : "");
175
+ process.exit(1);
176
+ }
177
+ console.log(`Translations imported for project "${project}" locale "${locale}":`);
178
+ console.log(` Total keys: ${data.stats.total}`);
179
+ console.log(` Keys created: ${data.stats.keysCreated}`);
180
+ console.log(` Translations created: ${data.stats.translationsCreated}`);
181
+ console.log(` Translations updated: ${data.stats.translationsUpdated}`);
182
+ console.log(` Translations skipped: ${data.stats.translationsSkipped}`);
183
+ } catch (error) {
184
+ console.error("Error importing translations:", error);
185
+ process.exit(1);
186
+ }
187
+ }
188
+ async function uploadForConfig(configPath, apiKey, strategy, branch) {
189
+ const cwd = process.cwd();
190
+ const fullPath = path.resolve(cwd, configPath);
191
+ if (!fs.existsSync(fullPath)) {
192
+ console.error(`Config file not found: ${configPath}`);
193
+ process.exit(1);
194
+ }
195
+ const config = (await import(pathToFileURL(fullPath).href, { with: { type: "json" } })).default;
196
+ const result = schema.safeParse(config);
197
+ if (!result.success) {
198
+ const pretty = z.prettifyError(result.error);
199
+ console.error("Config validation error:", pretty);
200
+ process.exit(1);
201
+ }
202
+ const domainRoot = result.data.domainRoot ?? "https://transi-store.com";
203
+ console.log(`Uploading translations to domain "${domainRoot}" for org "${result.data.org}"...`);
204
+ let modifiedFiles = null;
205
+ if (await isGitRepository()) {
206
+ const defaultBranch = await getDefaultBranch();
207
+ const currentBranch = await getCurrentBranch();
208
+ if (defaultBranch && currentBranch && currentBranch !== defaultBranch.replace(/^origin\//, "")) {
209
+ modifiedFiles = await getModifiedFiles(defaultBranch);
210
+ console.log(`Git optimization enabled: only uploading files modified compared to "${defaultBranch}"`);
211
+ }
212
+ }
213
+ for (const configItem of result.data.projects) for (const locale of configItem.langs) {
214
+ const input = configItem.output.replace("<lang>", locale).replace("<project>", configItem.project).replace("<format>", configItem.format);
215
+ const resolvedInput = path.resolve(cwd, input);
216
+ if (!fs.existsSync(resolvedInput)) {
217
+ console.log(`Skipping project "${configItem.project}" locale "${locale}": file not found "${input}"`);
218
+ continue;
219
+ }
220
+ if (modifiedFiles && !modifiedFiles.has(resolvedInput)) {
221
+ console.log(`Skipping project "${configItem.project}" locale "${locale}": file not modified`);
222
+ continue;
223
+ }
224
+ await uploadTranslations({
225
+ domainRoot,
226
+ apiKey,
227
+ org: result.data.org,
228
+ project: configItem.project,
229
+ format: configItem.format,
230
+ locale,
231
+ input,
232
+ strategy,
233
+ branch
234
+ });
235
+ }
236
+ }
237
+ //#endregion
238
+ //#region src/cli.ts
5
239
  const program = new Command();
6
- const apiKeyOption = new Option("-k, --api-key <apiKey>", "API key for authentication")
7
- .env("TRANSI_STORE_API_KEY")
8
- .makeOptionMandatory();
9
- program
10
- .command("download")
11
- .description("Download translations for a project")
12
- .addOption(apiKeyOption)
13
- .requiredOption("-d, --domain-root <domainRoot>", "Root domain to download translations from (default is https://transi-store.com)", DEFAULT_DOMAIN_ROOT)
14
- .requiredOption("-o, --org <org>", "Organization slug")
15
- .requiredOption("-p, --project <project>", "Project slug")
16
- .requiredOption("-l, --locale <locale>", "Locale to export")
17
- .requiredOption("-O, --output <output>", "Output file path")
18
- .option("-f, --format <format>", "Export format (json, csv, etc.)", "json")
19
- .option("-b, --branch <branch>", "Branch slug (exports main + branch keys)")
20
- .action((options) => {
21
- fetchTranslations(options);
240
+ const apiKeyOption = new Option("-k, --api-key <apiKey>", "API key for authentication").env("TRANSI_STORE_API_KEY").makeOptionMandatory();
241
+ program.command("download").description("Download translations for a project").addOption(apiKeyOption).requiredOption("-d, --domain-root <domainRoot>", "Root domain to download translations from (default is https://transi-store.com)", DEFAULT_DOMAIN_ROOT).requiredOption("-o, --org <org>", "Organization slug").requiredOption("-p, --project <project>", "Project slug").requiredOption("-l, --locale <locale>", "Locale to export").requiredOption("-O, --output <output>", "Output file path").option("-f, --format <format>", "Export format (json, csv, etc.)", "json").option("-b, --branch <branch>", `Branch slug (exports main + branch keys). Use "${ALL_BRANCHES_VALUE}" to export all branches`).action((options) => {
242
+ fetchTranslations(options);
22
243
  });
23
- program
24
- .command("upload")
25
- .description("Upload translations for a project")
26
- .addOption(apiKeyOption)
27
- .requiredOption("-d, --domain-root <domainRoot>", "Root domain to upload translations to (default is https://transi-store.com)", DEFAULT_DOMAIN_ROOT)
28
- .requiredOption("-o, --org <org>", "Organization slug")
29
- .requiredOption("-p, --project <project>", "Project slug")
30
- .requiredOption("-l, --locale <locale>", "Target locale")
31
- .requiredOption("-I, --input <input>", "Input file path (JSON or XLIFF)")
32
- .option("-s, --strategy <strategy>", `Import strategy: '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}' existing translations`, ImportStrategy.SKIP)
33
- .option("-f, --format <format>", "File format (json or xliff). Auto-detected from extension if omitted")
34
- .option("-b, --branch <branch>", "Branch slug (new keys will be created on this branch)")
35
- .action((options) => {
36
- const strategy = options.strategy;
37
- if (strategy !== ImportStrategy.OVERWRITE &&
38
- strategy !== ImportStrategy.SKIP) {
39
- console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
40
- process.exit(1);
41
- }
42
- uploadTranslations({
43
- ...options,
44
- strategy,
45
- });
244
+ program.command("upload").description("Upload translations for a project").addOption(apiKeyOption).requiredOption("-d, --domain-root <domainRoot>", "Root domain to upload translations to (default is https://transi-store.com)", DEFAULT_DOMAIN_ROOT).requiredOption("-o, --org <org>", "Organization slug").requiredOption("-p, --project <project>", "Project slug").requiredOption("-l, --locale <locale>", "Target locale").requiredOption("-I, --input <input>", "Input file path (JSON or XLIFF)").option("-s, --strategy <strategy>", `Import strategy: '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}' existing translations`, ImportStrategy.SKIP).option("-f, --format <format>", "File format (json or xliff). Auto-detected from extension if omitted").option("-b, --branch <branch>", "Branch slug (new keys will be created on this branch)").action((options) => {
245
+ const strategy = options.strategy;
246
+ if (strategy !== ImportStrategy.OVERWRITE && strategy !== ImportStrategy.SKIP) {
247
+ console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
248
+ process.exit(1);
249
+ }
250
+ uploadTranslations({
251
+ ...options,
252
+ strategy
253
+ });
46
254
  });
47
- program
48
- .command("download:config", { isDefault: true })
49
- .description("Use configuration from config file")
50
- .option("-c, --config <config>", "Path to config file", "transi-store.config.json")
51
- .option("-b, --branch <branch>", "Branch slug (exports main + branch keys)")
52
- .addOption(apiKeyOption)
53
- .action((options) => {
54
- fetchForConfig(options.config, options.apiKey, options.branch);
255
+ program.command("download:config", { isDefault: true }).description("Use configuration from config file").option("-c, --config <config>", "Path to config file", "transi-store.config.json").option("-b, --branch <branch>", `Branch slug (exports main + branch keys). Use "${ALL_BRANCHES_VALUE}" to export all branches`).addOption(apiKeyOption).action((options) => {
256
+ fetchForConfig(options.config, options.apiKey, options.branch);
55
257
  });
56
- program
57
- .command("upload:config")
58
- .description("Upload translations using configuration from config file")
59
- .option("-c, --config <config>", "Path to config file", "transi-store.config.json")
60
- .option("-b, --branch <branch>", "Branch slug (new keys will be created on this branch)")
61
- .option("-s, --strategy <strategy>", `Import strategy: '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}' existing translations`, ImportStrategy.SKIP)
62
- .addOption(apiKeyOption)
63
- .action((options) => {
64
- const strategy = options.strategy;
65
- if (strategy !== ImportStrategy.OVERWRITE &&
66
- strategy !== ImportStrategy.SKIP) {
67
- console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
68
- process.exit(1);
69
- }
70
- uploadForConfig(options.config, options.apiKey, strategy, options.branch);
258
+ program.command("upload:config").description("Upload translations using configuration from config file").option("-c, --config <config>", "Path to config file", "transi-store.config.json").option("-b, --branch <branch>", "Branch slug (new keys will be created on this branch)").option("-s, --strategy <strategy>", `Import strategy: '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}' existing translations`, ImportStrategy.SKIP).addOption(apiKeyOption).action((options) => {
259
+ const strategy = options.strategy;
260
+ if (strategy !== ImportStrategy.OVERWRITE && strategy !== ImportStrategy.SKIP) {
261
+ console.error(`Invalid strategy. Use '${ImportStrategy.OVERWRITE}' or '${ImportStrategy.SKIP}'.`);
262
+ process.exit(1);
263
+ }
264
+ uploadForConfig(options.config, options.apiKey, strategy, options.branch);
71
265
  });
72
266
  program.parse();
73
- //# sourceMappingURL=cli.js.map
267
+ //#endregion
@@ -1,4 +1,3 @@
1
- export declare const DEFAULT_DOMAIN_ROOT = "https://transi-store.com";
2
1
  export type Config = {
3
2
  domainRoot: string;
4
3
  org: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fetchTranslations.d.ts","sourceRoot":"","sources":["../src/fetchTranslations.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,mBAAmB,6BAA6B,CAAC;AAE9D,MAAM,MAAM,MAAM,GAAG;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,wBAAsB,iBAAiB,CAAC,EACtC,UAAU,EACV,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,MAAM,iBA+CR;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
1
+ {"version":3,"file":"fetchTranslations.d.ts","sourceRoot":"","sources":["../src/fetchTranslations.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,MAAM,GAAG;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,wBAAsB,iBAAiB,CAAC,EACtC,UAAU,EACV,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,MAAM,iBA+CR;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
@@ -1,7 +1,4 @@
1
- export declare enum ImportStrategy {
2
- OVERWRITE = "overwrite",
3
- SKIP = "skip"
4
- }
1
+ import { ImportStrategy } from "@transi-store/common";
5
2
  export type UploadConfig = {
6
3
  domainRoot: string;
7
4
  org: string;
@@ -1 +1 @@
1
- {"version":3,"file":"uploadTranslations.d.ts","sourceRoot":"","sources":["../src/uploadTranslations.ts"],"names":[],"mappings":"AAaA,oBAAY,cAAc;IACxB,SAAS,cAAc;IACvB,IAAI,SAAS;CACd;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,GACP,EAAE,YAAY,iBA0Dd;AAED,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAkFf"}
1
+ {"version":3,"file":"uploadTranslations.d.ts","sourceRoot":"","sources":["../src/uploadTranslations.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAUtD,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,MAAM,EACN,GAAG,EACH,OAAO,EACP,MAAM,EACN,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,GACP,EAAE,YAAY,iBA0Dd;AAED,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,cAAc,EACxB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAkFf"}
package/package.json CHANGED
@@ -1,10 +1,9 @@
1
1
  {
2
2
  "name": "@transi-store/cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dist/cli.js",
7
- "./schema": "./dist/schema.js",
8
7
  "./schema.json": "./dist/schema.json"
9
8
  },
10
9
  "bin": {
@@ -17,13 +16,17 @@
17
16
  "zod": "^4.3.6"
18
17
  },
19
18
  "devDependencies": {
19
+ "@transi-store/common": "^1.0.0",
20
+ "rolldown": "^1.0.0-rc.12",
20
21
  "typescript": "^5.7.3"
21
22
  },
22
23
  "scripts": {
23
- "build": "tsc",
24
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
25
+ "build": "rolldown -c && tsc --emitDeclarationOnly && yarn build:schema",
26
+ "build:schema": "node ./scripts/create-json-schema.mjs",
24
27
  "lint:types": "tsc --noEmit"
25
28
  },
26
29
  "files": [
27
30
  "dist"
28
31
  ]
29
- }
32
+ }
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,85 +0,0 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
- import fs from "node:fs";
10
- import path from "node:path";
11
- import { pathToFileURL } from "node:url";
12
- import z from "zod";
13
- import schema from "./schema.js";
14
- export const DEFAULT_DOMAIN_ROOT = "https://transi-store.com";
15
- export async function fetchTranslations({ domainRoot, apiKey, org, project, format, locale, output, branch, }) {
16
- const params = new URLSearchParams({ format, locale });
17
- if (branch) {
18
- params.set("branch", branch);
19
- }
20
- const url = `${domainRoot}/api/orgs/${org}/projects/${project}/export?${params.toString()}`;
21
- try {
22
- const content = await fetch(url, {
23
- headers: {
24
- Authorization: `Bearer ${apiKey}`,
25
- },
26
- });
27
- if (!content.ok) {
28
- const errorData = await content.text();
29
- console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, errorData);
30
- process.exit(1);
31
- }
32
- const data = await content.json();
33
- if (!content.ok) {
34
- console.error(`Failed to fetch translations: ${content.status} ${content.statusText}\n`, data.error);
35
- process.exit(1);
36
- }
37
- // create directory if not exists
38
- const dir = path.dirname(output);
39
- if (!fs.existsSync(dir)) {
40
- fs.mkdirSync(dir, { recursive: true });
41
- }
42
- fs.writeFileSync(output, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
43
- console.log(`Translations for project "${project}" and locale "${locale}" "saved to "${output}"`);
44
- }
45
- catch (error) {
46
- console.error("Error exporting translations:", error);
47
- process.exit(1);
48
- }
49
- }
50
- export async function fetchForConfig(configPath, apiKey, branch) {
51
- const cwd = process.cwd();
52
- const fullPath = path.resolve(cwd, configPath);
53
- if (!fs.existsSync(fullPath)) {
54
- console.error(`Config file not found: ${configPath}`);
55
- process.exit(1);
56
- }
57
- const config = (await import(__rewriteRelativeImportExtension(pathToFileURL(fullPath).href), { with: { type: "json" } })).default;
58
- const result = schema.safeParse(config);
59
- if (!result.success) {
60
- const pretty = z.prettifyError(result.error);
61
- console.error("Config validation error:", pretty);
62
- process.exit(1);
63
- }
64
- const domainRoot = result.data.domainRoot ?? DEFAULT_DOMAIN_ROOT;
65
- console.log(`Fetching translations from domain "${domainRoot}" for org "${result.data.org}"...`);
66
- for (const configItem of result.data.projects) {
67
- for (const locale of configItem.langs) {
68
- const options = {
69
- domainRoot,
70
- apiKey,
71
- org: result.data.org,
72
- project: configItem.project,
73
- format: configItem.format,
74
- locale,
75
- output: configItem.output
76
- .replace("<lang>", locale)
77
- .replace("<project>", configItem.project)
78
- .replace("<format>", configItem.format),
79
- branch,
80
- };
81
- fetchTranslations(options);
82
- }
83
- }
84
- }
85
- //# sourceMappingURL=fetchTranslations.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetchTranslations.js","sourceRoot":"","sources":["../src/fetchTranslations.ts"],"names":[],"mappings":"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"}
package/dist/git.js DELETED
@@ -1,74 +0,0 @@
1
- import { simpleGit } from "simple-git";
2
- let gitInstance;
3
- function getGit() {
4
- if (!gitInstance) {
5
- gitInstance = simpleGit();
6
- }
7
- return gitInstance;
8
- }
9
- export async function isGitRepository() {
10
- try {
11
- return await getGit().checkIsRepo();
12
- }
13
- catch {
14
- return false;
15
- }
16
- }
17
- /**
18
- * Detect the default branch (main/master) with remote or local ref.
19
- * Returns null if not found.
20
- */
21
- export async function getDefaultBranch() {
22
- const git = getGit();
23
- // Check remote branches
24
- try {
25
- const branches = await git.branch(["-r"]);
26
- if (branches.all.includes("origin/main"))
27
- return "origin/main";
28
- if (branches.all.includes("origin/master"))
29
- return "origin/master";
30
- }
31
- catch {
32
- // ignore
33
- }
34
- // Fallback to local branches
35
- try {
36
- const branches = await git.branchLocal();
37
- if (branches.all.includes("main"))
38
- return "main";
39
- if (branches.all.includes("master"))
40
- return "master";
41
- }
42
- catch {
43
- // ignore
44
- }
45
- return null;
46
- }
47
- export async function getCurrentBranch() {
48
- try {
49
- const { current } = await getGit().branchLocal();
50
- return current || null;
51
- }
52
- catch {
53
- return null;
54
- }
55
- }
56
- /**
57
- * Returns the set of absolute paths of files that have been modified
58
- * compared to the given base branch, including untracked files.
59
- */
60
- export async function getModifiedFiles(baseBranch) {
61
- const git = getGit();
62
- const repoRoot = (await git.revparse(["--show-toplevel"])).trim();
63
- // Files that differ from base branch (committed + staged + unstaged changes)
64
- const diff = await git.diff(["--name-only", baseBranch]);
65
- const diffFiles = diff.trim().split("\n").filter(Boolean);
66
- // Untracked files (not in git at all)
67
- const status = await git.status();
68
- const modified = new Set();
69
- for (const f of [...diffFiles, ...status.not_added]) {
70
- modified.add(`${repoRoot}/${f}`);
71
- }
72
- return modified;
73
- }
74
- //# sourceMappingURL=git.js.map
package/dist/git.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,YAAY,CAAC;AAEvD,IAAI,WAAkC,CAAC;AAEvC,SAAS,MAAM;IACb,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,SAAS,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IAErB,wBAAwB;IACxB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC;YAAE,OAAO,aAAa,CAAC;QAC/D,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC;YAAE,OAAO,eAAe,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IAED,6BAA6B;IAC7B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;QACjD,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,EAAE,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,OAAO,IAAI,IAAI,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAkB;IAElB,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IAErB,MAAM,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAElE,6EAA6E;IAC7E,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAE1D,sCAAsC;IACtC,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;IAElC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACpD,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/dist/schema.d.ts DELETED
@@ -1,21 +0,0 @@
1
- import z from "zod";
2
- declare const schema: z.ZodObject<{
3
- $schema: z.ZodOptional<z.ZodURL>;
4
- domainRoot: z.ZodOptional<z.ZodURL>;
5
- org: z.ZodString;
6
- projects: z.ZodArray<z.ZodObject<{
7
- project: z.ZodString;
8
- langs: z.ZodArray<z.ZodString>;
9
- format: z.ZodEnum<{
10
- json: "json";
11
- yaml: "yaml";
12
- po: "po";
13
- xlf: "xlf";
14
- xliff: "xliff";
15
- csv: "csv";
16
- }>;
17
- output: z.ZodString;
18
- }, z.z.core.$strip>>;
19
- }, z.z.core.$strip>;
20
- export default schema;
21
- //# sourceMappingURL=schema.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAC;AAmBpB,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;mBAKV,CAAC;AAEH,eAAe,MAAM,CAAC"}
package/dist/schema.js DELETED
@@ -1,25 +0,0 @@
1
- import z from "zod";
2
- const configItemSchema = z.object({
3
- project: z.string().nonempty(),
4
- langs: z
5
- .array(z.string().regex(/^[a-z]{2}(?:-[A-Za-z]{2,})?$/))
6
- .min(1)
7
- .refine((arr) => new Set(arr).size === arr.length, {
8
- message: "langs must contain unique items",
9
- }),
10
- format: z.enum(["json", "yaml", "po", "xlf", "xliff", "csv"]),
11
- output: z
12
- .string()
13
- .nonempty()
14
- .refine((s) => s.includes("<lang>"), {
15
- message: "output must contain '<lang>' placeholder",
16
- }),
17
- });
18
- const schema = z.object({
19
- $schema: z.url().optional(),
20
- domainRoot: z.url().optional(),
21
- org: z.string().nonempty(),
22
- projects: z.array(configItemSchema).min(1),
23
- });
24
- export default schema;
25
- //# sourceMappingURL=schema.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAC;AAEpB,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,KAAK,EAAE,CAAC;SACL,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;SACvD,GAAG,CAAC,CAAC,CAAC;SACN,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,EAAE;QACjD,OAAO,EAAE,iCAAiC;KAC3C,CAAC;IACJ,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7D,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;QACnC,OAAO,EAAE,0CAA0C;KACpD,CAAC;CACL,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC3B,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAC9B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAC3C,CAAC,CAAC;AAEH,eAAe,MAAM,CAAC"}
@@ -1,122 +0,0 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
- import fs from "node:fs";
10
- import path from "node:path";
11
- import { pathToFileURL } from "node:url";
12
- import z from "zod";
13
- import { DEFAULT_DOMAIN_ROOT } from "./fetchTranslations.js";
14
- import { getCurrentBranch, getDefaultBranch, getModifiedFiles, isGitRepository, } from "./git.js";
15
- import schema from "./schema.js";
16
- export var ImportStrategy;
17
- (function (ImportStrategy) {
18
- ImportStrategy["OVERWRITE"] = "overwrite";
19
- ImportStrategy["SKIP"] = "skip";
20
- })(ImportStrategy || (ImportStrategy = {}));
21
- export async function uploadTranslations({ domainRoot, apiKey, org, project, locale, input, strategy, format, branch, }) {
22
- const url = `${domainRoot}/api/orgs/${org}/projects/${project}/import`;
23
- const filePath = path.resolve(input);
24
- if (!fs.existsSync(filePath)) {
25
- console.error(`File not found: ${input}`);
26
- process.exit(1);
27
- }
28
- const fileContent = fs.readFileSync(filePath);
29
- const fileName = path.basename(filePath);
30
- const formData = new FormData();
31
- formData.append("file", new Blob([fileContent]), fileName);
32
- formData.append("locale", locale);
33
- formData.append("strategy", strategy);
34
- if (format) {
35
- formData.append("format", format);
36
- }
37
- if (branch) {
38
- formData.append("branch", branch);
39
- }
40
- try {
41
- const response = await fetch(url, {
42
- method: "POST",
43
- headers: {
44
- Authorization: `Bearer ${apiKey}`,
45
- },
46
- body: formData,
47
- });
48
- const data = await response.json();
49
- if (!response.ok) {
50
- console.error(`Failed to import translations: ${response.status} ${response.statusText}\n`, data.error, data.details ? `\nDetails: ${data.details}` : "");
51
- process.exit(1);
52
- }
53
- console.log(`Translations imported for project "${project}" locale "${locale}":`);
54
- console.log(` Total keys: ${data.stats.total}`);
55
- console.log(` Keys created: ${data.stats.keysCreated}`);
56
- console.log(` Translations created: ${data.stats.translationsCreated}`);
57
- console.log(` Translations updated: ${data.stats.translationsUpdated}`);
58
- console.log(` Translations skipped: ${data.stats.translationsSkipped}`);
59
- }
60
- catch (error) {
61
- console.error("Error importing translations:", error);
62
- process.exit(1);
63
- }
64
- }
65
- export async function uploadForConfig(configPath, apiKey, strategy, branch) {
66
- const cwd = process.cwd();
67
- const fullPath = path.resolve(cwd, configPath);
68
- if (!fs.existsSync(fullPath)) {
69
- console.error(`Config file not found: ${configPath}`);
70
- process.exit(1);
71
- }
72
- const config = (await import(__rewriteRelativeImportExtension(pathToFileURL(fullPath).href), { with: { type: "json" } })).default;
73
- const result = schema.safeParse(config);
74
- if (!result.success) {
75
- const pretty = z.prettifyError(result.error);
76
- console.error("Config validation error:", pretty);
77
- process.exit(1);
78
- }
79
- const domainRoot = result.data.domainRoot ?? DEFAULT_DOMAIN_ROOT;
80
- console.log(`Uploading translations to domain "${domainRoot}" for org "${result.data.org}"...`);
81
- // Determine if we can use git to skip unchanged files
82
- let modifiedFiles = null;
83
- if (await isGitRepository()) {
84
- const defaultBranch = await getDefaultBranch();
85
- const currentBranch = await getCurrentBranch();
86
- if (defaultBranch &&
87
- currentBranch &&
88
- currentBranch !== defaultBranch.replace(/^origin\//, "")) {
89
- modifiedFiles = await getModifiedFiles(defaultBranch);
90
- console.log(`Git optimization enabled: only uploading files modified compared to "${defaultBranch}"`);
91
- }
92
- }
93
- for (const configItem of result.data.projects) {
94
- for (const locale of configItem.langs) {
95
- const input = configItem.output
96
- .replace("<lang>", locale)
97
- .replace("<project>", configItem.project)
98
- .replace("<format>", configItem.format);
99
- const resolvedInput = path.resolve(cwd, input);
100
- if (!fs.existsSync(resolvedInput)) {
101
- console.log(`Skipping project "${configItem.project}" locale "${locale}": file not found "${input}"`);
102
- continue;
103
- }
104
- if (modifiedFiles && !modifiedFiles.has(resolvedInput)) {
105
- console.log(`Skipping project "${configItem.project}" locale "${locale}": file not modified`);
106
- continue;
107
- }
108
- await uploadTranslations({
109
- domainRoot,
110
- apiKey,
111
- org: result.data.org,
112
- project: configItem.project,
113
- format: configItem.format,
114
- locale,
115
- input,
116
- strategy,
117
- branch,
118
- });
119
- }
120
- }
121
- }
122
- //# sourceMappingURL=uploadTranslations.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"uploadTranslations.js","sourceRoot":"","sources":["../src/uploadTranslations.ts"],"names":[],"mappings":"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"}