Skip to content

Commit ad6a441

Browse files
fix: drop non-string translation values and trim malformed retries
Keep valid strings when a batch response contains a non-string value so the missing ids route through the existing retry-note rounds instead of failing the locale. Cut malformed-response retries to one, and report the locales that actually have pending work in the translation preflight.
1 parent a6696ce commit ad6a441

3 files changed

Lines changed: 26 additions & 27 deletions

File tree

scripts/i18n/translate.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type TranslateBatch = (
1818

1919
const defaultRequestTimeoutMs = 120_000
2020
const maxNetworkRetries = 3
21-
const maxMalformedResponseRetries = 3
21+
const maxMalformedResponseRetries = 1
2222

2323
// Unlike es-toolkit's mapAsync, which dispatches every item up front, this
2424
// pool stops dispatching once any task fails so a fatal error does not keep
@@ -54,8 +54,6 @@ export function chunkItems(
5454
maxItems: number,
5555
maxSourceChars: number
5656
): TranslationItem[][] {
57-
// Character count is an initial batching heuristic; truncated responses are
58-
// recursively split before the translation run fails.
5957
const chunks: TranslationItem[][] = []
6058
let chunk: TranslationItem[] = []
6159
let chunkChars = 0
@@ -101,18 +99,8 @@ function parseBatchResponse(content: string): Record<string, string> {
10199
throw new Error('translation response is not a JSON object')
102100
}
103101
const record: Record<string, string> = {}
104-
const invalidIds: string[] = []
105102
for (const [key, value] of Object.entries(parsed)) {
106-
if (typeof value === 'string') {
107-
record[key] = value
108-
} else {
109-
invalidIds.push(key)
110-
}
111-
}
112-
if (invalidIds.length > 0) {
113-
throw new Error(
114-
`translation response has non-string values for ids: ${invalidIds.join(', ')}`
115-
)
103+
if (typeof value === 'string') record[key] = value
116104
}
117105
return record
118106
}

scripts/i18n/update-locales.test.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -580,18 +580,23 @@ describe('createOpenAiTranslator', () => {
580580
expect(callCount()).toBe(1)
581581
})
582582

583-
it('reports non-string response values with their ids', async () => {
584-
const malformed = () => completion('{"1": {"text": "Bonjour"}, "2": 42}')
583+
it('drops non-string values instead of failing the whole batch', async () => {
585584
const { translate, callCount } = translatorFor([
586-
malformed(),
587-
malformed(),
588-
malformed(),
589-
malformed()
585+
completion('{"1": "Bonjour {name}", "2": 42}')
590586
])
591-
await expect(translate(locale, items)).rejects.toThrow(
592-
'translation response has non-string values for ids: 1, 2'
593-
)
594-
expect(callCount()).toBe(4)
587+
await expect(translate(locale, items)).resolves.toEqual({
588+
'1': 'Bonjour {name}'
589+
})
590+
expect(callCount()).toBe(1)
591+
})
592+
593+
it('stops after one retry on a malformed response', async () => {
594+
const { translate, callCount } = translatorFor([
595+
completion('not json'),
596+
completion('not json')
597+
])
598+
await expect(translate(locale, items)).rejects.toThrow()
599+
expect(callCount()).toBe(2)
595600
})
596601

597602
it('reports usage for every completed API request', async () => {

scripts/i18n/update-locales.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,8 +469,11 @@ async function run(argv: readonly string[]): Promise<void> {
469469
(count, plan) => count + plan.items.length,
470470
0
471471
)
472-
const initialBatchCount = [...translationPlans.values()].reduce(
473-
(count, plan) =>
472+
const pendingPlans = [...translationPlans].filter(
473+
([, plan]) => plan.items.length > 0
474+
)
475+
const initialBatchCount = pendingPlans.reduce(
476+
(count, [, plan]) =>
474477
count +
475478
chunkItems(
476479
plan.items,
@@ -479,9 +482,12 @@ async function run(argv: readonly string[]): Promise<void> {
479482
).length,
480483
0
481484
)
485+
const pendingLocaleCount = new Set(
486+
pendingPlans.map(([state]) => state.locale.code)
487+
).size
482488
if (pendingTotal > 0) {
483489
print(
484-
`Translation preflight: ${pendingTotal} strings in ${initialBatchCount} initial batches across ${config.outputLocales.length} locales; retries and truncation splits can add requests.`
490+
`Translation preflight: ${pendingTotal} strings in ${initialBatchCount} initial batches across ${pendingLocaleCount} locales; retries and truncation splits can add requests.`
485491
)
486492
}
487493

0 commit comments

Comments
 (0)