Files
zeekay 05a6444634 translate: numbered strings, streamed answers, one line of requests
Three faults met running the shared catalog on local models:

- A catalog keyed by its English sent "Log in": "Log in", and Jais 3,
  told to keep the keys, translated both sides: no answer matched and
  every Arabic group was refused. Strings now go numbered and are read
  back by number.
- An unstreamed group from a slow model (Jais 3 at four tokens a second
  a slot) outlasted fetch's five minutes for headers and was refused
  without a word. Answers are streamed; an unstreamed answer still reads.
- --jobs ran n locales at a slot each, so all finished at the end. It is
  now n requests in flight over one line in locale order (gate): a
  server's every slot works one locale's groups, which is written first.

Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
2026-10-22 15:12:20 -07:00

118 lines
5.9 KiB
TypeScript

import assert from 'node:assert/strict'
import { test } from 'node:test'
import { chat } from '../src/chat.ts'
import { asking, fill, type Strings } from '../src/fill.ts'
import { service } from '../src/service.ts'
type Sent = { url: string; headers: Record<string, string>; body: Record<string, unknown> }
/** A fetch that records each request and answers with `reply(body)`. */
function serve(reply: (body: Record<string, unknown>) => unknown, status = 200): Sent[] {
const sent: Sent[] = []
globalThis.fetch = (async (url: string, init: RequestInit) => {
const body = JSON.parse(String(init.body)) as Record<string, unknown>
sent.push({ url, headers: init.headers as Record<string, string>, body })
const answer = reply(body)
return { ok: status < 400, status, json: async () => answer, text: async () => JSON.stringify(answer) } as Response
}) as typeof fetch
return sent
}
const original = globalThis.fetch
test.afterEach(() => {
globalThis.fetch = original
})
const terms = { keep: ['Hanzo', 'Claude Code'] }
test('/v1/translate is asked for a batch, in order, with the protected names as its glossary', async () => {
const sent = serve((body) => ({
translations: (body.batch as string[]).map((s) => ({ source: s, text: `ar:${s}`, state: 'machine', cached: false })),
tier: 'quality',
}))
const source: Strings = { 'Sign in to Hanzo': 'Sign in to Hanzo', About: 'About' }
const { added, refused } = await fill(source, {}, 'ar', service('https://api.hanzo.ai/', 'k', 'quality', terms), terms)
assert.deepEqual(added, { 'Sign in to Hanzo': 'ar:Sign in to Hanzo', About: 'ar:About' })
assert.deepEqual(refused, [])
assert.equal(sent[0].url, 'https://api.hanzo.ai/v1/translate')
assert.equal(sent[0].headers.authorization, 'Bearer k')
assert.deepEqual(sent[0].body, {
batch: ['Sign in to Hanzo', 'About'],
target: 'ar',
source: 'en',
tier: 'quality',
glossary: { Hanzo: 'Hanzo', 'Claude Code': 'Claude Code' },
})
})
test('a string the endpoint could not translate stays missing for the next run', async () => {
serve(() => ({ translations: [{ source: 'A', text: 'Ah' }, { source: 'B', error: 'placeholder lost' }] }))
const { added, refused } = await fill({ a: 'A', b: 'B' }, {}, 'fr-FR', service('https://api.hanzo.ai', 'k', 'bulk'))
assert.deepEqual(added, { a: 'Ah' })
assert.deepEqual(refused, ['b'])
})
test('an answer that does not line up with the batch is an error, not a guess', async () => {
serve(() => ({ translations: [{ text: 'Ah' }] }))
await assert.rejects(fill({ a: 'A', b: 'B' }, {}, 'fr-FR', service('https://api.hanzo.ai', 'k', 'quality')), /1 translations for 2 strings/)
serve(() => ({ error: 'insufficient funds' }), 402)
await assert.rejects(fill({ a: 'A' }, {}, 'fr-FR', service('https://api.hanzo.ai', 'k', 'quality')), /402/)
})
test('a translation that drops a protected name or its {placeholder} is refused, whatever engine wrote it', async () => {
serve((body) => ({
translations: (body.batch as string[]).map((s) =>
s.startsWith('Accelerate') ? { text: 'سرّع كلود كود' } : { text: 'مرحبا' },
),
}))
const { added, refused } = await fill(
{ 'Accelerate Claude Code with Hanzo': 'Accelerate Claude Code with Hanzo', 'Hello {name}': 'Hello {name}' },
{},
'ar',
service('https://api.hanzo.ai', 'k', 'quality', terms),
terms,
)
assert.deepEqual(added, {})
assert.deepEqual(refused.sort(), ['Accelerate Claude Code with Hanzo', 'Hello {name}'])
})
test('a local OpenAI-compatible endpoint needs no key and takes template arguments', async () => {
const sent = serve((body) => {
const want = JSON.parse((body.messages as { content: string }[])[1].content) as Strings
const answer = Object.fromEntries(Object.entries(want).map(([n, english]) => [n, `مرحبا ${english}`]))
return { choices: [{ finish_reason: 'stop', message: { content: `<think></think>${JSON.stringify(answer)}` } }] }
})
const model = chat('jais3-ar', 'http://127.0.0.1:8733/v1/chat/completions', undefined, {
chat_template_kwargs: { enable_thinking: false, reasoning_effort: 'low' },
})
const { added } = await fill({ Hanzo: 'Hanzo' }, {}, 'ar', asking(model, terms), terms)
assert.deepEqual(added, { Hanzo: 'مرحبا Hanzo' })
assert.equal(sent[0].headers.authorization, undefined)
assert.equal(sent[0].body.model, 'jais3-ar')
assert.deepEqual(sent[0].body.chat_template_kwargs, { enable_thinking: false, reasoning_effort: 'low' })
const system = (sent[0].body.messages as { content: string }[])[0].content
assert.match(system, /into Arabic \(ar\)/)
assert.match(system, /Modern Standard Arabic/)
assert.match(system, /Keep these names exactly as written.*Hanzo, Claude Code/)
})
test('a truncated chat answer is an error, so its keys are asked again', async () => {
serve(() => ({ choices: [{ finish_reason: 'length', message: { content: '{"a": "A' } }] }))
const model = chat('zen5', 'https://api.hanzo.ai/v1/chat/completions', 'k')
await assert.rejects(fill({ a: 'A' }, {}, 'fr-FR', asking(model)), /truncated/)
})
test('a streamed chat answer is read whole, and a streamed truncation is still an error', async () => {
const events = (...chunks: object[]) => chunks.map((c) => `data: ${JSON.stringify(c)}\n\n`).join('') + 'data: [DONE]\n\n'
const stream = (body: string) =>
(globalThis.fetch = (async () =>
({ ok: true, status: 200, headers: new Headers({ 'content-type': 'text/event-stream' }), text: async () => body }) as Response) as typeof fetch)
stream(events({ choices: [{ delta: { content: '{"1": "Ah' } }] }, { choices: [{ delta: { content: '"}' }, finish_reason: 'stop' }] }))
const model = chat('jais3-ar', 'http://127.0.0.1:8733/v1/chat/completions')
assert.deepEqual((await fill({ a: 'A' }, {}, 'fr-FR', asking(model))).added, { a: 'Ah' })
stream(events({ choices: [{ delta: { content: '{"1": "A' }, finish_reason: 'length' }] }))
await assert.rejects(fill({ a: 'A' }, {}, 'fr-FR', asking(model)), /truncated/)
})