localingos 0.1.33 ā 0.1.35
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/package.json +1 -1
- package/src/api.js +54 -5
- package/src/commands/sync.js +33 -1
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -6,11 +6,30 @@ export class LocalingosApi {
|
|
|
6
6
|
this.apiKey = apiKey;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
async sync(familyId, translatables) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
async sync(familyId, translatables, { batchSize = 100, onProgress } = {}) {
|
|
10
|
+
// Push in batches to avoid API timeout
|
|
11
|
+
const batches = [];
|
|
12
|
+
for (let i = 0; i < translatables.length; i += batchSize) {
|
|
13
|
+
batches.push(translatables.slice(i, i + batchSize));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < batches.length; i++) {
|
|
17
|
+
await this._post('/translatable', {
|
|
18
|
+
familyId,
|
|
19
|
+
translatableList: batches[i]
|
|
20
|
+
});
|
|
21
|
+
if (onProgress) onProgress(Math.min((i + 1) * batchSize, translatables.length), translatables.length);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Pull translations separately
|
|
25
|
+
const translations = await this._get(`/translation/${familyId}`);
|
|
26
|
+
const pushedIds = new Set(translatables.map(t => t.id));
|
|
27
|
+
const available = Array.isArray(translations) ? translations : [];
|
|
28
|
+
const pending = translatables
|
|
29
|
+
.filter(t => !available.some(tr => tr.foreignId === t.id || tr.translatableId === t.id))
|
|
30
|
+
.map(t => t.id);
|
|
31
|
+
|
|
32
|
+
return { translations: available, pending };
|
|
14
33
|
}
|
|
15
34
|
|
|
16
35
|
async getTranslatables(familyId) {
|
|
@@ -28,6 +47,36 @@ export class LocalingosApi {
|
|
|
28
47
|
});
|
|
29
48
|
}
|
|
30
49
|
|
|
50
|
+
async deleteTranslatables(familyId, foreignIds) {
|
|
51
|
+
return this._request('DELETE', `/translatable/${familyId}`, { foreignIds });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async _request(method, path, body) {
|
|
55
|
+
const url = `${this.apiUrl}${path}`;
|
|
56
|
+
let response;
|
|
57
|
+
try {
|
|
58
|
+
const opts = {
|
|
59
|
+
method,
|
|
60
|
+
headers: {
|
|
61
|
+
'X-API-Key': this.apiKey,
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
'Accept': 'application/json'
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
if (body) opts.body = JSON.stringify(body);
|
|
67
|
+
response = await fetch(url, opts);
|
|
68
|
+
} catch (networkError) {
|
|
69
|
+
this._throwConnectionError(method, url, networkError);
|
|
70
|
+
}
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const responseBody = await response.text().catch(() => '');
|
|
73
|
+
throw new Error(`API ${method} ${path} failed (${response.status}): ${responseBody}`);
|
|
74
|
+
}
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
if (!text) return {};
|
|
77
|
+
return JSON.parse(text);
|
|
78
|
+
}
|
|
79
|
+
|
|
31
80
|
async _get(path) {
|
|
32
81
|
const url = `${this.apiUrl}${path}`;
|
|
33
82
|
let response;
|
package/src/commands/sync.js
CHANGED
|
@@ -75,7 +75,12 @@ export async function syncCommand(options) {
|
|
|
75
75
|
|
|
76
76
|
let result;
|
|
77
77
|
try {
|
|
78
|
-
result = await api.sync(config.familyId, translatables
|
|
78
|
+
result = await api.sync(config.familyId, translatables, {
|
|
79
|
+
onProgress: (done, total) => {
|
|
80
|
+
process.stdout.write(`\r Pushed ${done}/${total} strings...`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
process.stdout.write('\n');
|
|
79
84
|
} catch (e) {
|
|
80
85
|
console.error(chalk.red(`\nSync failed: ${e.message}`));
|
|
81
86
|
process.exit(1);
|
|
@@ -88,6 +93,33 @@ export async function syncCommand(options) {
|
|
|
88
93
|
console.log(chalk.yellow(` ā³ ${pending.length} keys pending translation: ${pending.slice(0, 5).join(', ')}${pending.length > 5 ? '...' : ''}`));
|
|
89
94
|
}
|
|
90
95
|
|
|
96
|
+
// 2b. Prune stale translatables from the remote family
|
|
97
|
+
if (options.prune) {
|
|
98
|
+
console.log(chalk.blue('\nš Checking for stale translatables in the remote family...'));
|
|
99
|
+
try {
|
|
100
|
+
const remoteTranslatables = await api.getTranslatables(config.familyId);
|
|
101
|
+
const localIds = new Set(translatables.map(t => t.id));
|
|
102
|
+
const staleIds = remoteTranslatables
|
|
103
|
+
.filter(t => !localIds.has(t.foreignId || t.id))
|
|
104
|
+
.map(t => t.foreignId || t.id);
|
|
105
|
+
|
|
106
|
+
if (staleIds.length > 0) {
|
|
107
|
+
console.log(chalk.yellow(` Found ${staleIds.length} stale translatable(s) not in source file:`));
|
|
108
|
+
for (const id of staleIds.slice(0, 10)) {
|
|
109
|
+
console.log(chalk.gray(` - ${id}`));
|
|
110
|
+
}
|
|
111
|
+
if (staleIds.length > 10) console.log(chalk.gray(` ... and ${staleIds.length - 10} more`));
|
|
112
|
+
|
|
113
|
+
const deleteResult = await api.deleteTranslatables(config.familyId, staleIds);
|
|
114
|
+
console.log(chalk.green(` šļø Deleted ${deleteResult.deleted} stale translatable(s) from remote`));
|
|
115
|
+
} else {
|
|
116
|
+
console.log(chalk.green(' ā
No stale translatables found'));
|
|
117
|
+
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error(chalk.yellow(` ā ļø Could not prune remote translatables: ${e.message}`));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
91
123
|
// 3. Write translation files (skip source locale to avoid overwriting the source file)
|
|
92
124
|
const targetTranslations = translations.filter(t => t.locale !== config.sourceLocale);
|
|
93
125
|
|