Tools: Your Drupal Commerce Store Serves 6 Languages. Your Chat Handles One.

Tools: Your Drupal Commerce Store Serves 6 Languages. Your Chat Handles One.

Source: Dev.to

How Translations Work in Drupal Commerce ## Three Layers of "Multilingual" ## How the Module Handles This ## Cross-Language Search ## What Doesn't Work Well ## The Practical Takeaway I already had a webhook sync module for Drupal Commerce. Products synced, the chat widget worked, customers could search and get answers. Then a store in Norway asked me why the assistant was answering in English when their site was in Norwegian. The module was syncing products. But only the default language. A store with translations in Norwegian, Swedish, Danish, and Finnish was sending English-only product data to the chat service. The assistant searched English descriptions and responded in English regardless of the store view. Their entire investment in translations was invisible to the chat. I spent two weeks fixing this. Here's what "multilingual chat" means, technically. Drupal Commerce uses the Content Translation module. A product entity has a canonical version (usually English) with translation sets for each enabled language. Each translation carries its own title, description, meta description, and any translatable custom fields. Drupal stores these as separate field_data rows keyed by language code. When you load a product with a specific language context, you get that language's field values with automatic fallback to the default if a translation is missing. For Commerce stores with product variations, the numbers multiply. A jacket with 3 colors and 4 sizes means 12 variations. Each variation can have its own translated title and attributes. Across 4 languages, that's 48 translation sets for one product. A store with 5,000 products in 4 languages can easily have 100,000+ translated field entries. When a vendor says "multilingual support," ask which layer: Layer 1: Widget UI. The chat interface (buttons, placeholders, system messages) displays in the visitor's language. This is the easy part. Load a translated string file, switch based on Drupal's current language. Layer 2: Search index. When a Norwegian customer searches for "vinterjakke," the search needs to hit Norwegian product data. If the index only contains English descriptions, the search either returns nothing (the word "vinterjakke" doesn't appear in English text) or returns irrelevant results from fuzzy matching. Layer 3: Response language. The LLM composes its answer in Norwegian, using Norwegian product names and descriptions as source material. If it pulls from the English index and translates on the fly, the product names won't match what the customer sees on the store. Most chat tools get Layer 1 right but stop there, leaving Layers 2 and 3 unaddressed. They translate the UI, search a single English index, and hope the LLM's output translation is good enough. The first version of my Drupal module sent one webhook per product containing only the default language fields. The fix was to iterate over all enabled languages, load each translation, and nest them in a single payload: The "" channel key means "store-wide" (single channel). Stores with B2B and retail channels would use "b2b" and "retail" as keys, each with their own prices and availability. All translatable fields (names, descriptions, links, attributes) nest languages inside the channel key. One HTTP request per product instead of four. On the receiving side, Emporiqa builds a separate search index for each language. Norwegian product descriptions go into the Norwegian index, Swedish into the Swedish index. The module reads enabled languages from Drupal's LanguageManagerInterface and checks which translations exist for each product. Missing translations fall back to the default language rather than sending empty fields. The widget embed tag carries the language: In the Drupal module, the language code comes from the current language context via \Drupal::languageManager()->getCurrentLanguage(). A visitor on the Norwegian store view gets language=no, the widget loads Norwegian UI text, searches the Norwegian index, and the LLM responds in Norwegian. A Bulgarian tourist visits a Norwegian store and types "зимно яке" (Bulgarian for winter jacket). The products are in Norwegian. Keyword matching fails completely. "зимно яке" shares zero characters with "Vinterjakke." But multilingual embedding models map both phrases to nearby points in vector space because they mean the same thing. The vector search returns the Norwegian winter jacket without any explicit translation step. I use multilingual embeddings that cover 65+ languages. The same model encodes both the Norwegian product descriptions and the Bulgarian query into vectors in a shared space. Semantic similarity works across language boundaries. A German store serving Austria and Switzerland will have customers who also speak Turkish, Croatian, Italian, or Serbian. The chat handles all of them without the store needing translations in each of those languages. Partial translations are the biggest issue. A product with English and Norwegian translations but no Swedish version means Swedish customers see the English fallback. The assistant doesn't warn them it's showing content in a different language. It returns what it found. Missing field translations create a subtler problem. Some stores translate product titles but not descriptions. The search finds the product by its Norwegian title, but the description comes back in English. The response mixes languages. Confusing for the customer. First-message language mismatch. The widget language is set by the embed tag based on Drupal's current store view. If an English-speaking tourist on a Norwegian site types in English, the system detects the switch after processing the first message. That first response may come back in Norwegian before it adapts. CMS page gaps. If your return policy only exists in English, a French customer asking about returns gets English text. Technically accurate, but not a great experience. The fix is simple (translate your key policy pages), but many stores skip this for low-traffic languages. Multilingual chat is only as good as your translation data. Full translations across all products and pages give the best results. Partial translations work but produce mixed-language responses. No translations mean everyone gets the default language regardless of the store view. If you're running a multilingual Drupal Commerce store, the question worth asking about any chat tool is: "does it search the right language index and respond in the right language with the right product names?" The Emporiqa module on drupal.org handles translation sync, language detection, and widget embedding for Drupal 10+. The full setup guide is at emporiqa.com/docs/drupal/. You can test multilingual search with a free sandbox (100 products, no credit card). Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: Product: Winter Jacket (node/42) ├── en: "Winter Jacket" / "Waterproof, breathable..." ├── no: "Vinterjakke" / "Vanntett, pustende..." ├── sv: "Vinterjacka" / "Vattentät, andningsbar..." └── da: "Vinterjakke" / "Vandtæt, åndbar..." Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: Product: Winter Jacket (node/42) ├── en: "Winter Jacket" / "Waterproof, breathable..." ├── no: "Vinterjakke" / "Vanntett, pustende..." ├── sv: "Vinterjacka" / "Vattentät, andningsbar..." └── da: "Vinterjakke" / "Vandtæt, åndbar..." CODE_BLOCK: Product: Winter Jacket (node/42) ├── en: "Winter Jacket" / "Waterproof, breathable..." ├── no: "Vinterjakke" / "Vanntett, pustende..." ├── sv: "Vinterjacka" / "Vattentät, andningsbar..." └── da: "Vinterjakke" / "Vandtæt, åndbar..." CODE_BLOCK: { "type": "product.updated", "data": { "identification_number": "PROD-42", "sku": "WJ-001", "channels": [""], "names": { "": { "en": "Winter Jacket", "no": "Vinterjakke", "sv": "Vinterjacka", "da": "Vinterjakke" } }, "descriptions": { "": { "en": "Waterproof, breathable...", "no": "Vanntett, pustende...", "sv": "Vattentät, andningsbar...", "da": "Vandtæt, åndbar..." } }, "prices": { "": [{ "currency": "EUR", "current_price": 89.99, "regular_price": 119.99 }] }, "availability_statuses": { "": "available" }, "stock_quantities": { "": 25 } } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "type": "product.updated", "data": { "identification_number": "PROD-42", "sku": "WJ-001", "channels": [""], "names": { "": { "en": "Winter Jacket", "no": "Vinterjakke", "sv": "Vinterjacka", "da": "Vinterjakke" } }, "descriptions": { "": { "en": "Waterproof, breathable...", "no": "Vanntett, pustende...", "sv": "Vattentät, andningsbar...", "da": "Vandtæt, åndbar..." } }, "prices": { "": [{ "currency": "EUR", "current_price": 89.99, "regular_price": 119.99 }] }, "availability_statuses": { "": "available" }, "stock_quantities": { "": 25 } } } CODE_BLOCK: { "type": "product.updated", "data": { "identification_number": "PROD-42", "sku": "WJ-001", "channels": [""], "names": { "": { "en": "Winter Jacket", "no": "Vinterjakke", "sv": "Vinterjacka", "da": "Vinterjakke" } }, "descriptions": { "": { "en": "Waterproof, breathable...", "no": "Vanntett, pustende...", "sv": "Vattentät, andningsbar...", "da": "Vandtæt, åndbar..." } }, "prices": { "": [{ "currency": "EUR", "current_price": 89.99, "regular_price": 119.99 }] }, "availability_statuses": { "": "available" }, "stock_quantities": { "": 25 } } } CODE_BLOCK: <script async src="https://emporiqa.com/chat/embed/?store_id=STORE_ID&language=no"> </script> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: <script async src="https://emporiqa.com/chat/embed/?store_id=STORE_ID&language=no"> </script> CODE_BLOCK: <script async src="https://emporiqa.com/chat/embed/?store_id=STORE_ID&language=no"> </script>