{"id":550326,"date":"2026-07-22T13:44:24","date_gmt":"2026-07-22T13:44:24","guid":{"rendered":"https:\/\/webkul.com\/blog\/?p=550326"},"modified":"2026-07-31T04:48:23","modified_gmt":"2026-07-31T04:48:23","slug":"pimcore-deepl-bundle","status":"publish","type":"post","link":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/","title":{"rendered":"Pimcore DeepL Bundle: Automatic Content Translation"},"content":{"rendered":"<p>Translating a Pimcore catalog into every locale you sell in is slow, error-prone work by hand. The <strong>Webkul Pimcore DeepL Bundle<\/strong> does it for you.<\/p>\n<p>It connects <strong>Pimcore<\/strong>\u00a0to <strong><a href=\"https:\/\/www.deepl.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">DeepL<\/a><\/strong> and translates your content \u2014 DataObjects, documents, shared translations, and whole office files.<\/p>\n<p>You go from one source locale into as many target locales as you need.<\/p>\n<p>Everything runs inside the <strong>Pimcore Studio UI<\/strong>. No middleware to host, no copy-paste into a browser tab, no exporting strings to a spreadsheet and importing them back.<\/p>\n<p>The connection is simple: one DeepL API key, encrypted at rest, verified live before it is ever saved.<\/p>\n<p>It is built on a provider-agnostic translation engine and runs the heavy lifting through an async job queue you can watch run live.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550355\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp\" alt=\"pimcore-deepl-bundle-studio-menu\" width=\"1529\" height=\"755\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu-300x148.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu-1200x593.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu-250x123.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu-768x379.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/p>\n<h2>Pimcore DeepL Bundle: Key Features at a Glance<\/h2>\n<ul>\n<li><strong>DeepL-powered translation<\/strong> \u2014 translate DataObject localized fields, documents, shared translations, and binary office files. It goes from one source locale into many targets, over the official <code>deeplcom\/deepl-php<\/code> SDK.<\/li>\n<li><strong>Provider-agnostic engine<\/strong> \u2014 a neutral translation contract speaks <em>units<\/em> and <em>quality mode<\/em>, never DeepL&#8217;s own vocabulary. All <code>\\DeepL\\*<\/code> coupling lives behind a single quarantined boundary a CI test enforces.<\/li>\n<li><strong>Encrypted credentials<\/strong> \u2014 API keys are sealed with libsodium at rest, keyed off your app secret. The plaintext never leaves two classes and never returns from any endpoint.<\/li>\n<li><strong>Six async job scopes<\/strong> \u2014 objects, documents, shared translations, glossary sync, usage polling, and whole-file document translation. Each runs on a Doctrine Messenger queue behind a Supervisor worker.<\/li>\n<li><strong>Live job monitoring<\/strong> \u2014 every run reports per-step read, write, update, skip, and fail counts. You also get a structured log stream and a cooperative Stop button.<\/li>\n<li><strong>DeepL v3 glossaries<\/strong> \u2014 multilingual glossaries with per-language dictionaries, auto-resolved per source-to-target pair, editable as TSV or CSV.<\/li>\n<li><strong>Budget and quota guards<\/strong> \u2014 a per-job cap and a daily-plus-hard cap veto a batch <em>before<\/em> any spend. A free-tier key never blows past its allowance mid-run.<\/li>\n<li><strong>Approval gate<\/strong> \u2014 an opt-in review mode queues proposed translations as pending diffs instead of committing them. A human signs off before anything lands.<\/li>\n<li><strong>Rephrase (DeepL Write)<\/strong> \u2014 improve existing copy with a writing style or tone, right from a Studio sandbox.<\/li>\n<li><strong>Studio-native UI<\/strong> \u2014 a full <em>DeepL Translation<\/em> navigation group with 7 leaf screens plus a job-detail modal. It adds a global language editor, and nothing external to open.<\/li>\n<\/ul>\n<h2>Connecting: One DeepL Key, Encrypted and Verified<\/h2>\n<p>DeepL authentication is refreshingly simple, and Pimcore DeepL Bundle keeps it safe.<\/p>\n<p>You paste your DeepL API key into the <strong>Settings<\/strong> screen, pick an account id, and save. That is the whole handshake.<\/p>\n<p>Before the bundle writes anything, it <strong>verifies the key live<\/strong>. A <code>CredentialVerifier<\/code> builds a transient credential \u2014 never touching the database \u2014 and calls DeepL&#8217;s <code>getUsage()<\/code> endpoint.<\/p>\n<p>That round-trip proves the key authenticates without spending a single translation unit.<\/p>\n<p>An invalid key, a bad server URL, or a broken proxy is caught at the door. It never surfaces three hundred objects into a job.<\/p>\n<p>Once verified, the key is <strong>encrypted at rest<\/strong>. The bundle uses libsodium&#8217;s <code>sodium_crypto_secretbox<\/code> \u2014 authenticated symmetric encryption.<\/p>\n<p>It derives a 32-byte key from your <code>%kernel.secret%<\/code>, uses a fresh random nonce per encryption, and adds a <code>te1:<\/code> prefix so it can spot its own ciphertext.<\/p>\n<p>The plaintext key lives in exactly two places: the writer that seals it on save, and the provider that unseals it to translate.<\/p>\n<p>It is never logged, and no <code>\/api\/deepl<\/code> endpoint ever returns it. The credential list gives you a <code>hasKey<\/code> boolean and nothing more.<\/p>\n<p>That is enough to render the UI, never enough to leak a secret.<\/p>\n<p>One more nicety: the bundle <strong>derives the tier from the key itself<\/strong>. Free DeepL keys end in <code>:fx<\/code>; pro keys do not.<\/p>\n<p>The tier is read at save time. So it can never contradict the endpoint the key actually routes to.<\/p>\n<blockquote><p>Rotating <code>kernel.secret<\/code> invalidates every stored credential by design \u2014 there is no migration path for keys encrypted with the old secret.<\/p>\n<p>If you rotate it, re-enter your keys. This is a deliberate security property, not a gap.<\/p><\/blockquote>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550361\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-settings.webp\" alt=\"pimcore-deepl-bundle-settings\" width=\"1529\" height=\"755\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-settings.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-settings-300x148.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-settings-1200x593.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-settings-250x123.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-settings-768x379.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/p>\n<h2>Installing Pimcore DeepL Bundle: One Command<\/h2>\n<p>Installation is a single script. From the project root:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">bash src\/Webkul\/DeepLBundle\/install\/config.sh<\/pre>\n<p>That script is idempotent. It detects whether you are on Docker or bare-metal PHP.<\/p>\n<p>It ensures the <code>Webkul\\<\/code> PSR-4 mapping in <code>composer.json<\/code>, regenerates the autoloader, and registers the bundle in <code>config\/bundles.php<\/code>.<\/p>\n<p>Then it runs the Makefile \u2014 cache-clear, bundle install, asset install, class install, class rebuild, and a worker stop.<\/p>\n<p>It also auto-configures the Supervisor worker for the async <code>deepl<\/code> job queue, so background translation is ready the moment the script finishes.<\/p>\n<p>Pimcore DeepL Bundle uses a deliberate <strong>two-installer pattern<\/strong>, and both halves run idempotently.<\/p>\n<p>First, <code>pimcore:bundle:install<\/code> runs <code>Installer.php<\/code>, which builds:<\/p>\n<ul>\n<li><strong>8 database tables<\/strong> \u2014 <code>translation_job<\/code> and <code>translation_job_log<\/code> (job state and audit), <code>translation_usage_snapshot<\/code>, <code>translation_document_handle<\/code>, <code>translation_settings<\/code>, <code>translation_class_map<\/code>, <code>translation_preset<\/code>, and <code>translation_approval<\/code>.<\/li>\n<li><strong>10 ACL permissions<\/strong> in a <em>Webkul Translation<\/em> category, plus the bundle&#8217;s DataObject category.<\/li>\n<\/ul>\n<p>The installer keeps <strong>fresh and upgraded databases in step<\/strong>. Its <code>CREATE<\/code> statements are mirrored by <code>ensureColumn<\/code> and <code>ensureIndex<\/code> guards that check <code>information_schema<\/code> before altering.<\/p>\n<p>So an older install picks up new columns without a hand-written migration.<\/p>\n<p>Second, the <code>deepl:install-classes<\/code> command builds <strong>3 DataObject classes by name<\/strong> \u2014 <code>WkTranslationCredential<\/code>, <code>WkGlossary<\/code>, and <code>WkGlossaryDictionary<\/code>.<\/p>\n<p>They are created programmatically, never from a hardcoded numeric id and never from an export JSON.<\/p>\n<p>The <code>WkTranslationCredential<\/code> class carries the account id, provider id, the encrypted key, tier, an optional server URL and proxy, and enabled and default flags.<\/p>\n<blockquote><p>Both installer steps must run, in order: <code>Installer.php<\/code> creates the tables and permissions first, then <code>deepl:install-classes<\/code> builds the classes.<\/p>\n<p>The engine boots cleanly before the classes exist \u2014 every DataObject lookup is guarded by <code>class_exists()<\/code> and no-ops \u2014 but credentials and glossaries only work once the classes are installed.<\/p>\n<p>Running only one half leaves the bundle half-wired.<\/p><\/blockquote>\n<h2>The Provider-Agnostic Engine: DeepL Today, Anything Tomorrow<\/h2>\n<p>This is the design decision that shapes everything else, so it is worth being explicit about it.<\/p>\n<p>Pimcore DeepL Bundle is built on a <strong>provider-agnostic engine<\/strong>.<\/p>\n<p>The whole neutral surface speaks a vendor-neutral vocabulary \u2014 the translation contract, the core orchestration, the DTOs, credentials, glossaries, the sync and job layers, and the controllers.<\/p>\n<p>It counts <em>units<\/em>, not &#8220;characters.&#8221; A <em>QualityMode<\/em> stands in for DeepL&#8217;s <code>model_type<\/code>, and <em>TranslationOptions<\/em> replaces the SDK&#8217;s own option class.<\/p>\n<p>Every last line of <code>\\DeepL\\*<\/code> SDK code is <strong>quarantined under <code>src\/Provider\/DeepL\/<\/code><\/strong>.<\/p>\n<p>That covers the client factory, the request and result factories, the exception mapper, the capabilities table, and the glossary and document adapters.<\/p>\n<p>Nothing else in the bundle imports a DeepL symbol.<\/p>\n<p>This is not a convention you have to trust. A <code>SdkQuarantineBoundaryTest<\/code> enforces it in CI.<\/p>\n<p>It scans every PHP file outside <code>Provider\/DeepL\/<\/code> and strips comments and docblocks so a mention in prose cannot fool it.<\/p>\n<p>It <strong>fails the build if any <code>\\DeepL\\*<\/code> use statement or symbol appears where it should not<\/strong>.<\/p>\n<p>One important knock-on effect: the SDK&#8217;s exceptions are caught in exactly one place. A <code>DeepLExceptionMapper<\/code> wraps every SDK call and maps its failures to neutral ones.<\/p>\n<p>DeepL&#8217;s authorization error becomes a <code>ProviderAuthException<\/code>, its quota error a <code>QuotaExceededException<\/code>, its rate-limit error a <code>RateLimitedException<\/code>.<\/p>\n<p>Because the boundary is real and tested, swapping in a different provider later means writing a new adapter under <code>src\/Provider\/<\/code> and touching nothing in the neutral core.<\/p>\n<blockquote><p>Straight talk: the architecture is designed for multiple providers, but only DeepL is implemented and certified today.<\/p>\n<p>This is one flat bundle, not a separate engine bundle \u2014 the neutral layering is an internal directory boundary, kept honest by that CI test.<\/p><\/blockquote>\n<h2>Inside a Pimcore DeepL Bundle Translation: One Orchestration Choke-Point<\/h2>\n<p>Every translation \u2014 live or batched, one string or ten thousand \u2014 flows through one class: <code>TranslationManager<\/code>, aliased as the public translation service.<\/p>\n<p>It resolves everything that matters <em>before<\/em> it delegates to the provider. So the sync and async paths inherit identical correctness.<\/p>\n<p>Here is what one <code>translateText()<\/code> call does, in order:<\/p>\n<ul>\n<li><strong>Fires a pre-translate event<\/strong> first. That lets a subscriber short-circuit from cache or veto on budget before any provider work happens.<\/li>\n<li><strong>Resolves the credential<\/strong> for the requested account, decrypting the key with libsodium only at this moment.<\/li>\n<li><strong>Resolves the provider<\/strong> by the credential&#8217;s provider id, defaulting to <code>deepl<\/code>.<\/li>\n<li><strong>Maps the locales<\/strong> \u2014 a <code>LocaleMapper<\/code> turns Pimcore&#8217;s <code>en_GB<\/code> into DeepL&#8217;s <code>EN-GB<\/code> and keeps a region only when DeepL supports it as a target. It also canonicalizes ambiguous bare codes (<code>EN<\/code> to <code>EN-GB<\/code>, <code>PT<\/code> to <code>PT-PT<\/code>, <code>ZH<\/code> to <code>ZH-HANS<\/code>).<\/li>\n<li><strong>Gates formality<\/strong> \u2014 a <code>FormalityGate<\/code> asks the provider&#8217;s own capabilities whether the target supports formality, and strips the setting if not. So DeepL never rejects the request with a 400.<\/li>\n<li><strong>Runs a budget pre-flight<\/strong> \u2014 a <code>BudgetGuard<\/code> estimates the units and vetoes the batch if it would exceed the per-job cap. It runs before a single unit is spent.<\/li>\n<li><strong>Auto-resolves a glossary<\/strong> for the source-to-target pair when you did not name one explicitly.<\/li>\n<\/ul>\n<p>Only then does it build a resolved request and call the provider. On the way back, the manager <strong>normalizes the result<\/strong> \u2014 turning DeepL&#8217;s detected source code (<code>EN-GB<\/code>) back into a Pimcore locale (<code>en_GB<\/code>).<\/p>\n<p>It then fires a post-translate event that lets a subscriber meter the billed units.<\/p>\n<blockquote><p>Two honest edges of that pipeline. The budget guard counts Unicode code points, so a multi-byte emoji is one unit.<\/p>\n<p>And glossary auto-resolve is skipped when the source locale is <code>null<\/code> (auto-detect) \u2014 with nothing to key on, no glossary applies even if a matching one exists.<\/p><\/blockquote>\n<h2>Pimcore DeepL Bundle Studio UI: A Full Translation Workspace<\/h2>\n<p>Pimcore DeepL Bundle is not headless. It ships a complete Studio plugin under a navigation group labeled exactly <strong>DeepL Translation<\/strong>, with 7 leaf screens:<\/p>\n<ul>\n<li><strong>Translate<\/strong> \u2014 dispatch a batch job for objects, documents, document files, or shared translations. You can also run a small sync preview first.<\/li>\n<li><strong>Translation Jobs<\/strong> \u2014 the live job monitor.<\/li>\n<li><strong>Shared Translations<\/strong> \u2014 edit website and shared translation-domain entries.<\/li>\n<li><strong>Usage &amp; Quota<\/strong> \u2014 units used, unit limit, and used-ratio, with a Refresh button.<\/li>\n<li><strong>Glossary Manager<\/strong> \u2014 create, browse, and delete glossaries.<\/li>\n<li><strong>Test &amp; Rephrase<\/strong> \u2014 an ad-hoc sandbox for translate and DeepL Write.<\/li>\n<li><strong>Settings<\/strong> \u2014 credentials and the global language editor.<\/li>\n<\/ul>\n<p>Five modals round it out \u2014 the credential form, the language editor, glossary create and entries, and the job detail.<\/p>\n<p>Every screen talks to the <code>\/api\/deepl<\/code> backend with <code>credentials: 'include'<\/code>, speaking a uniform <code>{success, data | error}<\/code> envelope.<\/p>\n<p>That backend is <strong>24 JSON routes across 11 controllers<\/strong>, all under the <code>\/api\/deepl<\/code> prefix.<\/p>\n<p>It is a session-authenticated Studio surface with inline permission gates, not the general Studio API. So it fits Pimcore&#8217;s admin login without a separate firewall.<\/p>\n<blockquote><p>The <em>DeepL Translation<\/em> label is the nav group.<\/p>\n<p>The <em>Webkul Translation<\/em> name you will see in the permission editor is the ACL category and the DataObject class group \u2014 same bundle, two different labels, easy to confuse.<\/p><\/blockquote>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550359\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-credential-form.webp\" alt=\"pimcore-deepl-bundle-credential-form\" width=\"1529\" height=\"755\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-credential-form.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-credential-form-300x148.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-credential-form-1200x593.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-credential-form-250x123.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-credential-form-768x379.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/p>\n<h2>The Async Job Engine: Read, Process, Write<\/h2>\n<p>Bulk translation runs <strong>asynchronously<\/strong>. Dispatching a job creates a <code>translation_job<\/code> row and drops a message on the dedicated <code>deepl<\/code> Doctrine Messenger transport.<\/p>\n<p>A Supervisor-managed worker then picks it up.<\/p>\n<p>Your Studio session stays responsive; the translating happens in the background.<\/p>\n<p>The message is deliberately thin \u2014 it carries only the job id, type, credential id, and filters.<\/p>\n<p><strong>All durable state lives in the database<\/strong>, in the job, log, and document-handle tables, never on the message itself.<\/p>\n<p>When a worker runs a job, a <code>JobRunner<\/code> resolves the right handler by asking each tagged handler whether it <em>supports<\/em> the job type. There is no hardcoded dispatch table.<\/p>\n<p>The job type strings are the specification, and adding a scope means tagging one more handler.<\/p>\n<p>Most scopes are built on a reusable <strong>Read-to-Process-to-Write<\/strong> step. A reader initializes and counts, then loops: read a batch, process it, write it, merge the counts.<\/p>\n<p>The runner walks the status from <code>queued<\/code> to <code>running<\/code> to a terminal <code>done<\/code>, <code>stopped<\/code>, <code>paused<\/code>, or <code>failed<\/code>.<\/p>\n<p>A quota error is special: it <strong>pauses<\/strong> the job rather than failing it, so you can raise a cap or wait for a reset and resume, instead of starting over.<\/p>\n<blockquote><p>The <code>deepl<\/code> transport must never reuse <code>%PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX%<\/code>.<\/p>\n<p>Dev environments often set that to <code>sync:\/\/<\/code>, which would run every &#8220;async&#8221; job inline on the web request and break the queue&#8217;s concurrency assumptions.<\/p>\n<p>The bundle prepends its own Doctrine transport for exactly this reason.<\/p><\/blockquote>\n<h2>The Six Job Scopes<\/h2>\n<p>Pimcore DeepL Bundle ships <strong>six job scopes<\/strong>, each a hardcoded type string on its handler. Five share the Read-to-Process-to-Write step; one is a different animal entirely.<\/p>\n<ul>\n<li><strong><code>object<\/code><\/strong> \u2014 batch-translates DataObject localized fields, one step per target locale. It then reindexes the class synchronously after the loop.<\/li>\n<li><strong><code>document<\/code><\/strong> \u2014 translates document editables plus nav and SEO into a target-language sibling document, one step per target locale. It then reindexes documents.<\/li>\n<li><strong><code>shared<\/code><\/strong> \u2014 batch-translates shared and website translation-domain entries. It is reindex-exempt, because those entries are not search-index elements.<\/li>\n<li><strong><code>glossary_sync<\/code><\/strong> \u2014 bulk-pushes locally created glossary rows up to DeepL; one step, reindex-exempt.<\/li>\n<li><strong><code>usage_poll<\/code><\/strong> \u2014 snapshots DeepL usage per account into the usage table. It is one step, dispatched by cron or the dashboard Refresh button.<\/li>\n<li><strong><code>document_file<\/code><\/strong> \u2014 translates a whole binary office file as an async Upload-to-Poll-to-Download lifecycle (its own section below).<\/li>\n<\/ul>\n<p>Notice what is <em>not<\/em> here: not every scope reindexes. Only <code>object<\/code> and <code>document<\/code> do, because they write search-index elements. The other four are reindex-exempt by design, not by omission.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550363\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-translate.webp\" alt=\"pimcore-deepl-bundle-translate\" width=\"1529\" height=\"755\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-translate.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-translate-300x148.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-translate-1200x593.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-translate-250x123.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-translate-768x379.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/p>\n<h2>Watch Pimcore DeepL Bundle Run Live<\/h2>\n<p>You are never flying blind. The <strong>Translation Jobs<\/strong> screen lists every run with its status and counters, and clicking a row opens a live detail view.<\/p>\n<p>The job detail shows the job&#8217;s metadata and its running read, write, update, skip, and fail counts. It also shows a <strong>structured log<\/strong> rendered as a grid \u2014 every line stamped with the job&#8217;s trace id.<\/p>\n<p>Per-step metrics are deep-merged into the job row atomically, using MySQL&#8217;s <code>JSON_MERGE_PATCH<\/code>. So each step writes its own counts without a read-modify-write race clobbering another step&#8217;s numbers.<\/p>\n<p>While a job runs, the detail view shows a <strong>Stop<\/strong> button \u2014 and Stop is <em>cooperative<\/em>. It does not kill a worker mid-write.<\/p>\n<p>It flips a <code>stop_requested<\/code> flag that the step loop polls on every iteration. The worker finishes its current batch cleanly and finalizes the job as <code>stopped<\/code>.<\/p>\n<p>There are no half-written objects and no torn state.<\/p>\n<blockquote><p>Reading jobs needs the <code>webkul_translation_queue_view<\/code> permission; stopping one needs <code>webkul_translation_queue_delete<\/code>. You can let an operator watch progress without letting them halt a run.<\/p><\/blockquote>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550365\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-job-detail.webp\" alt=\"pimcore-deepl-bundle-job-detail\" width=\"1529\" height=\"755\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-job-detail.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-job-detail-300x148.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-job-detail-1200x593.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-job-detail-250x123.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-job-detail-768x379.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/p>\n<h2>Reindex-After-Write: How Pimcore DeepL Bundle Shows Translations Instantly<\/h2>\n<p>Here is a subtle failure Pimcore DeepL Bundle handles for you, and most naive translation tooling does not.<\/p>\n<p>Pimcore Studio reads the <strong>OpenSearch generic data index<\/strong>, not the database directly.<\/p>\n<p>When a job step writes a translated value to an object, the index&#8217;s save-listener does <em>not<\/em> reliably enqueue that object.<\/p>\n<p>If nobody fixes it, your translations are in the database but invisible in the tree, the grid, search, and relation pickers. They stay hidden until some manual reindex runs.<\/p>\n<p>Users see &#8220;missing&#8221; translations and file confused tickets.<\/p>\n<p>So reindex-after-write is a deliberate <strong>handler responsibility<\/strong>.<\/p>\n<p>The <code>object<\/code> and <code>document<\/code> handlers call the reindexer <em>synchronously<\/em>, after the step loop finishes, on exactly the classes and documents they touched.<\/p>\n<p>It is best-effort. A reindex failure is logged as a warning and never fails an otherwise-successful translation.<\/p>\n<p>But in the normal path, your translated content is searchable the instant the job reports done.<\/p>\n<h2>Glossaries in Pimcore DeepL Bundle: v3, Multilingual, Auto-Resolved<\/h2>\n<p>Consistent terminology is where machine translation usually slips, so Pimcore DeepL Bundle gives glossaries a first-class home.<\/p>\n<p>It models DeepL&#8217;s <strong>v3 multilingual glossaries<\/strong> as a <code>WkGlossary<\/code> parent with <code>WkGlossaryDictionary<\/code> children.<\/p>\n<p>There is one dictionary per source-to-target language pair, keyed on DeepL&#8217;s own uppercase codes.<\/p>\n<p>When you translate and do not name a glossary explicitly, the bundle <strong>auto-resolves<\/strong> one for the source-to-target pair and account.<\/p>\n<p>That lookup is memoized per request, keyed on <code>SOURCE|TARGET|ACCOUNT<\/code>. So a bulk object job of ten thousand rows does not re-query the database ten thousand times.<\/p>\n<p>Only glossaries that are <em>ready<\/em> and synced to DeepL are eligible, so a half-created one never silently applies.<\/p>\n<p>Creating a glossary is safe. <code>GlossaryManager<\/code> creates the remote glossary at DeepL first, then persists the local rows.<\/p>\n<p>If the local write fails, it rolls back by deleting the remote glossary it just made, so you never end up with a dangling remote record.<\/p>\n<p>Glossary entries round-trip as <strong>TSV or CSV<\/strong> through a neutral codec, so you can export, edit in a spreadsheet, and re-import term pairs.<\/p>\n<p>In the Glossary Manager you list, browse, and delete glossaries. A glossary that is in use returns a <strong>409 Conflict<\/strong> on delete rather than breaking a job that depends on it.<\/p>\n<blockquote><p>Honest expectation-setting: a DeepL glossary is a <em>soft bias<\/em>, not a hard guarantee.<\/p>\n<p>DeepL applies it at its own discretion \u2014 reliable for standalone terms, more hit-or-miss inside full sentences as context grows.<\/p>\n<p>That is DeepL&#8217;s platform behavior, and the bundle stays honest about it.<\/p><\/blockquote>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550367\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-glossary.webp\" alt=\"pimcore-deepl-bundle-glossary\" width=\"1529\" height=\"755\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-glossary.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-glossary-300x148.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-glossary-1200x593.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-glossary-250x123.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-glossary-768x379.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/p>\n<h2>Document Translation: Upload, Poll, Download<\/h2>\n<p>Translating a whole binary file \u2014 a Word document, a PDF, a presentation \u2014 is not a Read-to-Process-to-Write loop. DeepL translates it server-side over time.<\/p>\n<p>So the <code>document_file<\/code> scope is a distinct <strong>Upload-to-Poll-to-Download<\/strong> lifecycle.<\/p>\n<p>It runs in three steps, all self-managing:<\/p>\n<ul>\n<li><strong>Upload<\/strong> resolves Pimcore Asset and vetoes the budget hard cap <em>before<\/em> spend. It then uploads the binary to DeepL and persists a resume handle row.<\/li>\n<li><strong>Poll<\/strong> checks DeepL&#8217;s status <em>exactly once<\/em> per worker delivery. If the file is still translating, it re-dispatches the job with a <code>DelayStamp<\/code> and returns. It never busy-loops a worker.<\/li>\n<li><strong>Download<\/strong> fetches the translated bytes and writes them as a <em>new version<\/em> of the source Asset, recording the billed units exactly once.<\/li>\n<\/ul>\n<p>The poll delay is capped at <strong>60 seconds maximum<\/strong>, even if DeepL suggests longer. So a single document can never sleep a worker for hours.<\/p>\n<p>A ceiling of 40 attempts stops a stuck file from retrying forever.<\/p>\n<p>Because multiple workers might pick up the same job, the handler <strong>serializes it with a MySQL advisory lock<\/strong>.<\/p>\n<p>If another worker holds the lock, this delivery requeues and exits without doing the work. There is no double-upload and no double-bill.<\/p>\n<p>And the whole thing is <strong>resumable across worker restarts<\/strong>. A persisted handle row means the upload already happened, so a restarted job skips straight to polling and downloading.<\/p>\n<p>The download slot is claimed atomically, so a race between two workers can never bill twice.<\/p>\n<blockquote><p>Two honest limits here. Document translation does not run a glossary \u2014 the upload passes none. And the document path threads formality straight through without the formality gate the text path uses.<\/p><\/blockquote>\n<h2>Usage, Budget, and the 500k Reality<\/h2>\n<p>DeepL bills by units, and a free-tier key is capped at 500,000 characters a month. Blow past it and translations start failing. Pimcore DeepL Bundle makes sure that never surprises you.<\/p>\n<p>The <strong>Usage &amp; Quota<\/strong> dashboard shows your units used, your unit limit, the used ratio as a percentage, and a limit-reached flag.<\/p>\n<p>A Refresh button enqueues a <code>usage_poll<\/code> job to snapshot the latest figures.<\/p>\n<p>Enforcement is a <strong>pre-flight veto<\/strong>, not a post-mortem. A <code>BudgetGuard<\/code> runs two checks before any SDK call \u2014 a per-job cap and a daily cap:<\/p>\n<ul>\n<li>the <strong>per-job cap<\/strong> rejects a batch whose estimated units are too large; and<\/li>\n<li>the <strong>daily-plus-hard cap<\/strong>, enforced by a budget subscriber on the pre-translate event, sums today&#8217;s billed units from the job table and vetoes the batch if it would breach the cap.<\/li>\n<\/ul>\n<p>When a real quota error does occur, the subscriber records a limit-reached snapshot, so the Usage &amp; Quota dashboard flags the account immediately.<\/p>\n<p>Two design details keep this trustworthy. The daily total is <strong>local<\/strong> \u2014 summed from your own job rows since midnight. So it does not depend on hammering DeepL&#8217;s usage endpoint.<\/p>\n<p>The office-document estimate applies DeepL&#8217;s <strong>50,000-character minimum billing floor<\/strong>. So the veto reflects what you will actually be charged, not an optimistic guess.<\/p>\n<blockquote><p>A quota breach <em>pauses<\/em> the job, it does not fail it. Raise your cap or wait for the monthly reset, and the paused job can pick up where it left off \u2014 you do not restart from zero.<\/p><\/blockquote>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550371\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-usage-quota.webp\" alt=\"pimcore-deepl-bundle-usage-quota\" width=\"1529\" height=\"755\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-usage-quota.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-usage-quota-300x148.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-usage-quota-1200x593.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-usage-quota-250x123.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-usage-quota-768x379.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/p>\n<h2>The Approval Gate: Human Sign-Off Before Anything Lands<\/h2>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/Machine_translation\" target=\"_blank\" rel=\"noreferrer noopener\">Machine translation<\/a> is good, not infallible, and some content you simply cannot ship unreviewed. Pimcore DeepL Bundle has an <strong>approval gate<\/strong> for exactly that.<\/p>\n<p>Turn on approval for an object job, and the writer stops committing.<\/p>\n<p>Instead of writing the translated value onto the object, it <strong>queues a pending row<\/strong> in the approval table \u2014 the source text and the proposed translation, side by side.<\/p>\n<p>It counts the row as skipped, because it was queued, not applied.<\/p>\n<p>A reviewer then works the queue over three endpoints: list the pending rows, approve or reject each one, and batch-apply the approved set.<\/p>\n<p>Only <strong>approved<\/strong> rows are ever committed; pending and rejected rows never touch a target element. Safe by default.<\/p>\n<p>When the approved rows are applied, the applier <strong>reindexes the touched classes<\/strong>. So the search index sees them just like a direct job would.<\/p>\n<p>The same apply step is available on the CLI as <code>deepl:approvals:apply<\/code>, wrapping the same reindex-aware applier. So a scheduled pipeline can gate translations the same way the UI does.<\/p>\n<h2>Rephrase: DeepL Write, Built In<\/h2>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-550479\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-rephrase-write.webp\" alt=\"pimcore-deepl-bundle-rephrase-write\" width=\"1526\" height=\"760\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-rephrase-write.webp 1526w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-rephrase-write-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-rephrase-write-1200x598.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-rephrase-write-250x125.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-rephrase-write-768x382.webp 768w\" sizes=\"(max-width: 1526px) 100vw, 1526px\" loading=\"lazy\" \/><\/p>\n<p>Translation is not the only thing DeepL does well. <strong><a href=\"https:\/\/www.deepl.com\/write\" target=\"_blank\" rel=\"noreferrer noopener\">DeepL Write<\/a><\/strong> rephrases and improves existing copy, and Pimcore DeepL Bundle exposes it as first-class <em>rephrase<\/em>.<\/p>\n<p>The <strong>Test &amp; Rephrase<\/strong> sandbox lets you paste text, pick a writing <em>style<\/em> <em>or<\/em> a <em>tone<\/em>, and get back improved copy synchronously.<\/p>\n<p>Available styles are default, simple, business, academic, and casual. For tone, choose default, enthusiastic, friendly, confident, or diplomatic.<\/p>\n<p>You pick one axis or the other \u2014 style or tone, never both \u2014 and the request object enforces that at construction.<\/p>\n<p>Rephrase checks the provider&#8217;s capabilities before it calls, so an unsupported request fails cleanly rather than at the SDK.<\/p>\n<blockquote><p>Rephrase is a Pro-tier feature. A free-tier key gets a clean &#8220;unsupported&#8221; response, which the sandbox surfaces gracefully rather than throwing a raw error.<\/p>\n<p>The same holds on the CLI via <code>deepl:dev:rephrase<\/code>.<\/p><\/blockquote>\n<h2>The Global Language Editor<\/h2>\n<p>Before you can translate <em>into<\/em> a locale, Pimcore has to know about it. Pimcore DeepL Bundle puts that control right in Settings, so you never leave the workspace to add a language.<\/p>\n<p>The <strong>language editor<\/strong> is a multi-select for your valid languages and a single-select for the default language. Save it, and the bundle does the careful thing.<\/p>\n<p>It performs a <strong>full round-trip of the system configuration<\/strong> \u2014 general, documents, objects, and assets. It merges your language change into the whole config and writes it back.<\/p>\n<p>A partial save would wipe your domain, versions, error pages, and fallback languages. The full round-trip preserves every one of them.<\/p>\n<p>When you add a <em>new<\/em> language, the bundle detects it and <strong>rebuilds the classes<\/strong>. It runs <code>pimcore:deployment:classes-rebuild<\/code> and then a generic-data-index update as isolated sub-processes.<\/p>\n<p>This is mandatory, because a new language&#8217;s per-locale localized-field database tables are created at class-save time, not by a settings toggle alone.<\/p>\n<blockquote><p>This is an instance-wide change.<\/p>\n<p>Adding a language through this editor affects every bundle and every localized-field class in the whole Pimcore install \u2014 because that is what adding a Pimcore language means.<\/p>\n<p>The bundle just makes the schema rebuild happen for you instead of leaving it as a forgotten manual step.<\/p><\/blockquote>\n<h2>Roles and Permissions<\/h2>\n<p>Pimcore DeepL Bundle ships <strong>10 permission keys<\/strong> in a <em>Webkul Translation<\/em> ACL category, each with a human-readable label in the permission editor.<\/p>\n<p>Each key maps cleanly to what a person can do:<\/p>\n<ul>\n<li><strong>Read usage and jobs<\/strong> \u2014 <code>webkul_translation_usage<\/code>.<\/li>\n<li><strong>Run a live sync translate or preview<\/strong> \u2014 <code>webkul_translation_live<\/code>.<\/li>\n<li><strong>Dispatch batch jobs<\/strong> \u2014 <code>webkul_translation_batch<\/code>.<\/li>\n<li><strong>Watch jobs, and stop them<\/strong> \u2014 <code>webkul_translation_queue_view<\/code> \/ <code>webkul_translation_queue_delete<\/code>.<\/li>\n<li><strong>Manage glossaries<\/strong> \u2014 <code>webkul_translation_glossary_create<\/code> \/ <code>update<\/code> \/ <code>delete<\/code>.<\/li>\n<li><strong>Use the sandbox<\/strong> \u2014 <code>webkul_translation_test<\/code>.<\/li>\n<li><strong>Manage credentials and languages<\/strong> \u2014 <code>webkul_translation_settings<\/code>.<\/li>\n<\/ul>\n<p>So you can grant a translator &#8220;run translations and watch jobs&#8221; without handing over credential management or the language editor.<\/p>\n<p>One endpoint is deliberately open to any authenticated user: the Settings bootstrap.<\/p>\n<p>It reveals only the caller&#8217;s permission map and public config \u2014 the object classes, the valid languages, and a <code>hasCredential<\/code> boolean.<\/p>\n<p>It never reveals a secret. That lets the UI populate itself early on page load without leaking anything.<\/p>\n<h2>The CLI: 16 deepl Commands<\/h2>\n<p>Everything the UI does, the CLI does too. Pimcore DeepL Bundle ships <strong>16 console commands<\/strong> under the <code>deepl:<\/code> prefix.<\/p>\n<p>You can drive it from a script, a cron, or a deploy pipeline. They group into four clear families:<\/p>\n<ul>\n<li><strong>Install<\/strong> \u2014 <code>deepl:install-classes<\/code> builds the DataObject classes, and <code>deepl:credential:set<\/code> registers an encrypted API key from the shell.<\/li>\n<li><strong>Translate<\/strong> \u2014 <code>deepl:translate:objects<\/code>, <code>deepl:translate:documents<\/code>, <code>deepl:translate:document-files<\/code>, and <code>deepl:translate:shared<\/code> each dispatch the matching job scope onto the queue.<\/li>\n<li><strong>Operational<\/strong> \u2014 <code>deepl:glossary:sync<\/code>, <code>deepl:documents:poll<\/code>, <code>deepl:usage:poll<\/code>, and <code>deepl:approvals:apply<\/code>. They keep glossaries, document lifecycles, usage snapshots, and the approval queue moving from cron or a pipeline.<\/li>\n<li><strong>Dev helpers<\/strong> \u2014 the <code>deepl:dev:*<\/code> commands seed test data (a page, an asset, a shared entry, a test class) and exercise glossary and rephrase for local verification.<\/li>\n<\/ul>\n<blockquote><p>The <code>deepl:*<\/code> translate commands dispatch jobs asynchronously and create durable state in <code>translation_job<\/code> immediately.<\/p>\n<p>But the actual work needs the Messenger worker running \u2014 <code>bin\/console messenger:consume deepl<\/code>.<\/p>\n<p>On Docker installs, a manual <code>docker compose up -d --force-recreate supervisord<\/code> after install starts the worker; bare-metal installs start it during the install script.<\/p><\/blockquote>\n<h2>Try Pimcore DeepL Bundle<\/h2>\n<p>If Pimcore is your content hub and you sell across languages, Pimcore DeepL Bundle is the bridge you need. It stops the copy-paste and the spreadsheet round-trips.<\/p>\n<p>Installation takes one command, and setup in Studio takes minutes.<\/p>\n<p>The bundle translates objects, documents, files, and shared strings on a queue you can watch, and it is built to be re-run every day without fear.<\/p>\n<p>Run other integrations too? The same async job engine powers our sibling Pimcore products.<\/p>\n<p>Grab the <strong>Webkul Pimcore DeepL Bundle<\/strong> from the <a href=\"https:\/\/store.webkul.com\">Webkul Store<\/a>, and if you have a question \u2014 a glossary, a quota, a custom class, anything \u2014 raise a ticket at <a href=\"https:\/\/webkul.uvdesk.com\">webkul.uvdesk.com<\/a>.<\/p>\n<p>We build these bundles for a living, and we&#8217;ll help you get yours running.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Translating a Pimcore catalog into every locale you sell in is slow, error-prone work by hand. The Webkul Pimcore DeepL Bundle does it for you. It connects Pimcore\u00a0to DeepL and translates your content \u2014 DataObjects, documents, shared translations, and whole office files. You go from one source locale into as many target locales as you <a href=\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\">[&#8230;]<\/a><\/p>\n","protected":false},"author":399,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8189],"tags":[5829,8201,8487],"class_list":["post-550326","post","type-post","status-publish","format-standard","hentry","category-pimcore","tag-pim","tag-pimcore","tag-pimcore-ecommerce-connector"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Pimcore DeepL Bundle: Studio Translation, No Middleware<\/title>\n<meta name=\"description\" content=\"Pimcore DeepL Bundle connects Pimcore to DeepL and translates objects, documents, and files inside Studio: encrypted keys, glossaries, approval gate.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Pimcore DeepL Bundle: Studio Translation, No Middleware\" \/>\n<meta property=\"og:description\" content=\"Pimcore DeepL Bundle connects Pimcore to DeepL and translates objects, documents, and files inside Studio: encrypted keys, glossaries, approval gate.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\" \/>\n<meta property=\"og:site_name\" content=\"Webkul Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webkul\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-22T13:44:24+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-31T04:48:23+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp\" \/>\n<meta name=\"author\" content=\"Nitesh Arora\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@webkul\" \/>\n<meta name=\"twitter:site\" content=\"@webkul\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Nitesh Arora\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\"},\"author\":{\"name\":\"Nitesh Arora\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0\"},\"headline\":\"Pimcore DeepL Bundle: Automatic Content Translation\",\"datePublished\":\"2026-07-22T13:44:24+00:00\",\"dateModified\":\"2026-07-31T04:48:23+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\"},\"wordCount\":4082,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/webkul.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp\",\"keywords\":[\"pim\",\"Pimcore\",\"Pimcore eCommerce Connector\"],\"articleSection\":[\"PimCore\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\",\"url\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\",\"name\":\"Pimcore DeepL Bundle: Studio Translation, No Middleware\",\"isPartOf\":{\"@id\":\"https:\/\/webkul.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp\",\"datePublished\":\"2026-07-22T13:44:24+00:00\",\"dateModified\":\"2026-07-31T04:48:23+00:00\",\"description\":\"Pimcore DeepL Bundle connects Pimcore to DeepL and translates objects, documents, and files inside Studio: encrypted keys, glossaries, approval gate.\",\"breadcrumb\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage\",\"url\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp\",\"contentUrl\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp\",\"width\":1529,\"height\":755,\"caption\":\"pimcore-deepl-bundle-studio-menu\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/webkul.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Pimcore DeepL Bundle: Automatic Content Translation\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/webkul.com\/blog\/#website\",\"url\":\"https:\/\/webkul.com\/blog\/\",\"name\":\"Webkul Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/webkul.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/webkul.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/webkul.com\/blog\/#organization\",\"name\":\"WebKul Software Private Limited\",\"url\":\"https:\/\/webkul.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png\",\"contentUrl\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png\",\"width\":380,\"height\":380,\"caption\":\"WebKul Software Private Limited\"},\"image\":{\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webkul\/\",\"https:\/\/x.com\/webkul\",\"https:\/\/www.instagram.com\/webkul\/\",\"https:\/\/www.linkedin.com\/company\/webkul\",\"https:\/\/www.youtube.com\/user\/webkul\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0\",\"name\":\"Nitesh Arora\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g\",\"caption\":\"Nitesh Arora\"},\"url\":\"https:\/\/webkul.com\/blog\/author\/nitesh-arora945\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Pimcore DeepL Bundle: Studio Translation, No Middleware","description":"Pimcore DeepL Bundle connects Pimcore to DeepL and translates objects, documents, and files inside Studio: encrypted keys, glossaries, approval gate.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/","og_locale":"en_US","og_type":"article","og_title":"Pimcore DeepL Bundle: Studio Translation, No Middleware","og_description":"Pimcore DeepL Bundle connects Pimcore to DeepL and translates objects, documents, and files inside Studio: encrypted keys, glossaries, approval gate.","og_url":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/","og_site_name":"Webkul Blog","article_publisher":"https:\/\/www.facebook.com\/webkul\/","article_published_time":"2026-07-22T13:44:24+00:00","article_modified_time":"2026-07-31T04:48:23+00:00","og_image":[{"url":"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp","type":"","width":"","height":""}],"author":"Nitesh Arora","twitter_card":"summary_large_image","twitter_creator":"@webkul","twitter_site":"@webkul","twitter_misc":{"Written by":"Nitesh Arora","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#article","isPartOf":{"@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/"},"author":{"name":"Nitesh Arora","@id":"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0"},"headline":"Pimcore DeepL Bundle: Automatic Content Translation","datePublished":"2026-07-22T13:44:24+00:00","dateModified":"2026-07-31T04:48:23+00:00","mainEntityOfPage":{"@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/"},"wordCount":4082,"commentCount":0,"publisher":{"@id":"https:\/\/webkul.com\/blog\/#organization"},"image":{"@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage"},"thumbnailUrl":"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp","keywords":["pim","Pimcore","Pimcore eCommerce Connector"],"articleSection":["PimCore"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/","url":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/","name":"Pimcore DeepL Bundle: Studio Translation, No Middleware","isPartOf":{"@id":"https:\/\/webkul.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage"},"image":{"@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage"},"thumbnailUrl":"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp","datePublished":"2026-07-22T13:44:24+00:00","dateModified":"2026-07-31T04:48:23+00:00","description":"Pimcore DeepL Bundle connects Pimcore to DeepL and translates objects, documents, and files inside Studio: encrypted keys, glossaries, approval gate.","breadcrumb":{"@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#primaryimage","url":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp","contentUrl":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-deepl-bundle-studio-menu.webp","width":1529,"height":755,"caption":"pimcore-deepl-bundle-studio-menu"},{"@type":"BreadcrumbList","@id":"https:\/\/webkul.com\/blog\/pimcore-deepl-bundle\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/webkul.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Pimcore DeepL Bundle: Automatic Content Translation"}]},{"@type":"WebSite","@id":"https:\/\/webkul.com\/blog\/#website","url":"https:\/\/webkul.com\/blog\/","name":"Webkul Blog","description":"","publisher":{"@id":"https:\/\/webkul.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/webkul.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/webkul.com\/blog\/#organization","name":"WebKul Software Private Limited","url":"https:\/\/webkul.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png","contentUrl":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png","width":380,"height":380,"caption":"WebKul Software Private Limited"},"image":{"@id":"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webkul\/","https:\/\/x.com\/webkul","https:\/\/www.instagram.com\/webkul\/","https:\/\/www.linkedin.com\/company\/webkul","https:\/\/www.youtube.com\/user\/webkul\/"]},{"@type":"Person","@id":"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0","name":"Nitesh Arora","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/webkul.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g","caption":"Nitesh Arora"},"url":"https:\/\/webkul.com\/blog\/author\/nitesh-arora945\/"}]}},"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts\/550326","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/users\/399"}],"replies":[{"embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/comments?post=550326"}],"version-history":[{"count":28,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts\/550326\/revisions"}],"predecessor-version":[{"id":551704,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts\/550326\/revisions\/551704"}],"wp:attachment":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/media?parent=550326"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/categories?post=550326"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/tags?post=550326"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}