@drone1/alt 0.4.2 → 0.7.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/README.md +63 -62
- package/localization/.localization.cache.json +3100 -908
- package/localization/aa.json +23 -16
- package/localization/af.json +19 -12
- package/localization/agq.json +23 -16
- package/localization/ak.json +23 -16
- package/localization/am.json +23 -16
- package/localization/ar.json +19 -12
- package/localization/as.json +23 -16
- package/localization/asa.json +23 -16
- package/localization/ast.json +16 -9
- package/localization/az.json +17 -10
- package/localization/ba.json +23 -16
- package/localization/bas.json +23 -16
- package/localization/be.json +23 -16
- package/localization/bem.json +23 -16
- package/localization/bez.json +22 -15
- package/localization/bg.json +18 -11
- package/localization/bm.json +17 -10
- package/localization/bn.json +20 -13
- package/localization/bo.json +23 -16
- package/localization/br.json +18 -11
- package/localization/brx.json +23 -16
- package/localization/bs.json +20 -13
- package/localization/byn.json +23 -16
- package/localization/ca.json +16 -9
- package/localization/ccp.json +23 -16
- package/localization/cd-RU.json +16 -9
- package/localization/ceb.json +21 -14
- package/localization/cgg.json +22 -15
- package/localization/chr.json +23 -16
- package/localization/co.json +22 -15
- package/localization/config.json +1 -1
- package/localization/cs.json +18 -11
- package/localization/cu-RU.json +23 -16
- package/localization/da.json +14 -7
- package/localization/de-AT.json +19 -12
- package/localization/de-CH.json +19 -12
- package/localization/de-DE.json +18 -11
- package/localization/dua.json +23 -16
- package/localization/dv.json +23 -16
- package/localization/dz.json +23 -16
- package/localization/ebu.json +23 -16
- package/localization/en.json +9 -2
- package/localization/es-ES.json +17 -10
- package/localization/es-MX.json +18 -11
- package/localization/et.json +20 -13
- package/localization/eu.json +20 -13
- package/localization/fr-CA.json +15 -8
- package/localization/fr-CH.json +15 -8
- package/localization/fr-FR.json +15 -8
- package/localization/gsw.json +20 -13
- package/localization/hi.json +19 -12
- package/localization/hr.json +18 -11
- package/localization/hy.json +21 -14
- package/localization/ja.json +18 -11
- package/localization/km.json +21 -14
- package/localization/ksf.json +23 -16
- package/localization/ku.json +22 -15
- package/localization/kw.json +23 -16
- package/localization/my.json +22 -15
- package/localization/nl.json +18 -11
- package/localization/prs.json +18 -11
- package/localization/reference.js +9 -1
- package/localization/ru.json +14 -7
- package/localization/sq.json +19 -12
- package/localization/swc.json +21 -14
- package/localization/th.json +15 -8
- package/localization/tzm-Latn-.json +23 -16
- package/localization/uk.json +14 -7
- package/localization/vi.json +17 -10
- package/localization/zh-Hans.json +17 -10
- package/localization/zh-Hant.json +18 -11
- package/package.json +4 -3
- package/src/commands/list-models.js +6 -0
- package/src/{translate.js → commands/translate.js} +124 -139
- package/src/{consts.js → lib/consts.js} +12 -0
- package/src/{io.js → lib/io.js} +1 -1
- package/src/{logging.js → lib/logging.js} +3 -3
- package/src/{options.js → lib/options.js} +1 -1
- package/src/lib/reference-loader.js +91 -0
- package/src/{utils.js → lib/utils.js} +15 -0
- package/src/localizer/localize.js +3 -4
- package/src/main.mjs +98 -49
- package/src/providers/anthropic.mjs +38 -2
- package/src/providers/openai.mjs +45 -2
- package/src/shutdown.js +1 -1
- /package/{bin.mjs → alt.mjs} +0 -0
- /package/src/{assert.js → lib/assert.js} +0 -0
- /package/src/{cache.js → lib/cache.js} +0 -0
- /package/src/{config.js → lib/config.js} +0 -0
- /package/src/{context-keys.js → lib/context-keys.js} +0 -0
- /package/src/{logo.js → lib/logo.js} +0 -0
- /package/src/{provider.js → lib/provider.js} +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { loadTranslationProvider } from '../lib/provider.js'
|
|
2
|
+
|
|
3
|
+
export async function runListModels({ appState, options, log }) {
|
|
4
|
+
const { apiKey, api } = await loadTranslationProvider({ __dirname: appState.__dirname, providerName: options.provider, log })
|
|
5
|
+
return log.I(await api.listModels(apiKey))
|
|
6
|
+
}
|
|
@@ -2,23 +2,22 @@ import * as path from 'path'
|
|
|
2
2
|
import axios from 'axios'
|
|
3
3
|
import { fileURLToPath } from 'url'
|
|
4
4
|
import { Listr } from 'listr2'
|
|
5
|
-
import { localize, localizeFormatted } from '
|
|
5
|
+
import { localize, localizeFormatted } from '../localizer/localize.js'
|
|
6
6
|
import {
|
|
7
|
-
DEFAULT_CACHE_FILENAME,
|
|
7
|
+
DEFAULT_CACHE_FILENAME, DEFAULT_LLM_MODELS,
|
|
8
8
|
OVERLOADED_BACKOFF_INTERVAL_MS,
|
|
9
9
|
TRANSLATION_FAILED_RESPONSE_TEXT,
|
|
10
10
|
VALID_TRANSLATION_PROVIDERS
|
|
11
|
-
} from '
|
|
12
|
-
import { assertValidPath } from '
|
|
13
|
-
import {
|
|
14
|
-
import { calculateHash, normalizeData, sleep } from '
|
|
15
|
-
import { formatContextKeyFromKey, isContextKey } from '
|
|
16
|
-
import { loadConfig } from '
|
|
17
|
-
import { loadTranslationProvider } from '
|
|
18
|
-
import { loadCache } from '
|
|
19
|
-
import { shutdown } from '
|
|
20
|
-
|
|
21
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
} from '../lib/consts.js'
|
|
12
|
+
import { assertValidPath } from '../lib/assert.js'
|
|
13
|
+
import { mkTmpDir, normalizeOutputPath, readFileAsText, readJsonFile, writeJsonFile } from '../lib/io.js'
|
|
14
|
+
import { calculateHash, normalizeData, sleep } from '../lib/utils.js'
|
|
15
|
+
import { formatContextKeyFromKey, isContextKey } from '../lib/context-keys.js'
|
|
16
|
+
import { loadConfig } from '../lib/config.js'
|
|
17
|
+
import { loadTranslationProvider } from '../lib/provider.js'
|
|
18
|
+
import { loadCache } from '../lib/cache.js'
|
|
19
|
+
import { shutdown } from '../shutdown.js'
|
|
20
|
+
import { loadReferenceFile } from '../lib/reference-loader.js'
|
|
22
21
|
|
|
23
22
|
export async function runTranslation({ appState, options, log }) {
|
|
24
23
|
let exitCode = 0
|
|
@@ -70,15 +69,9 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
70
69
|
appState.tmpDir = tmpDir
|
|
71
70
|
|
|
72
71
|
// Copy to a temp location first so we can ensure it has an .mjs extension
|
|
73
|
-
const
|
|
74
|
-
filePath: options.referenceFile,
|
|
75
|
-
tmpDir,
|
|
76
|
-
ext: 'mjs',
|
|
77
|
-
})
|
|
78
|
-
const referenceContent = normalizeData(JSON.parse(JSON.stringify(await importJsFile(tmpReferencePath))), log) // TODO: Don't do this
|
|
79
|
-
const referenceData = referenceContent[options.referenceVarName]
|
|
72
|
+
const referenceData = await loadReferenceFile({ appLang: appState.lang, options, tmpDir, log })
|
|
80
73
|
if (!referenceData) {
|
|
81
|
-
log.E(`No reference data found in variable "${options.
|
|
74
|
+
log.E(`No reference data found in variable "${options.referenceExportedVarName}" in ${options.referenceFile}`)
|
|
82
75
|
process.exit(2)
|
|
83
76
|
}
|
|
84
77
|
|
|
@@ -98,7 +91,7 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
98
91
|
assertValidPath(cacheFilePath)
|
|
99
92
|
appState.filesToWrite[cacheFilePath] = writableCache
|
|
100
93
|
|
|
101
|
-
const { apiKey, api: translationProvider } = await loadTranslationProvider({ __dirname, providerName, log })
|
|
94
|
+
const { apiKey, api: translationProvider } = await loadTranslationProvider({ __dirname: appState.__dirname, providerName, log })
|
|
102
95
|
log.V(`translation provider "${providerName}" loaded`)
|
|
103
96
|
|
|
104
97
|
const addContextToTranslation = options.lookForContextData || config.lookForContextData
|
|
@@ -153,7 +146,7 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
153
146
|
suffix: contextSuffix
|
|
154
147
|
})
|
|
155
148
|
log.T(`contextKey=${contextKey}`)
|
|
156
|
-
const storedHashForReferenceValue = readOnlyCache?.referenceKeyHashes?.[key]
|
|
149
|
+
const storedHashForReferenceValue = readOnlyCache?.referenceKeyHashes?.[targetLang]?.[key] // See https://github.com/drone1/alt/issues/1
|
|
157
150
|
const storedHashForTargetLangAndValue = readOnlyCache.state[targetLang]?.keyHashes?.[key]
|
|
158
151
|
const refValue = referenceData[key]
|
|
159
152
|
const refContextValue = (contextKey in referenceData) ? referenceData[contextKey] : null
|
|
@@ -161,8 +154,28 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
161
154
|
const curValue = (key in outputData) ? outputData[key] : null
|
|
162
155
|
|
|
163
156
|
// Skip non-string values (objects, arrays, etc.)
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
const refValueType = typeof refValue
|
|
158
|
+
if (refValueType !== 'string') {
|
|
159
|
+
if (refValueType === 'undefined') {
|
|
160
|
+
// This can happen if a user specifies a key explicitly via --keys
|
|
161
|
+
errors.push(
|
|
162
|
+
localizeFormatted({
|
|
163
|
+
token: 'error-value-not-in-reference-data',
|
|
164
|
+
data: { key },
|
|
165
|
+
lang: appState.lang,
|
|
166
|
+
log
|
|
167
|
+
})
|
|
168
|
+
)
|
|
169
|
+
} else {
|
|
170
|
+
errors.push(
|
|
171
|
+
localizeFormatted({
|
|
172
|
+
token: 'error-value-not-a-string',
|
|
173
|
+
data: { key, type: refValueType },
|
|
174
|
+
lang: appState.lang,
|
|
175
|
+
log
|
|
176
|
+
})
|
|
177
|
+
)
|
|
178
|
+
}
|
|
166
179
|
continue
|
|
167
180
|
}
|
|
168
181
|
|
|
@@ -249,7 +262,6 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
249
262
|
}
|
|
250
263
|
}
|
|
251
264
|
|
|
252
|
-
let nextTaskDelayMs = 0
|
|
253
265
|
let totalTasks = workQueue.length
|
|
254
266
|
let errorsEncountered = 0
|
|
255
267
|
for (const taskInfoIdx in workQueue) {
|
|
@@ -266,18 +278,14 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
266
278
|
log
|
|
267
279
|
}),
|
|
268
280
|
task: async (ctx, task) => {
|
|
269
|
-
ctx.nextTaskDelayMs = nextTaskDelayMs
|
|
270
|
-
|
|
271
281
|
return task.newListr([
|
|
272
282
|
{
|
|
273
283
|
title: localize({ token: 'msg-translating', lang: appState.lang, log }),
|
|
274
284
|
task: async (_, task) => {
|
|
275
285
|
const translationResult = await processTranslationTask({
|
|
276
|
-
appState, taskInfo, listrTask: task,
|
|
286
|
+
appState, taskInfo, listrTask: task, options, log
|
|
277
287
|
})
|
|
278
288
|
|
|
279
|
-
nextTaskDelayMs = translationResult.nextTaskDelayMs
|
|
280
|
-
|
|
281
289
|
if (translationResult.error) {
|
|
282
290
|
++errorsEncountered
|
|
283
291
|
throw new Error(translationResult.error)
|
|
@@ -321,7 +329,7 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
321
329
|
log.I(`\x1B[38;2;44;190;78m✔\x1B[0m ${localize({ token: 'msg-nothing-to-do', lang: appState.lang, log })}`)
|
|
322
330
|
}
|
|
323
331
|
} catch (error) {
|
|
324
|
-
log.E(
|
|
332
|
+
log.E(error)
|
|
325
333
|
exitCode = 2
|
|
326
334
|
}
|
|
327
335
|
|
|
@@ -332,50 +340,27 @@ export async function runTranslation({ appState, options, log }) {
|
|
|
332
340
|
}
|
|
333
341
|
}
|
|
334
342
|
|
|
335
|
-
export async function processTranslationTask({ appState, taskInfo, listrTask,
|
|
343
|
+
export async function processTranslationTask({ appState, taskInfo, listrTask, options, log }) {
|
|
336
344
|
const { key, sourceLang, targetLang, reasonsForTranslationMap, outputData, outputFilePath, writableCache, cacheFilePath, state } = taskInfo
|
|
337
|
-
const {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
appContextMessage,
|
|
341
|
-
storedHashForReferenceValue,
|
|
342
|
-
refValue,
|
|
343
|
-
refContextValue,
|
|
344
|
-
referenceValueHash,
|
|
345
|
-
userMissingReferenceValueHash,
|
|
346
|
-
userModifiedReferenceValue,
|
|
347
|
-
curValue,
|
|
348
|
-
currentValueHash,
|
|
349
|
-
storedHashForTargetLangAndValue
|
|
350
|
-
} = state
|
|
351
|
-
|
|
352
|
-
let reasons = Object.keys(reasonsForTranslationMap)
|
|
345
|
+
const { referenceValueHash } = state
|
|
346
|
+
|
|
347
|
+
listrTask.output = Object.keys(reasonsForTranslationMap)
|
|
353
348
|
.map(k => localize({ token: `msg-translation-reason-${k}`, lang: appState.lang, log }))
|
|
354
349
|
.join(', ')
|
|
355
|
-
listrTask.output = reasons
|
|
356
350
|
|
|
357
351
|
const {
|
|
358
352
|
success,
|
|
359
353
|
translated,
|
|
360
|
-
nextTaskDelayMs,
|
|
361
354
|
newValue,
|
|
362
355
|
error
|
|
363
356
|
} = await translateKeyForLanguage({
|
|
364
357
|
appState,
|
|
365
358
|
listrTask,
|
|
366
|
-
ctx: listrCtx,
|
|
367
|
-
translationProvider,
|
|
368
|
-
apiKey,
|
|
369
|
-
referenceValueHash,
|
|
370
|
-
storedHashForReferenceValue,
|
|
371
|
-
storedHashForTargetLangAndValue,
|
|
372
359
|
sourceLang,
|
|
373
360
|
targetLang,
|
|
374
361
|
key,
|
|
375
|
-
refValue,
|
|
376
|
-
refContextValue,
|
|
377
|
-
curValue,
|
|
378
362
|
options,
|
|
363
|
+
state,
|
|
379
364
|
log
|
|
380
365
|
})
|
|
381
366
|
|
|
@@ -398,7 +383,8 @@ export async function processTranslationTask({ appState, taskInfo, listrTask, li
|
|
|
398
383
|
listrTask.output = localizeFormatted({ token: 'msg-show-translation-result', data: { key, newValue }, lang: appState.lang, log })
|
|
399
384
|
|
|
400
385
|
// Update the hash for the reference key, so we can monitor if the user changed a specific key
|
|
401
|
-
writableCache.referenceKeyHashes[
|
|
386
|
+
writableCache.referenceKeyHashes[targetLang] = writableCache.referenceKeyHashes[targetLang] || {}
|
|
387
|
+
writableCache.referenceKeyHashes[targetLang][key] = referenceValueHash
|
|
402
388
|
|
|
403
389
|
// Update state file every time, in case the user kills the process
|
|
404
390
|
if (options.realtimeWrites) {
|
|
@@ -420,57 +406,43 @@ export async function processTranslationTask({ appState, taskInfo, listrTask, li
|
|
|
420
406
|
appState.filesToWrite[outputFilePath] = outputData
|
|
421
407
|
}
|
|
422
408
|
|
|
423
|
-
return {
|
|
409
|
+
return { error }
|
|
424
410
|
}
|
|
425
411
|
|
|
426
412
|
async function translateKeyForLanguage({
|
|
427
413
|
appState,
|
|
428
414
|
listrTask,
|
|
429
|
-
ctx,
|
|
430
|
-
translationProvider,
|
|
431
|
-
apiKey,
|
|
432
|
-
appContextMessage,
|
|
433
415
|
sourceLang,
|
|
434
416
|
targetLang,
|
|
417
|
+
state,
|
|
435
418
|
key,
|
|
436
|
-
|
|
437
|
-
refContextValue,
|
|
438
|
-
options: { maxRetries },
|
|
419
|
+
options: { maxRetries, model },
|
|
439
420
|
log
|
|
440
421
|
}) {
|
|
441
|
-
const
|
|
422
|
+
const { translationProvider, apiKey, appContextMessage, refValue, refContextValue } = state
|
|
423
|
+
const result = { success: false, translated: false, newValue: null, error: null }
|
|
424
|
+
|
|
425
|
+
const providerName = translationProvider.name().toLowerCase()
|
|
426
|
+
model = model ?? DEFAULT_LLM_MODELS[providerName]
|
|
427
|
+
if (!model?.length) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
localizeFormatted({ token: 'error-invalid-llm-model', data: { model }, lang: appState.lang, log })
|
|
430
|
+
)
|
|
431
|
+
}
|
|
442
432
|
|
|
443
433
|
// Call translation provider
|
|
444
434
|
log.D(`[${targetLang}] Translating "${key}"...`)
|
|
445
435
|
listrTask.output = localizeFormatted({ token: 'msg-translating-key', data: { key }, lang: appState.lang, log })
|
|
446
436
|
|
|
447
|
-
const providerName = translationProvider.name()
|
|
448
|
-
let translated = null
|
|
449
437
|
let newValue
|
|
450
438
|
|
|
451
|
-
|
|
452
|
-
// A single key may need to retry many times, since the algorithm is quite simple: if a task is told to retry after 10s,
|
|
453
|
-
// any subsequent tasks that run will delay 10s also, then those concurrent remaining tasks will all hammer at once, some
|
|
454
|
-
// will complete (maybe), then we'll wait again, then hammer again. A more proper solution may or may not be forthcoming...
|
|
455
|
-
for (let attempt = 0; !newValue && attempt <= maxRetries; ++attempt) {
|
|
439
|
+
for (let attempt = 0; !newValue?.length && attempt <= maxRetries; ++attempt) {
|
|
456
440
|
const attemptStr = attempt > 0 ? ` [Attempt: ${attempt + 1}]` : ''
|
|
457
441
|
log.D(`[translate] attempt=${attempt}`)
|
|
458
442
|
|
|
459
|
-
log.D('next task delay', ctx.nextTaskDelayMs)
|
|
460
|
-
if (ctx.nextTaskDelayMs > 0) {
|
|
461
|
-
const msg = localizeFormatted({
|
|
462
|
-
token: 'msg-rate-limited-sleeping',
|
|
463
|
-
data: { interval: Math.floor(ctx.nextTaskDelayMs / 1000), attemptStr }, lang: appState.lang, log
|
|
464
|
-
})
|
|
465
|
-
listrTask.output = msg
|
|
466
|
-
log.D(msg)
|
|
467
|
-
await sleep(ctx.nextTaskDelayMs)
|
|
468
|
-
}
|
|
469
|
-
|
|
470
443
|
const translateResult = await translate({
|
|
471
444
|
appState,
|
|
472
445
|
listrTask,
|
|
473
|
-
ctx,
|
|
474
446
|
provider: translationProvider,
|
|
475
447
|
text: refValue,
|
|
476
448
|
context: refContextValue,
|
|
@@ -478,16 +450,23 @@ async function translateKeyForLanguage({
|
|
|
478
450
|
targetLang,
|
|
479
451
|
appContextMessage,
|
|
480
452
|
apiKey,
|
|
453
|
+
model,
|
|
481
454
|
maxRetries: maxRetries,
|
|
482
455
|
attemptStr,
|
|
483
456
|
log
|
|
484
457
|
})
|
|
485
458
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
459
|
+
const { backoffInterval } = translateResult
|
|
460
|
+
if (backoffInterval > 0) {
|
|
461
|
+
log.D(`backing off... interval: ${backoffInterval}`)
|
|
462
|
+
|
|
463
|
+
if (backoffInterval > 0) {
|
|
464
|
+
listrTask.output = localizeFormatted({
|
|
465
|
+
token: 'msg-rate-limited-sleeping',
|
|
466
|
+
data: { interval: Math.floor(backoffInterval / 1000), attemptStr }, lang: appState.lang, log
|
|
467
|
+
})
|
|
468
|
+
await sleep(backoffInterval)
|
|
469
|
+
}
|
|
491
470
|
} else {
|
|
492
471
|
newValue = translateResult.translated
|
|
493
472
|
result.success = true
|
|
@@ -499,9 +478,40 @@ async function translateKeyForLanguage({
|
|
|
499
478
|
result.translated = true
|
|
500
479
|
result.newValue = newValue
|
|
501
480
|
} else {
|
|
502
|
-
result.error =
|
|
481
|
+
result.error = localizeFormatted({ token: 'error-translation-failed', data: { targetLang, key, refValue }, lang: appState.lang, log })
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return result
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function translate({
|
|
488
|
+
appState,
|
|
489
|
+
listrTask,
|
|
490
|
+
provider,
|
|
491
|
+
appContextMessage,
|
|
492
|
+
text,
|
|
493
|
+
context,
|
|
494
|
+
sourceLang,
|
|
495
|
+
targetLang,
|
|
496
|
+
apiKey,
|
|
497
|
+
model,
|
|
498
|
+
attemptStr,
|
|
499
|
+
log
|
|
500
|
+
}) {
|
|
501
|
+
log.D(`[translate] sourceLang=${sourceLang}; targetLang=${targetLang}; text=${text}`)
|
|
502
|
+
const result = { translated: null, backoffInterval: 0 }
|
|
503
|
+
|
|
504
|
+
if (sourceLang === targetLang) {
|
|
505
|
+
log.D(`Using reference value since source & target language are the same`)
|
|
506
|
+
result.translated = text
|
|
507
|
+
} else {
|
|
508
|
+
await translateTextViaProvider({
|
|
509
|
+
appState, provider, listrTask, sourceLang, targetLang, appContextMessage, context, text, log, apiKey, model, attemptStr, providerName: provider.name(), outResult: result
|
|
510
|
+
})
|
|
503
511
|
}
|
|
504
512
|
|
|
513
|
+
log.D(`[translate] `, result)
|
|
514
|
+
|
|
505
515
|
return result
|
|
506
516
|
}
|
|
507
517
|
|
|
@@ -516,9 +526,10 @@ async function translateTextViaProvider({
|
|
|
516
526
|
text,
|
|
517
527
|
log,
|
|
518
528
|
apiKey,
|
|
529
|
+
model,
|
|
519
530
|
attemptStr,
|
|
520
|
-
|
|
521
|
-
|
|
531
|
+
providerName,
|
|
532
|
+
outResult
|
|
522
533
|
}) {
|
|
523
534
|
try {
|
|
524
535
|
const providerName = provider.name()
|
|
@@ -541,7 +552,7 @@ async function translateTextViaProvider({
|
|
|
541
552
|
+ `\n\n${text}`
|
|
542
553
|
)
|
|
543
554
|
log.D(`prompt: `, messages)
|
|
544
|
-
const { url, params, config } = provider.getTranslationRequestDetails({ messages, apiKey, log })
|
|
555
|
+
const { url, params, config } = provider.getTranslationRequestDetails({ model, messages, apiKey, log })
|
|
545
556
|
log.T('url: ', url, 'params: ', params, 'config: ', config)
|
|
546
557
|
listrTask.output = localizeFormatted({ token: 'msg-hitting-provider-endpoint', data: { providerName, attemptStr }, lang: appState.lang, log })
|
|
547
558
|
const response = await axios.post(url, params, config)
|
|
@@ -550,53 +561,27 @@ async function translateTextViaProvider({
|
|
|
550
561
|
if (!translated?.length) throw new Error(`${providerName} translated text to empty string. You may need to top up your credits.`)
|
|
551
562
|
log.D(`${translated}`)
|
|
552
563
|
if (translated === TRANSLATION_FAILED_RESPONSE_TEXT) throw new Error(`${providerName} failed to translate string to ${targetLang}; string: ${text}`)
|
|
553
|
-
|
|
564
|
+
outResult.translated = translated
|
|
554
565
|
} catch (error) {
|
|
566
|
+
let errorHandled = false
|
|
567
|
+
|
|
555
568
|
const response = error?.response
|
|
556
569
|
if (response) {
|
|
557
570
|
if (response.status === 429) {
|
|
558
|
-
|
|
559
|
-
log.D(`Rate limited; retrying in ${
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
571
|
+
outResult.backoffInterval = provider.getSleepInterval(response.headers, log)
|
|
572
|
+
log.D(`Rate limited; retrying in ${outResult.backoffInterval}`)
|
|
573
|
+
errorHandled = true
|
|
574
|
+
} else if (response.status === 529) { // Unofficial 'overloaded' code
|
|
575
|
+
outResult.backoffInterval = OVERLOADED_BACKOFF_INTERVAL_MS
|
|
576
|
+
log.D(`Overloaded; retrying in ${outResult.backoffInterval}`)
|
|
577
|
+
listrTask.output = `${providerName} overloaded; retrying in ${outResult.backoffInterval / 1000}s `
|
|
578
|
+
errorHandled = true
|
|
564
579
|
}
|
|
565
|
-
} else {
|
|
566
|
-
log.W(`API failed. Error:`, error.message)
|
|
567
580
|
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
async function translate({
|
|
572
|
-
appState,
|
|
573
|
-
listrTask,
|
|
574
|
-
ctx,
|
|
575
|
-
provider,
|
|
576
|
-
appContextMessage,
|
|
577
|
-
text,
|
|
578
|
-
context,
|
|
579
|
-
sourceLang,
|
|
580
|
-
targetLang,
|
|
581
|
-
apiKey,
|
|
582
|
-
attemptStr,
|
|
583
|
-
log
|
|
584
|
-
}) {
|
|
585
|
-
log.D(`[translate] sourceLang=${sourceLang}; targetLang=${targetLang}; text=${text}`)
|
|
586
|
-
const result = { translated: null, backoffInterval: 0 }
|
|
587
|
-
|
|
588
|
-
const providerName = provider.name()
|
|
589
581
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
} else {
|
|
594
|
-
await translateTextViaProvider({
|
|
595
|
-
appState, provider, listrTask, sourceLang, targetLang, appContextMessage, context, text, log, apiKey, attemptStr, result, providerName
|
|
596
|
-
})
|
|
582
|
+
if (!errorHandled) {
|
|
583
|
+
log.W(`${providerName} API failed. Error:`, error?.message ?? error)
|
|
584
|
+
}
|
|
597
585
|
}
|
|
598
|
-
|
|
599
|
-
log.D(`[translate] `, result)
|
|
600
|
-
|
|
601
|
-
return result
|
|
602
586
|
}
|
|
587
|
+
|
|
@@ -21,3 +21,15 @@ export const DEFAULT_CONFIG_FILENAME = 'config.json'
|
|
|
21
21
|
export const OVERLOADED_BACKOFF_INTERVAL_MS = 30 * 1000
|
|
22
22
|
export const CWD = process.cwd()
|
|
23
23
|
|
|
24
|
+
export const SUPPORTED_REFERENCE_FILE_EXTENSIONS = [
|
|
25
|
+
'js',
|
|
26
|
+
'mjs',
|
|
27
|
+
'json',
|
|
28
|
+
'jsonc'
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_LLM_MODELS = {
|
|
32
|
+
anthropic: 'claude-3-7-sonnet-20250219',
|
|
33
|
+
openai: 'gpt-4-turbo'
|
|
34
|
+
}
|
|
35
|
+
|
package/src/{io.js → lib/io.js}
RENAMED
|
@@ -33,7 +33,7 @@ export async function readFileAsText(filePath) {
|
|
|
33
33
|
|
|
34
34
|
export async function readJsonFile(filePath, isJSONComments = false) {
|
|
35
35
|
let content = await readFileAsText(filePath)
|
|
36
|
-
if (isJSONComments) content = stripJsonComments
|
|
36
|
+
if (isJSONComments) content = stripJsonComments(content)
|
|
37
37
|
return parseJson(content)
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export function createLog() {
|
|
1
|
+
export function createLog(isDevMode) {
|
|
2
2
|
return {
|
|
3
3
|
// T/D/V blackholed until program options are parsed
|
|
4
4
|
T: () => { },
|
|
5
5
|
D: () => { },
|
|
6
6
|
V: () => { },
|
|
7
7
|
|
|
8
|
-
E: function(
|
|
9
|
-
console.error(
|
|
8
|
+
E: function(args) {
|
|
9
|
+
console.error(isDevMode ? args : (args?.message ?? args))
|
|
10
10
|
},
|
|
11
11
|
W: function(...args) {
|
|
12
12
|
console.warn(...args)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { copyFileToTempAndEnsureExtension, importJsFile, readJsonFile } from './io.js'
|
|
2
|
+
import { getFileExtension } from './utils.js'
|
|
3
|
+
import { SUPPORTED_REFERENCE_FILE_EXTENSIONS } from './consts.js'
|
|
4
|
+
import { localizeFormatted } from '../localizer/localize.js'
|
|
5
|
+
|
|
6
|
+
export async function loadReferenceFile({ appLang, options: { referenceFile, referenceExportedVarName }, tmpDir, log }) {
|
|
7
|
+
const ext = getFileExtension(referenceFile)?.toLowerCase()
|
|
8
|
+
if (!SUPPORTED_REFERENCE_FILE_EXTENSIONS.includes(ext)) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
localizeFormatted({
|
|
11
|
+
token: 'error-bad-reference-file-ext',
|
|
12
|
+
data: { ext },
|
|
13
|
+
lang: appLang,
|
|
14
|
+
log
|
|
15
|
+
})
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let content
|
|
20
|
+
let useRefVar
|
|
21
|
+
switch(ext) {
|
|
22
|
+
case 'js': {
|
|
23
|
+
log.D(`Reading JS file "${referenceFile}"...`)
|
|
24
|
+
|
|
25
|
+
// For .js, we need to copy to a temp location as an .mjs so we can dynamically import
|
|
26
|
+
const tmpReferencePath = await copyFileToTempAndEnsureExtension({
|
|
27
|
+
filePath: referenceFile,
|
|
28
|
+
tmpDir,
|
|
29
|
+
ext: 'mjs',
|
|
30
|
+
})
|
|
31
|
+
//const referenceContent = normalizeData(await importJsFile(tmpReferencePath), log)
|
|
32
|
+
content = await importJsFile(tmpReferencePath)
|
|
33
|
+
useRefVar = true
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case 'mjs': {
|
|
38
|
+
// We can dynamically import an .mjs directly from its actual path
|
|
39
|
+
log.D(`Reading MJS file "${referenceFile}"...`)
|
|
40
|
+
content = await importJsFile(referenceFile)
|
|
41
|
+
useRefVar = true
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case 'json': {
|
|
46
|
+
log.D(`Reading JSON file "${referenceFile}"...`)
|
|
47
|
+
content = await readJsonFile(referenceFile, false)
|
|
48
|
+
useRefVar = false
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case 'jsonc': {
|
|
53
|
+
log.D(`Reading JSONC file "${referenceFile}"...`)
|
|
54
|
+
content = await readJsonFile(referenceFile, true)
|
|
55
|
+
useRefVar = false
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!content) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
localizeFormatted({
|
|
63
|
+
token: 'error-reference-file-load-failed',
|
|
64
|
+
data: { referenceFile },
|
|
65
|
+
lang: appLang,
|
|
66
|
+
log
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let result
|
|
72
|
+
if (useRefVar && referenceExportedVarName?.length) {
|
|
73
|
+
log.D(`[loadReferenceFile] useRefVar: ${useRefVar}`)
|
|
74
|
+
if (!(referenceExportedVarName in content)) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
localizeFormatted({
|
|
77
|
+
token: 'error-reference-var-not-found-in-data',
|
|
78
|
+
data: { referenceExportedVarName, referenceFile, possibleKeys: Object.keys(content) },
|
|
79
|
+
lang: appLang,
|
|
80
|
+
log
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
result = content[referenceExportedVarName]
|
|
86
|
+
} else {
|
|
87
|
+
result = content
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result
|
|
91
|
+
}
|
|
@@ -106,3 +106,18 @@ export function normalizeData(data) {
|
|
|
106
106
|
}
|
|
107
107
|
return normalizedData
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
export function pick(o, ...props) {
|
|
111
|
+
return Object.assign(
|
|
112
|
+
{},
|
|
113
|
+
...props.filter(prop => o[prop] !== undefined)
|
|
114
|
+
.map(prop => ({ [prop]: o[prop] }))
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Returns the extension without the '.'
|
|
119
|
+
export function getFileExtension(path) {
|
|
120
|
+
if (path.indexOf('.') < 0) return null
|
|
121
|
+
return path.split('.').pop()
|
|
122
|
+
}
|
|
123
|
+
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import * as fsp from 'fs/promises'
|
|
2
1
|
import * as locale from 'locale-codes'
|
|
3
2
|
import * as path from 'path'
|
|
4
|
-
import { obj2Str, replaceStringVarsWithObjectValues } from '../utils.js'
|
|
5
|
-
import {
|
|
6
|
-
import { LANGTAG_DEFAULT } from '../consts.js'
|
|
3
|
+
import { obj2Str, replaceStringVarsWithObjectValues } from '../lib/utils.js'
|
|
4
|
+
import { readJsonFile } from '../lib/io.js'
|
|
5
|
+
import { LANGTAG_DEFAULT } from '../lib/consts.js'
|
|
7
6
|
|
|
8
7
|
const LocalizationMap = {}
|
|
9
8
|
|