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