@localheroai/cli 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README +1 -1
  2. package/dist/api/auth.d.ts +2 -0
  3. package/dist/api/auth.js +28 -0
  4. package/dist/api/auth.js.map +1 -0
  5. package/dist/api/client.d.ts +3 -0
  6. package/dist/api/client.js +80 -0
  7. package/dist/api/client.js.map +1 -0
  8. package/dist/api/imports.d.ts +5 -0
  9. package/dist/api/imports.js +43 -0
  10. package/dist/api/imports.js.map +1 -0
  11. package/dist/api/projects.d.ts +2 -0
  12. package/dist/api/projects.js +42 -0
  13. package/dist/api/projects.js.map +1 -0
  14. package/dist/api/translations.d.ts +15 -0
  15. package/dist/api/translations.js +71 -0
  16. package/dist/api/translations.js.map +1 -0
  17. package/dist/cli.d.ts +2 -0
  18. package/dist/cli.js +79 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/commands/_sync.js +22 -0
  21. package/dist/commands/_sync.js.map +1 -0
  22. package/dist/commands/_translate.js +3 -0
  23. package/dist/commands/_translate.js.map +1 -0
  24. package/dist/commands/init.d.ts +1 -0
  25. package/dist/commands/init.js +439 -0
  26. package/dist/commands/init.js.map +1 -0
  27. package/dist/commands/login.d.ts +16 -0
  28. package/dist/commands/login.js +58 -0
  29. package/dist/commands/login.js.map +1 -0
  30. package/dist/commands/pull.js +22 -0
  31. package/dist/commands/pull.js.map +1 -0
  32. package/dist/commands/push.js +56 -0
  33. package/dist/commands/push.js.map +1 -0
  34. package/dist/commands/sync.d.ts +20 -0
  35. package/dist/commands/sync.js +22 -0
  36. package/dist/commands/sync.js.map +1 -0
  37. package/dist/commands/translate.d.ts +14 -0
  38. package/dist/commands/translate.js +145 -0
  39. package/dist/commands/translate.js.map +1 -0
  40. package/dist/index.d.ts +5 -0
  41. package/dist/index.js +8 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/types/index.d.ts +75 -0
  44. package/dist/types/index.js +17 -0
  45. package/dist/types/index.js.map +1 -0
  46. package/dist/types/translate/index.js +2 -0
  47. package/dist/types/translate/index.js.map +1 -0
  48. package/dist/utils/auth.d.ts +2 -0
  49. package/dist/utils/auth.js +29 -0
  50. package/dist/utils/auth.js.map +1 -0
  51. package/dist/utils/common.js +9 -0
  52. package/dist/utils/common.js.map +1 -0
  53. package/dist/utils/config.d.ts +23 -0
  54. package/dist/utils/config.js +137 -0
  55. package/dist/utils/config.js.map +1 -0
  56. package/dist/utils/errors.js +37 -0
  57. package/dist/utils/errors.js.map +1 -0
  58. package/dist/utils/files.d.ts +32 -0
  59. package/dist/utils/files.js +347 -0
  60. package/dist/utils/files.js.map +1 -0
  61. package/dist/utils/git.d.ts +21 -0
  62. package/dist/utils/git.js +87 -0
  63. package/dist/utils/git.js.map +1 -0
  64. package/dist/utils/github.d.ts +241 -0
  65. package/dist/utils/github.js +161 -0
  66. package/dist/utils/github.js.map +1 -0
  67. package/dist/utils/import-service.d.ts +4 -0
  68. package/dist/utils/import-service.js +218 -0
  69. package/dist/utils/import-service.js.map +1 -0
  70. package/dist/utils/prompt-service.d.ts +44 -0
  71. package/dist/utils/prompt-service.js +104 -0
  72. package/dist/utils/prompt-service.js.map +1 -0
  73. package/dist/utils/sync-service.d.ts +58 -0
  74. package/dist/utils/sync-service.js +159 -0
  75. package/dist/utils/sync-service.js.map +1 -0
  76. package/dist/utils/translation-processor.js +197 -0
  77. package/dist/utils/translation-processor.js.map +1 -0
  78. package/dist/utils/translation-updater/common.d.ts +6 -0
  79. package/{src → dist}/utils/translation-updater/common.js +16 -10
  80. package/dist/utils/translation-updater/common.js.map +1 -0
  81. package/dist/utils/translation-updater/index.d.ts +5 -0
  82. package/{src → dist}/utils/translation-updater/index.js +21 -9
  83. package/dist/utils/translation-updater/index.js.map +1 -0
  84. package/dist/utils/translation-updater/json-handler.d.ts +5 -0
  85. package/{src → dist}/utils/translation-updater/json-handler.js +42 -31
  86. package/dist/utils/translation-updater/json-handler.js.map +1 -0
  87. package/dist/utils/translation-updater/yaml-handler.d.ts +5 -0
  88. package/{src → dist}/utils/translation-updater/yaml-handler.js +40 -41
  89. package/dist/utils/translation-updater/yaml-handler.js.map +1 -0
  90. package/dist/utils/translation-utils.d.ts +30 -0
  91. package/dist/utils/translation-utils.js +324 -0
  92. package/dist/utils/translation-utils.js.map +1 -0
  93. package/dist/utils/updater.js +38 -0
  94. package/dist/utils/updater.js.map +1 -0
  95. package/package.json +33 -28
  96. package/src/api/auth.js +0 -24
  97. package/src/api/client.js +0 -83
  98. package/src/api/imports.js +0 -22
  99. package/src/api/projects.js +0 -24
  100. package/src/api/translations.js +0 -58
  101. package/src/cli.js +0 -78
  102. package/src/commands/init.js +0 -485
  103. package/src/commands/login.js +0 -80
  104. package/src/commands/sync.js +0 -28
  105. package/src/commands/translate.js +0 -262
  106. package/src/utils/auth.js +0 -23
  107. package/src/utils/config.js +0 -125
  108. package/src/utils/files.js +0 -361
  109. package/src/utils/git.js +0 -72
  110. package/src/utils/github.js +0 -122
  111. package/src/utils/import-service.js +0 -129
  112. package/src/utils/prompt-service.js +0 -67
  113. package/src/utils/sync-service.js +0 -147
  114. package/src/utils/translation-utils.js +0 -237
@@ -1,361 +0,0 @@
1
- import chalk from 'chalk';
2
- import { glob } from 'glob';
3
- import { readFile } from 'fs/promises';
4
- import path from 'path';
5
- import yaml from 'yaml';
6
- import { promises as fs } from 'fs';
7
-
8
- export function parseFile(content, format) {
9
- try {
10
- if (format === 'json') {
11
- return JSON.parse(content);
12
- }
13
- return yaml.parse(content);
14
- } catch (error) {
15
- throw new Error(`Failed to parse ${format} file: ${error.message}`);
16
- }
17
- }
18
-
19
- export function extractLocaleFromPath(filePath, localeRegex, knownLocales = []) {
20
- if (knownLocales && knownLocales.length > 0) {
21
- const basename = path.basename(filePath, path.extname(filePath));
22
- const foundLocaleInFilename = knownLocales.find(locale =>
23
- locale && basename.toLowerCase() === locale.toLowerCase()
24
- );
25
- if (foundLocaleInFilename) {
26
- return foundLocaleInFilename.toLowerCase();
27
- }
28
-
29
- // Then try to match in the path
30
- const pathParts = filePath.toLowerCase().split(path.sep);
31
- const foundLocaleInPath = knownLocales.find(locale =>
32
- locale && pathParts.includes(locale.toLowerCase())
33
- );
34
- if (foundLocaleInPath) {
35
- return foundLocaleInPath.toLowerCase();
36
- }
37
- }
38
-
39
- const dirName = path.basename(path.dirname(filePath));
40
- if (dirName && isValidLocale(dirName)) {
41
- return dirName.toLowerCase();
42
- }
43
-
44
- if (localeRegex) {
45
- const filename = path.basename(filePath);
46
- const regexPattern = new RegExp(localeRegex);
47
- const regexMatch = filename.match(regexPattern);
48
- if (regexMatch && regexMatch[1]) {
49
- const locale = regexMatch[1].toLowerCase();
50
- if (isValidLocale(locale)) {
51
- return locale;
52
- }
53
- }
54
- }
55
-
56
- throw new Error(`Could not extract locale from path: ${filePath}`);
57
- }
58
-
59
- export function isValidLocale(locale) {
60
- // Basic validation for language code (2 letters) or language-region code (e.g., en-US)
61
- return /^[a-z]{2}(?:-[A-Z]{2})?$/.test(locale);
62
- }
63
-
64
- export function flattenTranslations(obj, parentKey = '') {
65
- const result = {};
66
-
67
- for (const [key, value] of Object.entries(obj)) {
68
- const newKey = parentKey ? `${parentKey}.${key}` : key;
69
-
70
- if (value && typeof value === 'object' && !Array.isArray(value)) {
71
- Object.assign(result, flattenTranslations(value, newKey));
72
- } else {
73
- result[newKey] = value;
74
- }
75
- }
76
-
77
- return result;
78
- }
79
-
80
- function detectJsonFormat(obj) {
81
- let hasNested = false;
82
- let hasDotNotation = false;
83
-
84
- for (const [key, value] of Object.entries(obj)) {
85
- if (key.includes('.')) {
86
- hasDotNotation = true;
87
- }
88
-
89
- if (value && typeof value === 'object' && !Array.isArray(value)) {
90
- hasNested = true;
91
-
92
- for (const [, nestedValue] of Object.entries(value)) {
93
- if (nestedValue && typeof nestedValue === 'object' && !Array.isArray(nestedValue)) {
94
- return 'nested';
95
- }
96
- }
97
- }
98
- }
99
-
100
- if (hasNested && hasDotNotation) {
101
- return 'mixed';
102
- } else if (hasNested) {
103
- return 'nested';
104
- } else if (hasDotNotation) {
105
- return 'flat';
106
- }
107
-
108
- return 'flat';
109
- }
110
-
111
- function unflattenTranslations(flatObj) {
112
- const result = {};
113
-
114
- for (const [key, value] of Object.entries(flatObj)) {
115
- const keys = key.split('.');
116
- let current = result;
117
-
118
- for (let i = 0; i < keys.length - 1; i++) {
119
- const k = keys[i];
120
- current[k] = current[k] || {};
121
- current = current[k];
122
- }
123
-
124
- current[keys[keys.length - 1]] = value;
125
- }
126
-
127
- return result;
128
- }
129
-
130
- function preserveJsonStructure(originalObj, newTranslations, format) {
131
- if (format === 'flat') {
132
- return { ...originalObj, ...newTranslations };
133
- }
134
-
135
- if (format === 'nested') {
136
- const merged = { ...originalObj };
137
- const unflattenedNew = unflattenTranslations(newTranslations);
138
- return deepMerge(merged, unflattenedNew);
139
- }
140
- const result = { ...originalObj };
141
-
142
- for (const [key, value] of Object.entries(newTranslations)) {
143
- if (key.includes('.')) {
144
- const keys = key.split('.');
145
- if (originalObj[key] !== undefined) {
146
- result[key] = value;
147
- continue;
148
- }
149
-
150
- let current = result;
151
-
152
- for (let i = 0; i < keys.length - 1; i++) {
153
- const k = keys[i];
154
- current[k] = current[k] || {};
155
- if (typeof current[k] !== 'object' || Array.isArray(current[k])) {
156
- current[k] = {};
157
- }
158
-
159
- current = current[k];
160
- }
161
-
162
- current[keys[keys.length - 1]] = value;
163
- } else {
164
- result[key] = value;
165
- }
166
- }
167
-
168
- return result;
169
- }
170
-
171
- function deepMerge(target, source) {
172
- const result = { ...target };
173
-
174
- for (const [key, value] of Object.entries(source)) {
175
- if (value && typeof value === 'object' &&
176
- result[key] && typeof result[key] === 'object' &&
177
- !Array.isArray(value) && !Array.isArray(result[key])) {
178
- result[key] = deepMerge(result[key], value);
179
- } else {
180
- result[key] = value;
181
- }
182
- }
183
-
184
- return result;
185
- }
186
-
187
- function extractNamespace(filePath) {
188
- const fileName = path.basename(filePath, path.extname(filePath));
189
- const dirName = path.basename(path.dirname(filePath));
190
-
191
- // Pattern 1: /path/to/en/common.json -> namespace = common
192
- if (/^[a-z]{2}(-[A-Z]{2})?$/.test(dirName)) {
193
- return fileName;
194
- }
195
-
196
- // Pattern 2: /path/to/messages.en.json -> namespace = messages
197
- const dotMatch = fileName.match(/^(.+)\.([a-z]{2}(?:-[A-Z]{2})?)$/);
198
- if (dotMatch) {
199
- return dotMatch[1];
200
- }
201
-
202
- // Pattern 3: /path/to/common-en.json -> namespace = common
203
- const dashMatch = fileName.match(/^(.+)-([a-z]{2}(?:-[A-Z]{2})?)$/);
204
- if (dashMatch) {
205
- return dashMatch[1];
206
- }
207
-
208
- return '';
209
- }
210
-
211
- export async function findTranslationFiles(config, options = {}) {
212
- const {
213
- parseContent = true,
214
- includeContent = true,
215
- extractKeys = true,
216
- basePath = process.cwd(),
217
- sourceLocale = config.sourceLocale,
218
- targetLocales = config.outputLocales || [],
219
- includeNamespace = false,
220
- verbose = false,
221
- returnFullResult = false
222
- } = options;
223
- const knownLocales = [sourceLocale, ...targetLocales];
224
- const { translationFiles } = config;
225
- const {
226
- paths = [],
227
- pattern = '**/*.{json,yml,yaml}',
228
- ignore = [],
229
- localeRegex = '.*?([a-z]{2}(?:-[A-Z]{2})?)\\.(?:yml|yaml|json)$'
230
- } = translationFiles || {};
231
-
232
- const processedFiles = [];
233
-
234
- for (const translationPath of paths) {
235
- const fullPath = path.join(basePath, translationPath);
236
- const globPattern = path.join(fullPath, pattern);
237
-
238
- if (verbose) {
239
- console.log(chalk.blue(`Searching for translation files in ${globPattern}`));
240
- }
241
-
242
- let files;
243
- try {
244
- files = await glob(globPattern, {
245
- ignore: ignore.map(i => path.join(basePath, i)),
246
- absolute: false
247
- });
248
-
249
- if (verbose) {
250
- console.log(chalk.blue(`Found ${files.length} files in ${translationPath}`));
251
- }
252
- } catch (error) {
253
- if (verbose) {
254
- console.error(chalk.red(`Error searching for files in ${translationPath}: ${error.message}`));
255
- }
256
- files = [];
257
- }
258
-
259
- for (const file of files) {
260
- try {
261
- const filePath = file;
262
- const format = path.extname(file).slice(1);
263
- const locale = extractLocaleFromPath(file, localeRegex, knownLocales);
264
-
265
- const result = {
266
- path: filePath,
267
- format,
268
- locale
269
- };
270
-
271
- if (parseContent) {
272
- const content = await readFile(filePath, 'utf8');
273
- const parsedContent = parseFile(content, format);
274
-
275
- if (includeContent) {
276
- result.content = Buffer.from(content).toString('base64');
277
- }
278
-
279
- if (extractKeys) {
280
- const hasLanguageWrapper = parsedContent[locale] !== undefined;
281
- result.hasLanguageWrapper = hasLanguageWrapper;
282
- const translationData = hasLanguageWrapper ? parsedContent[locale] : parsedContent;
283
- result.translations = translationData;
284
- result.keys = flattenTranslations(translationData);
285
- }
286
- }
287
-
288
- if (includeNamespace) {
289
- result.namespace = extractNamespace(filePath);
290
- }
291
-
292
- processedFiles.push(result);
293
- } catch (error) {
294
- if (verbose) {
295
- console.warn(chalk.yellow(`Warning: ${error.message}`));
296
- }
297
- }
298
- }
299
- }
300
-
301
- if (!returnFullResult) {
302
- return processedFiles;
303
- }
304
-
305
- const allFiles = processedFiles;
306
- const sourceFiles = allFiles.filter(file => file.locale === sourceLocale);
307
- const targetFilesByLocale = {};
308
-
309
- for (const locale of targetLocales) {
310
- targetFilesByLocale[locale] = allFiles.filter(file => file.locale === locale);
311
- }
312
-
313
- return {
314
- allFiles,
315
- sourceFiles,
316
- targetFilesByLocale
317
- };
318
- }
319
-
320
- export {
321
- unflattenTranslations,
322
- detectJsonFormat,
323
- preserveJsonStructure,
324
- directoryExists,
325
- findFirstExistingPath,
326
- getDirectoryContents
327
- };
328
-
329
- async function directoryExists(path, fsModule = fs) {
330
- try {
331
- const stats = await fsModule.stat(path);
332
- return stats.isDirectory();
333
- } catch (error) {
334
- if (error.code === 'ENOENT') {
335
- return false;
336
- }
337
- throw error;
338
- }
339
- }
340
-
341
- async function findFirstExistingPath(paths, fsModule = fs) {
342
- for (const path of paths) {
343
- if (await directoryExists(path, fsModule)) {
344
- return path;
345
- }
346
- }
347
- return null;
348
- }
349
-
350
- async function getDirectoryContents(dir, fsModule = fs) {
351
- try {
352
- const files = await fsModule.readdir(dir);
353
- return {
354
- files,
355
- jsonFiles: files.filter(f => f.endsWith('.json')),
356
- yamlFiles: files.filter(f => f.endsWith('.yml') || f.endsWith('.yaml'))
357
- };
358
- } catch {
359
- return null;
360
- }
361
- }
package/src/utils/git.js DELETED
@@ -1,72 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
3
- import { promisify } from 'util';
4
- import { execFile } from 'child_process';
5
-
6
- const execFileAsync = promisify(execFile);
7
-
8
- const defaultDeps = {
9
- fs,
10
- path,
11
- exec: execFileAsync,
12
- };
13
-
14
- export const gitService = {
15
- deps: { ...defaultDeps },
16
-
17
- setDependencies(customDeps = {}) {
18
- this.deps = { ...defaultDeps, ...customDeps };
19
- return this;
20
- },
21
-
22
- async updateGitignore(basePath) {
23
- const { fs, path } = this.deps;
24
- const gitignorePath = path.join(basePath, '.gitignore');
25
- let content = '';
26
-
27
- try {
28
- content = await fs.readFile(gitignorePath, 'utf8');
29
- } catch (error) {
30
- if (error.code !== 'ENOENT') {
31
- return false;
32
- }
33
- }
34
-
35
- if (content.includes('.localhero_key')) {
36
- return false;
37
- }
38
-
39
- try {
40
- await fs.appendFile(gitignorePath, '\n.localhero_key\n');
41
- return true;
42
- } catch (error) {
43
- if (error.code === 'ENOENT') {
44
- try {
45
- await fs.writeFile(gitignorePath, '.localhero_key\n');
46
- return true;
47
- } catch {
48
- return false;
49
- }
50
- }
51
- return false;
52
- }
53
- },
54
-
55
- async getCurrentBranch() {
56
- try {
57
- const { exec } = this.deps;
58
- const { stdout } = await exec('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
59
- return stdout.trim();
60
- } catch {
61
- return null;
62
- }
63
- }
64
- };
65
-
66
- export async function updateGitignore(basePath) {
67
- return gitService.updateGitignore(basePath);
68
- }
69
-
70
- export async function getCurrentBranch() {
71
- return gitService.getCurrentBranch();
72
- }
@@ -1,122 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
-
5
- const defaultDependencies = {
6
- exec: (cmd, options) => execSync(cmd, options),
7
- fs,
8
- path,
9
- env: process.env
10
- };
11
-
12
- export const githubService = {
13
- deps: { ...defaultDependencies },
14
-
15
- // For testing - reset or inject custom dependencies
16
- setDependencies(customDeps = {}) {
17
- this.deps = { ...defaultDependencies, ...customDeps };
18
- return this;
19
- },
20
-
21
- isGitHubAction() {
22
- return this.deps.env.GITHUB_ACTIONS === 'true';
23
- },
24
-
25
- async createGitHubActionFile(basePath, translationPaths) {
26
- const { fs, path } = this.deps;
27
- const workflowDir = path.join(basePath, '.github', 'workflows');
28
- const workflowFile = path.join(workflowDir, 'localhero-translate.yml');
29
-
30
- await fs.mkdir(workflowDir, { recursive: true });
31
-
32
- const actionContent = `name: Localhero.ai - I18n translation
33
-
34
- on:
35
- pull_request:
36
- paths:
37
- ${translationPaths.map(p => `- "${p}"`).join('\n ')}
38
-
39
- jobs:
40
- translate:
41
- runs-on: ubuntu-latest
42
- permissions:
43
- contents: write
44
- pull-requests: write
45
-
46
- steps:
47
- - name: Checkout code
48
- uses: actions/checkout@v4
49
- with:
50
- ref: \${{ github.head_ref }}
51
- fetch-depth: 0
52
-
53
- - name: Set up Node.js
54
- uses: actions/setup-node@v4
55
- with:
56
- node-version: 18
57
-
58
- - name: Run LocalHero CLI
59
- env:
60
- LOCALHERO_API_KEY: \${{ secrets.LOCALHERO_API_KEY }}
61
- GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
62
- run: npx -y @localheroai/cli translate`;
63
-
64
- await fs.writeFile(workflowFile, actionContent);
65
- return workflowFile;
66
- },
67
-
68
- autoCommitChanges(filesPath) {
69
- const { exec, env } = this.deps;
70
-
71
- if (!this.isGitHubAction()) return;
72
-
73
- console.log("Running in GitHub Actions. Committing changes...");
74
- try {
75
- exec('git config --global user.name "LocalHero Bot"', { stdio: "inherit" });
76
- exec('git config --global user.email "hi@localhero.ai"', { stdio: "inherit" });
77
-
78
- const branchName = env.GITHUB_HEAD_REF;
79
- if (!branchName) {
80
- throw new Error('Could not determine branch name from GITHUB_HEAD_REF');
81
- }
82
-
83
- exec(`git add ${filesPath}`, { stdio: "inherit" });
84
-
85
- const status = exec('git status --porcelain').toString();
86
- if (!status) {
87
- console.log("No changes to commit.");
88
- return;
89
- }
90
-
91
- exec('git commit -m "Update translations"', { stdio: "inherit" });
92
-
93
- const token = env.GITHUB_TOKEN;
94
- if (!token) {
95
- throw new Error('GITHUB_TOKEN is not set');
96
- }
97
-
98
- const repository = env.GITHUB_REPOSITORY;
99
- if (!repository) {
100
- throw new Error('GITHUB_REPOSITORY is not set');
101
- }
102
-
103
- const remoteUrl = `https://x-access-token:${token}@github.com/${repository}.git`;
104
-
105
- exec(`git remote set-url origin ${remoteUrl}`, { stdio: "inherit" });
106
- exec(`git push origin HEAD:${branchName}`, { stdio: "inherit" });
107
- console.log("Changes committed and pushed successfully.");
108
- } catch (error) {
109
- console.error("Auto-commit failed:", error.message);
110
- throw error;
111
- }
112
- }
113
- };
114
-
115
- // Only export the functions needed externally
116
- export function createGitHubActionFile(basePath, translationPaths) {
117
- return githubService.createGitHubActionFile(basePath, translationPaths);
118
- }
119
-
120
- export function autoCommitChanges(filesPath) {
121
- return githubService.autoCommitChanges(filesPath);
122
- }
@@ -1,129 +0,0 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
3
- import { createImport, checkImportStatus } from '../api/imports.js';
4
- import { findTranslationFiles, flattenTranslations } from './files.js';
5
-
6
- function getFileFormat(filePath) {
7
- const ext = path.extname(filePath).toLowerCase();
8
- if (ext === '.json') return 'json';
9
- if (ext === '.yml' || ext === '.yaml') return 'yaml';
10
- return null;
11
- }
12
-
13
- async function readFileContent(filePath) {
14
- const content = await fs.readFile(filePath, 'utf8');
15
- const format = getFileFormat(filePath);
16
-
17
- if (format === 'json') {
18
- try {
19
- const jsonContent = JSON.parse(content);
20
- const flattened = flattenTranslations(jsonContent);
21
-
22
- return Buffer.from(JSON.stringify(flattened)).toString('base64');
23
- } catch {
24
- return Buffer.from(content).toString('base64');
25
- }
26
- }
27
-
28
- return Buffer.from(content).toString('base64');
29
- }
30
-
31
- export const importService = {
32
- async findTranslationFiles(config, basePath = process.cwd()) {
33
- const files = await findTranslationFiles(config, {
34
- basePath,
35
- parseContent: false,
36
- includeContent: false,
37
- extractKeys: false,
38
- includeNamespace: true
39
- });
40
-
41
- return files.map(file => ({
42
- path: path.isAbsolute(file.path) ? path.relative(basePath, file.path) : file.path,
43
- language: file.locale,
44
- format: file.format === 'yml' ? 'yaml' : file.format,
45
- namespace: file.namespace || ''
46
- }));
47
- },
48
-
49
- async importTranslations(config, basePath = process.cwd()) {
50
- const files = await this.findTranslationFiles(config, basePath);
51
-
52
- if (!files.length) {
53
- return { status: 'no_files' };
54
- }
55
-
56
- const sourceFiles = files.filter(file => file.language === config.sourceLocale);
57
- const targetFiles = files.filter(file => file.language !== config.sourceLocale);
58
- const importedFiles = {
59
- source: sourceFiles,
60
- target: targetFiles
61
- };
62
-
63
- if (!sourceFiles.length) {
64
- return {
65
- status: 'failed',
66
- error: 'No source language files found. Source language files must be included in the first import.',
67
- files: importedFiles
68
- };
69
- }
70
-
71
- const allTranslations = [];
72
-
73
- for (const file of sourceFiles) {
74
- const fullPath = path.join(basePath, file.path);
75
- allTranslations.push({
76
- language: file.language,
77
- format: file.format === 'yml' ? 'yaml' : file.format,
78
- filename: file.path,
79
- content: await readFileContent(fullPath)
80
- });
81
- }
82
-
83
- for (const file of targetFiles) {
84
- const fullPath = path.join(basePath, file.path);
85
- allTranslations.push({
86
- language: file.language,
87
- format: file.format === 'yml' ? 'yaml' : file.format,
88
- filename: file.path,
89
- content: await readFileContent(fullPath)
90
- });
91
- }
92
-
93
- const importResult = await createImport({
94
- projectId: config.projectId,
95
- translations: allTranslations
96
- });
97
-
98
- if (importResult.status === 'failed') {
99
- return {
100
- ...importResult,
101
- files: importedFiles
102
- };
103
- }
104
-
105
- let finalImportResult = importResult;
106
- while (finalImportResult.status === 'processing') {
107
- await new Promise(resolve => setTimeout(resolve, finalImportResult.poll_interval * 1000));
108
- finalImportResult = await checkImportStatus(config.projectId, finalImportResult.id);
109
-
110
- if (finalImportResult.status === 'failed') {
111
- return {
112
- ...finalImportResult,
113
- files: importedFiles
114
- };
115
- }
116
- }
117
-
118
- // Ensure we pass through all relevant fields from the API response
119
- const { status, statistics, warnings, translations_url, sourceImport } = finalImportResult;
120
- return {
121
- status,
122
- statistics,
123
- warnings,
124
- translations_url,
125
- sourceImport,
126
- files: importedFiles
127
- };
128
- }
129
- };