diff --git a/plugin/package.json b/plugin/package.json index 7ddd0926..c5935d9b 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -31,6 +31,7 @@ "axios": "^1.6.7", "blocks-html-renderer": "^1.0.5", "bottleneck": "^2.19.5", + "cache-manager": "^6.1.0", "jsdom": "^24.0.0", "showdown": "^2.1.0" }, diff --git a/plugin/server/services/__tests__/format-service.test.js b/plugin/server/services/__tests__/format-service.test.js index ca322cb8..1938aaca 100644 --- a/plugin/server/services/__tests__/format-service.test.js +++ b/plugin/server/services/__tests__/format-service.test.js @@ -108,12 +108,11 @@ describe('format', () => { formatService.htmlToMarkdown(formatService.markdownToHtml(markdown)) ).toEqual(markdown) }) - test('block to html and back', () => { + test('block to html and back', async () => { const formatService = strapi.service('plugin::translate.format') - const html = formatService.blockToHtml(block) - console.log('html', html) - expect( + const html = await formatService.blockToHtml(block) + await expect( formatService.htmlToBlock(html) - ).toEqual(block) + ).resolves.toEqual(block) }) }) diff --git a/plugin/server/services/format.js b/plugin/server/services/format.js index 24d049b0..ee760960 100644 --- a/plugin/server/services/format.js +++ b/plugin/server/services/format.js @@ -2,6 +2,8 @@ const showdown = require('showdown') const jsdom = require('jsdom') +const cacheManager = require('cache-manager') +const { TRANSLATE_BLOCKS_IMAGE_CACHE_TTL } = require('../utils/constants') const renderBlock = require('blocks-html-renderer').renderBlock @@ -11,7 +13,9 @@ const showdownConverter = new showdown.Converter({ strikethrough: true, }) -const blocksImageCache = new Map() +const blocksImageCache = cacheManager.createCache({ + ttl: TRANSLATE_BLOCKS_IMAGE_CACHE_TTL +}) function markdownToHtml(singleText) { return showdownConverter.makeHtml(singleText) @@ -28,10 +32,10 @@ function htmlToMarkdown(singleText) { * * @param {Array} blocks */ -function cacheImages(blocks) { +async function cacheImages(blocks) { for (const block of blocks.flat(2)) { if (block.type === 'image') { - blocksImageCache.set(block.image.url, block.image) + await blocksImageCache.set(block.image.url, block.image) } } } @@ -111,7 +115,7 @@ function convertInlineElementToBlocks(element) { } -function convertHtmlToBlock(html) { +async function convertHtmlToBlock(html) { const root = dom.window.document.createElement('div') root.innerHTML = html @@ -163,7 +167,8 @@ function convertHtmlToBlock(html) { }) } if (child.tagName === "IMG") { - const image = blocksImageCache.has(child.src) ? blocksImageCache.get(child.src) : { + const cachedImage = await blocksImageCache.get(child.src) + const image = cachedImage != null ? cachedImage : { url: child.src, alt: child.alt, } @@ -197,11 +202,11 @@ module.exports = () => ({ } return htmlToMarkdown(text) }, - blockToHtml(block) { + async blockToHtml(block) { if (!Array.isArray(block)) { throw new Error('blockToHtml expects an array of blocks or a single block. Got ' + typeof block) } - cacheImages(block) + await cacheImages(block) if (block.length > 0 ) { if (!block[0].type) { return block.map(renderBlock) @@ -209,9 +214,9 @@ module.exports = () => ({ return renderBlock(block) } }, - htmlToBlock(html) { + async htmlToBlock(html) { if (Array.isArray(html)) { - return html.map(convertHtmlToBlock) + return Promise.all(html.map(convertHtmlToBlock)) } return convertHtmlToBlock(html) }, diff --git a/plugin/server/utils/constants.js b/plugin/server/utils/constants.js index b4bb6da5..f1d407c8 100644 --- a/plugin/server/utils/constants.js +++ b/plugin/server/utils/constants.js @@ -5,4 +5,5 @@ module.exports = { TRANSLATE_PRIORITY_BATCH_TRANSLATION: 6, TRANSLATE_PRIORITY_DIRECT_TRANSLATION: 3, TRANSLATE_PRIORITY_DEFAULT: 5, + TRANSLATE_BLOCKS_IMAGE_CACHE_TTL: 60000, } diff --git a/providers/deepl/lib/index.js b/providers/deepl/lib/index.js index 50e048c1..ad403bac 100644 --- a/providers/deepl/lib/index.js +++ b/providers/deepl/lib/index.js @@ -70,7 +70,7 @@ module.exports = { let input = text if (format === 'jsonb') { - input = formatService.blockToHtml(input) + input = await formatService.blockToHtml(input) } else if (format === 'markdown') { input = formatService.markdownToHtml(input) } diff --git a/providers/libretranslate/lib/index.js b/providers/libretranslate/lib/index.js index db1ee4be..83640602 100644 --- a/providers/libretranslate/lib/index.js +++ b/providers/libretranslate/lib/index.js @@ -75,7 +75,7 @@ module.exports = { let input = text if (format === 'jsonb') { - input = formatService.blockToHtml(input) + input = await formatService.blockToHtml(input) } else if (format === 'markdown') { input = formatService.markdownToHtml(input) } diff --git a/yarn.lock b/yarn.lock index 4d697dcc..f8fd0325 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2165,6 +2165,13 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== +"@keyv/serialize@*": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@keyv/serialize/-/serialize-1.0.1.tgz#8dae240d5fe11c589e38b73a2db238dcf26a33cf" + integrity sha512-kKXeynfORDGPUEEl2PvTExM2zs+IldC6ZD8jPcfvI351MDNtfMlw9V9s4XZXuJNDK2qR5gbEKxRyoYx3quHUVQ== + dependencies: + buffer "^6.0.3" + "@koa/cors@3.4.3": version "3.4.3" resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.4.3.tgz#d669ee6e8d6e4f0ec4a7a7b0a17e7a3ed3752ebb" @@ -5585,6 +5592,14 @@ buffer@^5.1.0, buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + buildmail@3.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/buildmail/-/buildmail-3.10.0.tgz#c6826d716e7945bb6f6b1434b53985e029a03159" @@ -5686,6 +5701,13 @@ cache-content-type@^1.0.0: mime-types "^2.1.18" ylru "^1.2.0" +cache-manager@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-6.1.0.tgz#e1d60430b358decc8b6df2ce6cdc3013303740e4" + integrity sha512-Z0gN4aTrCPNxWnXJqdTM+fAFbjYV1al9PNb3bShtw8CeNuaDloYm184f4wPNodPzTBznT3F2sqzTpwlGcL2Hjg== + dependencies: + keyv "^5.0.3" + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -9686,7 +9708,7 @@ icss-utils@^5.0.0, icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -11283,6 +11305,13 @@ keyv@*, keyv@^4.0.0: dependencies: json-buffer "3.0.1" +keyv@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-5.0.3.tgz#f0a5a3b3bf41ee4a15a1c481140c0f1e26e6af3f" + integrity sha512-WmefGWaWkWiWDkIasfHxpWmM1lych/LPtRmNj8jnIQVGLsAgFw73Vg9utZ7ss97/JwRlERABb/fSejTPY4hlZQ== + dependencies: + "@keyv/serialize" "*" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"