Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-popover": "^1.1.15",
"@translated/lara": "1.8.0-beta.3",
"@translated/lara": "^1.9.0",
"classnames": "^2.2.6",
"crypto-js": "^4.1.1",
"diff-match-patch": "^1.0.5",
Expand Down
58 changes: 57 additions & 1 deletion public/js/api/laraTranslate/laraTranslate.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
import {AuthToken, Translator} from '@translated/lara'

class LaraTranslator extends Translator {
async translate(text, source, target, options, callback) {
const headers = {}

if (options?.headers) {
for (const [name, value] of Object.entries(options.headers)) {
headers[name] = value
}
}

if (options?.noTrace) {
headers['X-No-Trace'] = 'true'
}

const response = this.client.postAndGetStream(
'/v2/translate',
{
q: text,
source,
target,
source_hint: options?.sourceHint,
content_type: options?.contentType,
multiline: options?.multiline !== false,
adapt_to: options?.adaptTo,
glossaries: options?.glossaries,
instructions: options?.instructions,
timeout: options?.timeoutInMillis,
priority: options?.priority,
use_cache: options?.useCache,
cache_ttl: options?.cacheTTLSeconds,
verbose: options?.verbose,
style: options?.style,
reasoning: options?.reasoning,
metadata: options?.metadata,
profanities_detect: options?.profanitiesDetect,
profanities_handling: options?.profanitiesHandling,
styleguide_id: options?.styleguideId,
styleguide_reasoning: options?.styleguideReasoning,
styleguide_explanation_language: options?.styleguideExplanationLanguage,
},
undefined,
headers,
)

let lastResult
for await (const partial of response) {
if (options?.reasoning && callback) callback(partial)
Comment thread
riccio82 marked this conversation as resolved.
lastResult = partial
}

if (!lastResult) throw new Error('No translation result received.')

return lastResult
}
}

export const laraTranslate = async ({
token,
source,
Expand All @@ -13,7 +69,7 @@ export const laraTranslate = async ({
}) => {
const credentials = new AuthToken(token, null)

const lara = new Translator(credentials, {
const lara = new LaraTranslator(credentials, {
connectionTimeoutMs: 30000,
})
let textBlocks = [
Expand Down
170 changes: 170 additions & 0 deletions public/js/api/laraTranslate/laraTranslate.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {laraTranslate} from './laraTranslate'

const mockPostAndGetStream = jest.fn()

jest.mock('@translated/lara', () => {
class MockTranslator {
constructor() {
this.client = {postAndGetStream: mockPostAndGetStream}
}
}
return {
AuthToken: jest.fn(),
Translator: MockTranslator,
}
})

global.config = {
source_rfc: 'en-US',
target_rfc: 'it-IT',
}

beforeEach(() => {
mockPostAndGetStream.mockReset()
})

async function* asyncIterableOf(...items) {
for (const item of items) {
yield item
}
}

test('Returns last streamed translation result', async () => {
mockPostAndGetStream.mockReturnValue(
asyncIterableOf({translation: 'partial'}, {translation: 'Ciao mondo'}),
)

const result = await laraTranslate({
token: 'fake-token',
source: 'Hello world',
contextListBefore: [],
contextListAfter: [],
sid: '1',
jobId: '100',
glossaries: [],
style: undefined,
reasoning: false,
})

expect(result).toEqual({translation: 'Ciao mondo'})
})

test('Builds text blocks with context before and after', async () => {
mockPostAndGetStream.mockReturnValue(
asyncIterableOf({translation: 'result'}),
)

await laraTranslate({
token: 'fake-token',
source: 'Main segment',
contextListBefore: ['ctx before 1', 'ctx before 2'],
contextListAfter: ['ctx after 1'],
sid: '5',
jobId: '200',
glossaries: [],
style: undefined,
reasoning: false,
})

const callArgs = mockPostAndGetStream.mock.calls[0]
const endpoint = callArgs[0]
const payload = callArgs[1]

expect(endpoint).toBe('/v2/translate')
expect(payload.q).toEqual([
{text: 'ctx before 1', translatable: false},
{text: 'ctx before 2', translatable: false},
{text: 'Main segment', translatable: true},
{text: 'ctx after 1', translatable: false},
])
})

test('Passes correct translation options', async () => {
mockPostAndGetStream.mockReturnValue(
asyncIterableOf({translation: 'result'}),
)

await laraTranslate({
token: 'fake-token',
source: 'Hello',
contextListBefore: [],
contextListAfter: [],
sid: '3',
jobId: '50',
glossaries: [{id: 'g1'}],
style: 'formal',
reasoning: true,
})

const callArgs = mockPostAndGetStream.mock.calls[0]
const payload = callArgs[1]
const headers = callArgs[3]

expect(payload.source).toBe('en-US')
expect(payload.target).toBe('it-IT')
expect(payload.multiline).toBe(false)
expect(payload.content_type).toBe('application/xliff+xml')
expect(payload.glossaries).toEqual([{id: 'g1'}])
expect(payload.reasoning).toBe(true)
expect(payload.style).toBe('formal')
expect(headers).toEqual({'X-Lara-Engine-Tuid': '50:3'})
})

test('Sets X-Lara-Engine-Tuid header from jobId and sid', async () => {
mockPostAndGetStream.mockReturnValue(
asyncIterableOf({translation: 'result'}),
)

await laraTranslate({
token: 'token',
source: 'text',
contextListBefore: [],
contextListAfter: [],
sid: '42',
jobId: '999',
glossaries: [],
style: undefined,
reasoning: false,
})

const headers = mockPostAndGetStream.mock.calls[0][3]
expect(headers).toEqual({'X-Lara-Engine-Tuid': '999:42'})
})

test('Throws error when stream yields no results', async () => {
mockPostAndGetStream.mockReturnValue(asyncIterableOf())

await expect(
laraTranslate({
token: 'token',
source: 'text',
contextListBefore: [],
contextListAfter: [],
sid: '1',
jobId: '1',
glossaries: [],
style: undefined,
reasoning: false,
}),
).rejects.toThrow('No translation result received.')
})

test('Returns final result when multiple partials are streamed with reasoning', async () => {
const partial1 = {translation: 'thinking...', reasoning: 'step 1'}
const partial2 = {translation: 'final answer', reasoning: 'step 2'}
mockPostAndGetStream.mockReturnValue(asyncIterableOf(partial1, partial2))

const result = await laraTranslate({
token: 'token',
source: 'text',
contextListBefore: [],
contextListAfter: [],
sid: '1',
jobId: '1',
glossaries: [],
style: undefined,
reasoning: true,
})

expect(result).toEqual(partial2)
})
Loading
Loading