diff --git a/providers/deepl/README.md b/providers/deepl/README.md index 31d9ee59..5e201c20 100644 --- a/providers/deepl/README.md +++ b/providers/deepl/README.md @@ -21,6 +21,8 @@ module.exports = { // use uppercase here! EN: 'EN-US', }, + // controls if placeholder text inside double curly brackets should be omitted from translation + omitPlaceholders: false, // Optional: Pass glossaries on translation. The correct glossary for each translation is selected by the target_lang and source_lang properties glossaries: [ { diff --git a/providers/deepl/lib/__tests__/deepl.test.js b/providers/deepl/lib/__tests__/deepl.test.js index 6a48ca41..a85cda38 100644 --- a/providers/deepl/lib/__tests__/deepl.test.js +++ b/providers/deepl/lib/__tests__/deepl.test.js @@ -395,6 +395,27 @@ describe('deepl provider', () => { await expect(deeplProvider.usage()).resolves.toBeTruthy() }) + it('omits placeholders when option used', async () => { + server.use(http.post(`${DEEPL_PAID_API}/translate`, translateHandler)) + + const deeplProvider = provider.init({ + apiKey: authKey, + omitPlaceholders: true, + }) + + // given + const params = { + sourceLocale: 'en', + targetLocale: 'de', + text: 'Some text with a placeholder {{placeholder}} in it and an {{other}} one', + } + // when + const result = await deeplProvider.translate(params) + + // then + expect(result).toEqual([params.text]) + }) + describe('api options used', () => { const registerHandlerEnforceParams = ( requiredParams, diff --git a/providers/deepl/lib/index.js b/providers/deepl/lib/index.js index 7c9012ba..5f669196 100644 --- a/providers/deepl/lib/index.js +++ b/providers/deepl/lib/index.js @@ -31,6 +31,8 @@ module.exports = { typeof providerOptions.apiOptions === 'object' ? providerOptions.apiOptions : {} + const omitPlaceholders = providerOptions.omitPlaceholders || false + const omitTags = providerOptions.omitTags || false const glossaries = Array.isArray(providerOptions.glossaries) ? providerOptions.glossaries @@ -48,6 +50,22 @@ module.exports = { const rateLimitedTranslate = limiter.wrap(client.translateText.bind(client)) + let availableGlossaries = []; + let lastGlossariesFetch = new Date(); + + const fetchGlossaries = async () => { + availableGlossaries = await client.listGlossaries(); + lastGlossariesFetch = new Date(); + } + const findGlossary = providerOptions.findGlossary || ((glossaries, sourceLocale, targetLocale) => { + return glossaries.find((glossary) => + glossary.sourceLang === sourceLocale && glossary.targetLang === targetLocale + ); + }); + + if (providerOptions.fetchGlossaries) + fetchGlossaries(); + return { /** * @param {{ @@ -81,6 +99,27 @@ module.exports = { let textArray = Array.isArray(input) ? input : [input] + let placeholderTexts = []; + let placeholderTags = []; + + if (omitTags) { + textArray = textArray.map((text) => + text.replace(/<\/?[^>]+(>|$)/g, (match) => { + placeholderTags.push(match) + return `` + }) + ) + } + + if (omitPlaceholders) { + textArray = textArray.map((text) => + text.replace(/{{(.*?)}}/g, (match) => { + placeholderTexts.push(match) + return `` + }) + ) + } + const { chunks, reduceFunction } = chunksService.split(textArray, { maxLength: DEEPL_API_MAX_TEXTS, maxByteSize: DEEPL_API_ROUGH_MAX_REQUEST_SIZE, @@ -89,7 +128,9 @@ module.exports = { const parsedSourceLocale = parseLocale(sourceLocale, localeMap, 'source') const parsedTargetLocale = parseLocale(targetLocale, localeMap, 'target') - const glossary = glossaries.find( + let glossary = undefined; + + glossary = glossaries.find( (g) => g.target_lang === parsedTargetLocale && g.source_lang === parsedSourceLocale )?.id @@ -97,7 +138,13 @@ module.exports = { console.warn('Glossary provided in apiOptions will be ignored and overwritten by the actual glossary that should be used for this translation.') } - const result = reduceFunction( + if (providerOptions.fetchGlossaries) { + if (new Date() - lastGlossariesFetch > (providerOptions.fetchGlossariesIntervalMs || 3600000)) + await fetchGlossaries(); + glossary = findGlossary(availableGlossaries, sourceLocale, targetLocale)?.glossaryId || glossary; + } + + let result = reduceFunction( await Promise.all( chunks.map(async (texts) => { const result = await rateLimitedTranslate.withOptions( @@ -116,7 +163,19 @@ module.exports = { }) ) ) - + + if (omitPlaceholders) { + result = result.map((text) => + text.replace(//g, (_, id) => placeholderTexts[id]) + ) + } + + if (omitTags) { + result = result.map((text) => + text.replace(//g, (_, id) => placeholderTags[id]) + ) + } + if (format === 'jsonb') { return formatService.htmlToBlock(result) } diff --git a/providers/deepl/package.json b/providers/deepl/package.json index 01791f9a..023c3025 100644 --- a/providers/deepl/package.json +++ b/providers/deepl/package.json @@ -1,6 +1,6 @@ { - "name": "strapi-provider-translate-deepl", - "version": "1.2.8", + "name": "strapi-provider-translate-deepl-lokki", + "version": "1.2.9", "description": "DeepL provider for translate plugin in Strapi 4", "keywords": [ "strapi",