strapi-provider-translate-libretranslate
Version:
Libretranslate provider for translate plugin in Strapi 4
352 lines (307 loc) • 8.54 kB
JavaScript
const { faker } = require('@faker-js/faker')
const { http, HttpResponse } = require('msw')
const provider = require('..')
const { getServer } = require('../../__mocks__/server')
const BASE_URL = 'https://lt.example.org'
const enabledLocales = [
{
code: 'en',
name: 'English',
targets: [
'ar',
'az',
'ca',
'cs',
'da',
'de',
'el',
'en',
'eo',
'fi',
'fr',
'ga',
'he',
'hi',
'hu',
'id',
'it',
'nl',
'zh',
],
},
]
describe('libretranslate provider', () => {
let server
function buildTranslateHandler({ maxChars, maxTexts }) {
maxChars = maxChars || -1
maxTexts = maxTexts || -1
return async ({ request }) => {
const json = await request.json()
if (
maxTexts !== -1 &&
Array.isArray(json.q) &&
json.q.length > maxTexts
) {
console.warn('Too many Texts', json.q.length)
return new HttpResponse(null, { status: 400 })
}
if (maxChars !== -1) {
if (
Array.isArray(json.q) &&
json.q.reduce((prev, curr) => prev + curr.length, 0) > maxChars
) {
console.warn('Batch translation, too many characters')
return new HttpResponse(null, { status: 400 })
} else if (json.q.length > maxChars) {
console.warn('Single translation, too many characters')
return new HttpResponse(null, { status: 400 })
}
}
let targetLang = json.target
if (!targetLang) {
return new HttpResponse(null, { status: 400 })
}
let sourceLang = json.source
if (!sourceLang) {
return new HttpResponse(null, { status: 400 })
}
return HttpResponse.json({
translatedText: json.q,
})
}
}
beforeAll(() => {
server = getServer()
server.use(
http.get(`${BASE_URL}/languages`, () => HttpResponse.json(enabledLocales))
)
Object.defineProperty(global, 'strapi', {
value: require('../../__mocks__/initStrapi')({}),
writable: true,
})
})
afterEach(async () => {
server.resetHandlers()
})
afterAll(async () => {
server.close()
})
describe('usage', () => {
let ltProvider
beforeAll(() => {
ltProvider = provider.init({ apiUrl: BASE_URL })
})
it('returns undefined', async () => {
await expect(ltProvider.usage()).resolves.not.toBeDefined()
})
})
describe('translate', () => {
let ltProvider
beforeEach(() => {
server.use(
http.post(
`${BASE_URL}/translate`,
buildTranslateHandler({ maxTexts: 50, maxChars: 10000 })
)
)
server.use(
http.get(`${BASE_URL}/languages`, async () =>
HttpResponse.json(enabledLocales)
)
)
ltProvider = provider.init({
apiUrl: BASE_URL,
apiMaxTexts: 50,
apiMaxChars: 10000,
})
})
describe('succeeds', () => {
async function singleText() {
// given
const params = {
sourceLocale: 'en',
targetLocale: 'de',
text: 'Some text',
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual([params.text])
}
async function multipleTexts() {
// given
const params = {
sourceLocale: 'en',
targetLocale: 'de',
text: ['Some text', 'Some more text', 'Even more text'],
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual(params.text)
}
async function markdownTexts() {
// given
const params = {
sourceLocale: 'en',
targetLocale: 'de',
text: ['# Heading\n\nSome text', '## Subheading\n\nSome more text'],
format: 'markdown',
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual(params.text)
}
async function forMissingText() {
// given
const params = {
sourceLocale: 'en',
targetLocale: 'de',
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual([])
}
async function forNTexts(n = 120) {
// given
const textLength = n
const params = {
sourceLocale: 'en',
targetLocale: 'de',
text: Array.from({ length: textLength }, (_v, i) => `text ${i}`),
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual(params.text)
}
it('with single text', async () => {
await singleText()
})
it('with multiple texts', async () => {
await multipleTexts()
})
it('with missing text', async () => {
await forMissingText()
})
it('with markdown texts', async () => {
await markdownTexts()
})
it('with more than more texts than limit', async () => {
await forNTexts()
})
it('with all fields together more than request size limit', async () => {
// given
const textLength = 40
const params = {
sourceLocale: 'en',
targetLocale: 'de',
text: faker.helpers.uniqueArray(
() => faker.lorem.paragraphs(20),
textLength
),
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual(params.text)
})
it('with a field larger than request size limit', async () => {
// given
let text = faker.lorem.paragraphs(5)
while (text.length < 10000) {
text += faker.lorem.paragraphs(5)
}
const params = {
sourceLocale: 'en',
targetLocale: 'de',
text: [text],
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual(params.text)
})
it('with some fields larger than request size limit', async () => {
// given
const textLength = 20
const params = {
sourceLocale: 'en',
targetLocale: 'de',
text: faker.helpers.uniqueArray(
() =>
faker.lorem.paragraphs(
faker.number.int({
min: 10000 / 2000,
max: 10000 / 200,
})
),
textLength
),
}
// when
const result = await ltProvider.translate(params)
// then
expect(result).toEqual(params.text)
})
it('with rate limiting', async () => {
ltProvider = provider.init({
apiUrl: BASE_URL,
apiMaxRPM: 60,
apiMaxTexts: 1,
})
let firstRequest
let lastRequest
server.use(
http.post(`${BASE_URL}/translate`, async ({ request }) => {
const json = await request.json()
if (!firstRequest) firstRequest = new Date()
lastRequest = new Date()
return HttpResponse.json({
translatedText: json.q,
})
})
)
await forNTexts(5)
expect(lastRequest - firstRequest).toBeGreaterThanOrEqual(3900)
})
})
describe('fails', () => {
it('with missing target language', async () => {
// given
const params = {
text: 'Some text',
sourceLocale: 'en',
}
await expect(
// when
async () => ltProvider.translate(params)
// then
).rejects.toThrow('source and target locale must be defined')
})
it('with missing source language', async () => {
// given
const params = {
targetLocale: 'de',
text: 'Some text',
}
await expect(
// when
async () => ltProvider.translate(params)
// then
).rejects.toThrow('source and target locale must be defined')
})
})
})
describe('init', () => {
it('throws when no URL is provided', () => {
delete process.env.LT_API_URL
expect(() => provider.init()).toThrow(
'You must provide a URL to the LibreTranslate API'
)
})
})
})