@localheroai/cli 0.0.86 → 0.0.88-rc.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.
Files changed (77) hide show
  1. package/dist/api/target-edit-alignments.js +45 -0
  2. package/dist/api/target-edit-alignments.js.map +1 -0
  3. package/dist/commands/translate.js +62 -26
  4. package/dist/commands/translate.js.map +1 -1
  5. package/dist/utils/aligned-import.js +24 -5
  6. package/dist/utils/aligned-import.js.map +1 -1
  7. package/dist/utils/alignment-candidates.js +9 -5
  8. package/dist/utils/alignment-candidates.js.map +1 -1
  9. package/dist/utils/git-changes.js +26 -21
  10. package/dist/utils/git-changes.js.map +1 -1
  11. package/dist/utils/github.js +4 -0
  12. package/dist/utils/github.js.map +1 -1
  13. package/dist/utils/import-guard.js +192 -0
  14. package/dist/utils/import-guard.js.map +1 -0
  15. package/dist/utils/source-alignment.js +7 -5
  16. package/dist/utils/source-alignment.js.map +1 -1
  17. package/dist/utils/target-changes.js.map +1 -1
  18. package/dist/utils/target-edit-alignment.js +238 -0
  19. package/dist/utils/target-edit-alignment.js.map +1 -0
  20. package/dist/utils/target-edit-candidates.js +88 -0
  21. package/dist/utils/target-edit-candidates.js.map +1 -0
  22. package/package.json +3 -2
  23. package/dist/api/auth.d.ts +0 -2
  24. package/dist/api/client.d.ts +0 -3
  25. package/dist/api/imports.d.ts +0 -5
  26. package/dist/api/projects.d.ts +0 -2
  27. package/dist/api/translation-jobs.js +0 -28
  28. package/dist/api/translation-jobs.js.map +0 -1
  29. package/dist/api/translations.d.ts +0 -15
  30. package/dist/cli.d.ts +0 -2
  31. package/dist/commands/_sync.js +0 -22
  32. package/dist/commands/_sync.js.map +0 -1
  33. package/dist/commands/_translate.js +0 -3
  34. package/dist/commands/_translate.js.map +0 -1
  35. package/dist/commands/github-action.js +0 -111
  36. package/dist/commands/github-action.js.map +0 -1
  37. package/dist/commands/init.d.ts +0 -1
  38. package/dist/commands/login.d.ts +0 -16
  39. package/dist/commands/sync.d.ts +0 -20
  40. package/dist/commands/sync.js +0 -22
  41. package/dist/commands/sync.js.map +0 -1
  42. package/dist/commands/translate.d.ts +0 -14
  43. package/dist/document-formats/markdown.js +0 -519
  44. package/dist/document-formats/markdown.js.map +0 -1
  45. package/dist/document-formats/types.js +0 -11
  46. package/dist/document-formats/types.js.map +0 -1
  47. package/dist/index.d.ts +0 -5
  48. package/dist/types/index.d.ts +0 -75
  49. package/dist/types/translate/index.js +0 -2
  50. package/dist/types/translate/index.js.map +0 -1
  51. package/dist/utils/auth.d.ts +0 -2
  52. package/dist/utils/chunked-json-processor.js +0 -52
  53. package/dist/utils/chunked-json-processor.js.map +0 -1
  54. package/dist/utils/common.js +0 -9
  55. package/dist/utils/common.js.map +0 -1
  56. package/dist/utils/config.d.ts +0 -23
  57. package/dist/utils/errors.js +0 -37
  58. package/dist/utils/errors.js.map +0 -1
  59. package/dist/utils/files.d.ts +0 -32
  60. package/dist/utils/git-diff.js +0 -251
  61. package/dist/utils/git-diff.js.map +0 -1
  62. package/dist/utils/git.d.ts +0 -21
  63. package/dist/utils/github.d.ts +0 -241
  64. package/dist/utils/import-service.d.ts +0 -4
  65. package/dist/utils/po-utils-fallback.js +0 -254
  66. package/dist/utils/po-utils-fallback.js.map +0 -1
  67. package/dist/utils/prompt-service.d.ts +0 -44
  68. package/dist/utils/streaming-json-processor.js +0 -125
  69. package/dist/utils/streaming-json-processor.js.map +0 -1
  70. package/dist/utils/sync-service.d.ts +0 -58
  71. package/dist/utils/translation-updater/common.d.ts +0 -6
  72. package/dist/utils/translation-updater/index.d.ts +0 -5
  73. package/dist/utils/translation-updater/json-handler.d.ts +0 -5
  74. package/dist/utils/translation-updater/yaml-handler.d.ts +0 -5
  75. package/dist/utils/translation-utils.d.ts +0 -30
  76. package/dist/utils/updater.js +0 -38
  77. package/dist/utils/updater.js.map +0 -1
@@ -0,0 +1,238 @@
1
+ import { readFileSync } from 'fs';
2
+ import chalk from 'chalk';
3
+ import { createTargetEditAlignment, TargetEditAlignmentRequestError } from '../api/target-edit-alignments.js';
4
+ import { localeCodesMatch } from './translation-processor.js';
5
+ import { chunked, groupBy } from './source-alignment.js';
6
+ const MAX_ITEMS_PER_REQUEST = 25;
7
+ const MAX_KEYS = 250;
8
+ const NOT_FOUND_STATUS = 404;
9
+ const INVALID_PARAMETERS_STATUS = 422;
10
+ const UPDATED = 'updated';
11
+ const LOCAL_FIX = 'local_fix';
12
+ // Nothing to report: no translation was edited, or the developer edited the source text too.
13
+ const QUIET_STATUSES = new Set(['no_trigger', 'source_edited']);
14
+ /**
15
+ * Ask the backend whether a translation edited in this branch changed what the
16
+ * text says, and write the source text it sends back. The other locales then
17
+ * follow that source text through the ordinary source alignment. An older
18
+ * server or a project without the setting answers nothing, and the run goes on
19
+ * as before.
20
+ */
21
+ export async function runTargetEditAlignment(candidates, request, deps) {
22
+ const result = emptyTargetEditResult();
23
+ const updatedKeys = [];
24
+ let enabled = false;
25
+ for (const chunk of chunked(candidates.slice(0, MAX_KEYS), MAX_ITEMS_PER_REQUEST)) {
26
+ try {
27
+ const response = await createTargetEditAlignment({ ...request, items: chunk.map(toRequestItem) });
28
+ if (!response.enabled)
29
+ return result;
30
+ enabled = true;
31
+ matchedItems(chunk, response.items).forEach((item, index) => {
32
+ if (isUpdate(item)) {
33
+ updatedKeys.push({ candidate: chunk[index], item });
34
+ }
35
+ else if (item.status === LOCAL_FIX) {
36
+ result.localFixes += 1;
37
+ }
38
+ else if (!QUIET_STATUSES.has(item.status)) {
39
+ result.skipped[item.status] = (result.skipped[item.status] ?? 0) + 1;
40
+ }
41
+ });
42
+ }
43
+ catch (error) {
44
+ if (error instanceof TargetEditAlignmentRequestError && error.status === NOT_FOUND_STATUS)
45
+ return result;
46
+ logRequestFailure(deps.console, error);
47
+ }
48
+ }
49
+ const leftOut = candidates.length - MAX_KEYS;
50
+ if (enabled && leftOut > 0) {
51
+ deps.console.log(chalk.yellow(`Not following ${leftOut} more ${leftOut === 1 ? 'key' : 'keys'}: more than ${MAX_KEYS} to check.`));
52
+ }
53
+ for (const [sourcePath, fileKeys] of groupBy(updatedKeys, updated => updated.candidate.source_path)) {
54
+ if (!(await writeSourceTexts(sourcePath, fileKeys, deps)))
55
+ continue;
56
+ result.writtenPaths.push(sourcePath);
57
+ for (const updated of fileKeys) {
58
+ result.sourceCells.push(sourceCell(updated, deps.config.sourceLocale));
59
+ result.alignmentCandidates.push(...alignmentCandidates(updated));
60
+ const reverted = revertedChange(updated);
61
+ if (reverted)
62
+ result.revertedChanges.push(reverted);
63
+ }
64
+ }
65
+ return result;
66
+ }
67
+ export function emptyTargetEditResult() {
68
+ return { sourceCells: [], alignmentCandidates: [], revertedChanges: [], writtenPaths: [], localFixes: 0, skipped: {} };
69
+ }
70
+ /**
71
+ * A key whose source text followed an edit is aligned from the source text as
72
+ * it was before the write, for the locales the server named, instead of from
73
+ * the first pass's rewording.
74
+ */
75
+ export function replaceAlignmentCandidates(candidates, targetEdits) {
76
+ const updated = new Set(targetEdits.sourceCells.map(keyId));
77
+ return [
78
+ ...candidates.filter(candidate => !updated.has(keyId(candidate))),
79
+ ...targetEdits.alignmentCandidates
80
+ ];
81
+ }
82
+ export function withRevertedChanges(targetChanges, reverted) {
83
+ const files = targetChanges.map(file => ({ ...file, changes: [...file.changes] }));
84
+ for (const revertedFile of reverted) {
85
+ const file = files.find(f => f.path === revertedFile.path && f.locale === revertedFile.locale);
86
+ if (file) {
87
+ file.changes.push(...revertedFile.changes);
88
+ }
89
+ else {
90
+ files.push(revertedFile);
91
+ }
92
+ }
93
+ return files;
94
+ }
95
+ export function addSourceCells(alignment, sourceCells) {
96
+ if (sourceCells.length === 0)
97
+ return alignment;
98
+ const base = alignment ?? { enabled: true, alignedCells: [], localeSummaries: {}, notices: [], shortUrl: null };
99
+ return { ...base, alignedCells: [...base.alignedCells, ...sourceCells] };
100
+ }
101
+ /**
102
+ * Missing-key detection reads the content cached when the files were found, so
103
+ * a file this run wrote has to be read again first.
104
+ */
105
+ export function refreshFileContent(files, writtenPaths) {
106
+ const contentByPath = new Map();
107
+ for (const file of files) {
108
+ if (file.content === undefined || !writtenPaths.includes(file.path))
109
+ continue;
110
+ if (!contentByPath.has(file.path)) {
111
+ contentByPath.set(file.path, Buffer.from(readFileSync(file.path, 'utf-8')).toString('base64'));
112
+ }
113
+ file.content = contentByPath.get(file.path);
114
+ }
115
+ }
116
+ export function reportTargetEditAlignment(result, logger) {
117
+ const lines = result.sourceCells.map(cell => `${cell.key}: ${cell.locale} updated to match the ${cell.trigger_locale} edit`);
118
+ if (result.localFixes > 0) {
119
+ lines.push(`${result.localFixes} left as they are: the edit kept the meaning`);
120
+ }
121
+ const reasons = Object.entries(result.skipped);
122
+ if (reasons.length > 0) {
123
+ const total = reasons.reduce((sum, [, count]) => sum + count, 0);
124
+ lines.push(`${total} skipped (${reasons.map(([reason, count]) => `${count} ${reason}`).join(', ')})`);
125
+ }
126
+ if (lines.length === 0)
127
+ return;
128
+ logger.log('» Edited translations:');
129
+ for (const line of lines)
130
+ logger.log(` ${line}`);
131
+ }
132
+ function isUpdate(item) {
133
+ return item.status === UPDATED &&
134
+ typeof item.source === 'string' &&
135
+ typeof item.trigger_locale === 'string' &&
136
+ typeof item.trigger_value === 'string' &&
137
+ Array.isArray(item.align_locales);
138
+ }
139
+ async function writeSourceTexts(sourcePath, keys, deps) {
140
+ const values = Object.fromEntries(keys.map(({ candidate, item }) => [candidate.key, item.source]));
141
+ try {
142
+ await deps.updateTranslationFile(sourcePath, values, deps.config.sourceLocale, sourcePath, undefined, deps.config);
143
+ return true;
144
+ }
145
+ catch (error) {
146
+ deps.console.log(chalk.yellow(`⚠ Could not write the updated source texts to ${sourcePath}: ${error.message}`));
147
+ return false;
148
+ }
149
+ }
150
+ function sourceCell({ candidate, item }, sourceLocale) {
151
+ return {
152
+ locale: sourceLocale,
153
+ source_path: candidate.source_path,
154
+ target_path: candidate.source_path,
155
+ format: candidate.format,
156
+ key: candidate.key,
157
+ previous_source: candidate.source,
158
+ source: item.source,
159
+ target_base: candidate.source_base,
160
+ target_current: candidate.source,
161
+ value: item.source,
162
+ trigger_locale: item.trigger_locale,
163
+ trigger_path: findTarget(candidate, item.trigger_locale)?.target_path,
164
+ aligned_source: item.trigger_value
165
+ };
166
+ }
167
+ // The branch value is sent as both base and current: the source alignment reads
168
+ // the cell as untouched and aligns it from where the branch has it.
169
+ function alignmentCandidates({ candidate, item }) {
170
+ return item.align_locales
171
+ .map(locale => findTarget(candidate, locale))
172
+ .filter((target) => target !== undefined && target.writable)
173
+ .map(target => ({
174
+ locale: target.locale,
175
+ source_path: candidate.source_path,
176
+ target_path: target.target_path,
177
+ format: target.format,
178
+ key: candidate.key,
179
+ previous_source: candidate.source,
180
+ source: item.source,
181
+ target_base: target.current,
182
+ target_current: target.current,
183
+ trigger_locale: item.trigger_locale
184
+ }));
185
+ }
186
+ // The edit was undone: the trigger's own proposal is withdrawn rather than
187
+ // staging its merge-base value, which merge could apply over a newer live text.
188
+ function revertedChange({ candidate, item }) {
189
+ if (!item.trigger_at_base)
190
+ return null;
191
+ const trigger = findTarget(candidate, item.trigger_locale);
192
+ if (!trigger)
193
+ return null;
194
+ return {
195
+ path: trigger.target_path,
196
+ source_path: candidate.source_path,
197
+ locale: trigger.locale,
198
+ format: trigger.format,
199
+ changes: [{ key: candidate.key, status: 'reverted', value: trigger.current, old_value: trigger.base }]
200
+ };
201
+ }
202
+ function findTarget(candidate, locale) {
203
+ return candidate.targets.find(target => localeCodesMatch(target.locale, locale));
204
+ }
205
+ function matchedItems(requested, returned) {
206
+ const matches = returned.length === requested.length && returned.every((item, index) => item.key === requested[index].key && item.source_path === requested[index].source_path);
207
+ if (!matches) {
208
+ throw new Error('the response did not match the request');
209
+ }
210
+ return returned;
211
+ }
212
+ function logRequestFailure(logger, error) {
213
+ const message = error.message;
214
+ if (error instanceof TargetEditAlignmentRequestError && error.status === INVALID_PARAMETERS_STATUS) {
215
+ logger.log(chalk.yellow(`⚠ Request to update source texts to match edited translations was rejected: ${message}`));
216
+ return;
217
+ }
218
+ logger.log(chalk.yellow(`⚠ Could not update source texts to match edited translations: ${message}. Source texts were left as they are.`));
219
+ }
220
+ function toRequestItem(candidate) {
221
+ return {
222
+ source_path: candidate.source_path,
223
+ format: candidate.format,
224
+ key: candidate.key,
225
+ source_base: candidate.source_base,
226
+ source: candidate.source,
227
+ targets: candidate.targets.map(target => ({
228
+ locale: target.locale,
229
+ target_path: target.target_path,
230
+ base: target.base,
231
+ current: target.current
232
+ }))
233
+ };
234
+ }
235
+ function keyId(cell) {
236
+ return `${cell.source_path}\0${cell.key}`;
237
+ }
238
+ //# sourceMappingURL=target-edit-alignment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target-edit-alignment.js","sourceRoot":"","sources":["../../src/utils/target-edit-alignment.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { readFileSync } from 'fs';
2
+ import { getBaseBranch, resolveCompareRef, extractLocaleContent } from './git-changes.js';
3
+ import { parseFile } from './files.js';
4
+ import { findTargetFile } from './translation-utils.js';
5
+ import { ALIGNABLE_FORMATS, isFilledString, isRewording, readDiff, uniqueByPath } from './alignment-candidates.js';
6
+ /**
7
+ * Collect the keys whose translation or source text this branch changed, with
8
+ * the source text and every locale's value at the merge-base and now. The
9
+ * backend decides which edit, if any, the source text should follow. Keys the
10
+ * source does not have at the merge-base are left out: an edit needs a before.
11
+ */
12
+ export function collectTargetEditCandidates(targetChanges, sourceFiles, targetFilesByLocale, config, verbose, ignoreMatcher) {
13
+ const editedKeysBySource = editedKeys(targetChanges, ignoreMatcher);
14
+ const editedSourceFiles = uniqueByPath(sourceFiles).filter(file => ALIGNABLE_FORMATS.has(file.format.toLowerCase()) && editedKeysBySource.has(file.path) && writableUnderLocale(file));
15
+ if (editedSourceFiles.length === 0)
16
+ return [];
17
+ const resolvedRef = resolveCompareRef(getBaseBranch(config), verbose);
18
+ if (!resolvedRef)
19
+ return [];
20
+ const candidates = [];
21
+ for (const sourceFile of editedSourceFiles) {
22
+ const sourceDiff = readDiff(sourceFile, resolvedRef, verbose);
23
+ if (!sourceDiff)
24
+ continue;
25
+ const targetDiffs = readTargetDiffs(sourceFile, targetFilesByLocale, config, resolvedRef, verbose);
26
+ for (const key of editedKeysBySource.get(sourceFile.path) ?? []) {
27
+ const sourceBase = sourceDiff.oldFlat[key];
28
+ const source = sourceDiff.newFlat[key];
29
+ if (!isFilledString(sourceBase) || !isFilledString(source))
30
+ continue;
31
+ const targets = targetsWithKey(targetDiffs, key);
32
+ if (targets.length === 0)
33
+ continue;
34
+ candidates.push({ source_path: sourceFile.path, format: sourceFile.format, key, source_base: sourceBase, source, targets });
35
+ }
36
+ }
37
+ return candidates;
38
+ }
39
+ const YAML_FORMATS = new Set(['yml', 'yaml']);
40
+ // The YAML writer always puts the key under a locale root; in a file without
41
+ // one it would add a second tree that hides the existing keys. Such a target
42
+ // still counts as an edited language; it is just never written.
43
+ function writableUnderLocale(file) {
44
+ if (!YAML_FORMATS.has(file.format.toLowerCase()))
45
+ return true;
46
+ try {
47
+ const parsed = parseFile(readFileSync(file.path, 'utf-8'), file.format, file.path);
48
+ return extractLocaleContent(parsed, file.locale) !== parsed;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ function readTargetDiffs(sourceFile, targetFilesByLocale, config, resolvedRef, verbose) {
55
+ const diffs = [];
56
+ for (const locale of config.outputLocales) {
57
+ const file = findTargetFile(targetFilesByLocale[locale] ?? [], locale, sourceFile, config.sourceLocale);
58
+ if (!file)
59
+ continue;
60
+ const diff = readDiff(file, resolvedRef, verbose);
61
+ if (diff)
62
+ diffs.push({ locale, file, diff, writable: writableUnderLocale(file) });
63
+ }
64
+ return diffs;
65
+ }
66
+ function targetsWithKey(targetDiffs, key) {
67
+ return targetDiffs.flatMap(({ locale, file, diff, writable }) => {
68
+ const current = diff.newFlat[key];
69
+ if (!isFilledString(current))
70
+ return [];
71
+ const base = diff.oldFlat[key];
72
+ return [{ locale, target_path: file.path, format: file.format, writable, base: isFilledString(base) ? base : null, current }];
73
+ });
74
+ }
75
+ function editedKeys(targetChanges, ignoreMatcher) {
76
+ const keysBySource = new Map();
77
+ for (const file of targetChanges) {
78
+ for (const change of file.changes) {
79
+ if (!isRewording(change) || ignoreMatcher?.(change.key))
80
+ continue;
81
+ if (!keysBySource.has(file.source_path))
82
+ keysBySource.set(file.source_path, new Set());
83
+ keysBySource.get(file.source_path).add(change.key);
84
+ }
85
+ }
86
+ return keysBySource;
87
+ }
88
+ //# sourceMappingURL=target-edit-candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target-edit-candidates.js","sourceRoot":"","sources":["../../src/utils/target-edit-candidates.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@localheroai/cli",
3
- "version": "0.0.86",
3
+ "version": "0.0.88-rc.1",
4
4
  "description": "CLI tool for managing translations with LocalHero.ai",
5
5
  "homepage": "https://localhero.ai",
6
6
  "repository": {
@@ -16,7 +16,8 @@
16
16
  "build": "tsc && chmod +x dist/cli.js",
17
17
  "start": "node dist/cli.js",
18
18
  "dev": "tsc --watch",
19
- "prepublishOnly": "npm run build",
19
+ "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
20
+ "prepublishOnly": "npm run clean && npm run build",
20
21
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
21
22
  "lint": "eslint 'src/**/*.{js,ts}'",
22
23
  "postinstall": "chmod +x dist/cli.js",
@@ -1,2 +0,0 @@
1
- import { VerifyApiKeyResult } from '../types/index.js';
2
- export declare function verifyApiKey(apiKey: string): Promise<VerifyApiKeyResult>;
@@ -1,3 +0,0 @@
1
- import { ApiRequestOptions } from '../types/index.js';
2
- export declare function getApiHost(): string;
3
- export declare function apiRequest<T = any>(endpoint: string, options?: ApiRequestOptions): Promise<T>;
@@ -1,5 +0,0 @@
1
- export function createImport({ projectId, translations }: {
2
- projectId: any;
3
- translations: any;
4
- }): Promise<any>;
5
- export function checkImportStatus(projectId: any, importId: any): Promise<any>;
@@ -1,2 +0,0 @@
1
- export function listProjects(): Promise<any>;
2
- export function createProject(data: any): Promise<any>;
@@ -1,28 +0,0 @@
1
- import { apiRequest } from './client.js';
2
- /**
3
- * Create translation jobs
4
- * @param params Translation job parameters
5
- * @returns The created translation jobs
6
- */
7
- export async function createTranslationJob(params) {
8
- return await apiRequest(`/api/v1/projects/${params.projectId}/translation_jobs`, {
9
- method: 'POST',
10
- body: JSON.stringify({
11
- target_languages: params.target_languages,
12
- files: params.files,
13
- branch: params.branch
14
- })
15
- });
16
- }
17
- /**
18
- * Get translation job status
19
- * @param projectId Project ID
20
- * @param jobId Job ID
21
- * @returns Translation job details
22
- */
23
- export async function getTranslationJob(projectId, jobId) {
24
- return await apiRequest(`/api/v1/projects/${projectId}/translation_jobs/${jobId}`, {
25
- method: 'GET'
26
- });
27
- }
28
- //# sourceMappingURL=translation-jobs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"translation-jobs.js","sourceRoot":"","sources":["../../src/api/translation-jobs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAqDzC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAkC;IAC3E,OAAO,MAAM,UAAU,CAAC,oBAAoB,MAAM,CAAC,SAAS,mBAAmB,EAAE;QAC/E,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC;KACH,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAiB,EAAE,KAAa;IACtE,OAAO,MAAM,UAAU,CAAC,oBAAoB,SAAS,qBAAqB,KAAK,EAAE,EAAE;QACjF,MAAM,EAAE,KAAK;KACd,CAAC,CAAC;AACL,CAAC"}
@@ -1,15 +0,0 @@
1
- export function createTranslationJob({ sourceFiles, targetLocales, projectId, targetPaths }: {
2
- sourceFiles: any;
3
- targetLocales: any;
4
- projectId: any;
5
- targetPaths: any;
6
- }): Promise<{
7
- jobs: any;
8
- totalJobs: any;
9
- }>;
10
- export function checkJobStatus(jobId: any, includeTranslations?: boolean): Promise<any>;
11
- export function getTranslations(jobId: any): Promise<any>;
12
- export function getUpdates(projectId: any, { since, page }: {
13
- since: any;
14
- page?: number | undefined;
15
- }): Promise<any>;
package/dist/cli.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
@@ -1,22 +0,0 @@
1
- import { syncService as defaultSyncService } from '../utils/sync-service.js';
2
- import chalk from 'chalk';
3
- export async function sync({ verbose = false } = {}, deps = { syncService: defaultSyncService }) {
4
- const { syncService } = deps;
5
- const { hasUpdates, updates } = await syncService.checkForUpdates({ verbose });
6
- if (!hasUpdates) {
7
- console.log(chalk.green('✓ All translations are up to date'));
8
- return;
9
- }
10
- const result = await syncService.applyUpdates(updates, { verbose });
11
- const { totalUpdates = 0, totalDeleted = 0 } = result;
12
- if (!verbose) {
13
- if (totalUpdates > 0) {
14
- console.log(chalk.green(`✓ Updated ${totalUpdates} translations`));
15
- }
16
- if (totalDeleted > 0) {
17
- console.log(chalk.green(`✓ Deleted ${totalDeleted} keys`));
18
- }
19
- }
20
- return result;
21
- }
22
- //# sourceMappingURL=_sync.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_sync.js","sourceRoot":"","sources":["../../src/commands/_sync.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,EAAE,OAAO,GAAG,KAAK,EAAE,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,WAAW,EAAE,kBAAkB,EAAE;IAC7F,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IAE/E,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAC;QAC9D,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAEpE,MAAM,EAAE,YAAY,GAAG,CAAC,EAAE,YAAY,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC;IAEtD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,YAAY,eAAe,CAAC,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,YAAY,OAAO,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,3 +0,0 @@
1
- // This is a compatibility wrapper for the TypeScript implementation
2
- export { translate } from './translate.ts';
3
- //# sourceMappingURL=_translate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_translate.js","sourceRoot":"","sources":["../../src/commands/_translate.js"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
@@ -1,111 +0,0 @@
1
- import chalk from 'chalk';
2
- import { translate } from './translate.js';
3
- import { configService } from '../utils/config.js';
4
- import { checkAuth } from '../utils/auth.js';
5
- import { githubService } from '../utils/github.js';
6
- const defaultDeps = {
7
- console,
8
- configUtils: configService,
9
- authUtils: { checkAuth },
10
- githubUtils: githubService,
11
- env: process.env,
12
- translateCommand: translate
13
- };
14
- /**
15
- * Detects if running in a PR context (feature branch)
16
- * @param env Environment variables
17
- * @returns true if in PR context (should use --changed-only), false otherwise
18
- */
19
- function detectPRContext(env) {
20
- const baseRef = env.GITHUB_BASE_REF;
21
- // If no base ref, assume main context (full translation)
22
- if (!baseRef) {
23
- return false;
24
- }
25
- // If base ref is main or master, do full translation
26
- if (baseRef === 'main' || baseRef === 'master') {
27
- return false;
28
- }
29
- // Otherwise, it's a feature branch PR - use changed-only
30
- return true;
31
- }
32
- /**
33
- * Run translation mode with smart defaults for GitHub Actions
34
- */
35
- async function runTranslateMode(options, deps) {
36
- const { verbose, forceChangedOnly, forceFull } = options;
37
- const { console, env, translateCommand } = deps;
38
- // Determine if we should use --changed-only
39
- let shouldUseChangedOnly;
40
- if (forceFull) {
41
- // User explicitly requested full translation
42
- shouldUseChangedOnly = false;
43
- if (verbose) {
44
- console.log(chalk.blue('ℹ Using full translation mode (--force-full)'));
45
- }
46
- }
47
- else if (forceChangedOnly) {
48
- // User explicitly requested changed-only
49
- shouldUseChangedOnly = true;
50
- if (verbose) {
51
- console.log(chalk.blue('ℹ Using changed-only mode (--force-changed-only)'));
52
- }
53
- }
54
- else {
55
- // Auto-detect based on context
56
- shouldUseChangedOnly = detectPRContext(env);
57
- if (verbose) {
58
- const baseRef = env.GITHUB_BASE_REF || 'unknown';
59
- if (shouldUseChangedOnly) {
60
- console.log(chalk.blue(`ℹ Auto-detected PR context (base: ${baseRef}) - using --changed-only`));
61
- }
62
- else {
63
- console.log(chalk.blue(`ℹ Auto-detected main branch context (base: ${baseRef}) - using full translation`));
64
- }
65
- }
66
- }
67
- // Run translate with computed options
68
- const translateOptions = {
69
- ...options,
70
- changedOnly: shouldUseChangedOnly,
71
- // translate command already handles auto-commit in GitHub Actions
72
- };
73
- await translateCommand(translateOptions);
74
- }
75
- /**
76
- * GitHub Actions command - runs translations optimized for CI/CD
77
- * Auto-detects PR vs main branch context and adjusts behavior accordingly
78
- *
79
- * In Phase 5, this will also support sync mode by checking for sync-trigger-id in config
80
- */
81
- export async function githubAction(options = {}, deps = defaultDeps) {
82
- const { console, configUtils, authUtils, githubUtils } = deps;
83
- // Verify we're running in GitHub Actions
84
- if (!githubUtils.isGitHubAction()) {
85
- console.error(chalk.yellow('\n⚠ Warning: This command is designed to run in GitHub Actions.'));
86
- console.error(chalk.yellow(' For local development, use `npx @localheroai/cli translate` instead.\n'));
87
- // Continue anyway - useful for testing
88
- }
89
- // Check authentication
90
- const isAuthenticated = await authUtils.checkAuth();
91
- if (!isAuthenticated) {
92
- console.error(chalk.red('\n✖ Your API key is invalid. Please check LOCALHERO_API_KEY secret.\n'));
93
- process.exit(1);
94
- return;
95
- }
96
- // Load configuration
97
- const config = await configUtils.getProjectConfig();
98
- if (!config) {
99
- console.error(chalk.red('\n✖ No configuration found. localhero.json is missing.\n'));
100
- process.exit(1);
101
- return;
102
- }
103
- // Phase 5 addition point: Check for sync-trigger-id here
104
- // Example for future implementation:
105
- // if (config['sync-trigger-id']) {
106
- // return runSyncMode(config['sync-trigger-id'], options, deps);
107
- // }
108
- // Phase 1: Run translate mode with smart defaults
109
- await runTranslateMode(options, deps);
110
- }
111
- //# sourceMappingURL=github-action.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"github-action.js","sourceRoot":"","sources":["../../src/commands/github-action.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- export function init(deps?: {}): Promise<void>;
@@ -1,16 +0,0 @@
1
- import { createPromptService } from '../utils/prompt-service.js';
2
- import { verifyApiKey as defaultVerifyApiKey } from '../api/auth.js';
3
- import { configService } from '../utils/config.js';
4
- interface LoginDependencies {
5
- console?: Console;
6
- basePath?: string;
7
- promptService?: ReturnType<typeof createPromptService>;
8
- verifyApiKey?: typeof defaultVerifyApiKey;
9
- gitUtils?: {
10
- updateGitignore: (path: string) => Promise<boolean>;
11
- };
12
- configUtils?: typeof configService;
13
- isCalledFromInit?: boolean;
14
- }
15
- export declare function login(deps?: LoginDependencies): Promise<void>;
16
- export {};
@@ -1,20 +0,0 @@
1
- import { SyncOptions } from '../types/index.js';
2
- interface SyncResult {
3
- totalUpdates?: number;
4
- totalDeleted?: number;
5
- [key: string]: any;
6
- }
7
- import type { CheckUpdatesResult, UpdatesContainer } from '../utils/sync-service.js';
8
- interface SyncServiceInterface {
9
- checkForUpdates: (options: {
10
- verbose?: boolean;
11
- }) => Promise<CheckUpdatesResult>;
12
- applyUpdates: (updates: UpdatesContainer, options: {
13
- verbose?: boolean;
14
- }) => Promise<SyncResult>;
15
- }
16
- interface SyncDependencies {
17
- syncService: SyncServiceInterface;
18
- }
19
- export declare function sync({ verbose }?: SyncOptions, deps?: SyncDependencies): Promise<SyncResult | void>;
20
- export {};