therealworld / langfallback-module
Language fallback for multilingual database views. Shows fallback language content when translations are empty.
Package info
bitbucket.org/therealworld/langfallback-module/
Type:oxideshop-module
pkg:composer/therealworld/langfallback-module
Requires
- php: ^8.3
- oxid-esales/oxideshop-ce: >=v7.2
- therealworld/tools-plugin: >=v3.0
This package is auto-updated.
Last update: 2026-03-30 12:02:23 UTC
README
Module ID: trwlangfallback
Vendor: therealworld
Compatibility: OXID eShop 7.2+ (CE, PE, EE)
PHP: 8.3+
Overview
When running a multilingual OXID eShop, every translatable field (article titles, descriptions, category names, etc.) must be maintained in each language. If a field is not translated, the frontend displays an empty value — which leads to a broken user experience.
This module solves the problem by introducing a language fallback mechanism at the database view level. Instead of showing empty content for untranslated fields, the shop automatically falls back to the configured source language (e.g. German). This allows you to incrementally translate your content — anything not yet translated will show the fallback language's content until a proper translation is provided.
How It Works
OXID generates database views for each language, e.g.:
oxv_oxarticles_de— mapsOXTITLE→oxarticles.OXTITLEoxv_oxarticles_en— mapsOXTITLE→oxarticles.OXTITLE_1
If OXTITLE_1 is empty, the English frontend shows nothing.
This module modifies the view generation process. After OXID creates its standard views, the module recreates the non-fallback language views with COALESCE wrapping:
-- Standard OXID view (before module):
SELECT oxarticles.OXTITLE_1 AS OXTITLE FROM oxarticles
-- Modified view (after module):
SELECT COALESCE(NULLIF(oxarticles.OXTITLE_1, ''), oxarticles.OXTITLE) AS OXTITLE FROM oxarticles
This means:
- If
OXTITLE_1(English) has content → English content is shown - If
OXTITLE_1is empty →OXTITLE(German/fallback) is shown instead
The fallback language's own views remain untouched.
Enterprise Edition (EE) Compatibility
In addition to the standard CE views (oxv_{table}_{lang}), the OXID Enterprise Edition generates shop-specific views for multi-shop setups:
oxv_{table}_{shopId}_{lang}
These views include an INNER JOIN {table}2shop and a WHERE oxshopid = {shopId} clause to filter data per shop.
This module fully supports EE multi-shop views. When running on a non-CE edition (detected via ToolsConfig::isMultiShop()), it also recreates the shop-specific language views with the same COALESCE fallback — preserving the shop filtering logic.
| Edition | Views Modified |
|---|---|
| CE | oxv_{table}_{lang} |
| PE / EE | oxv_{table}_{lang} and oxv_{table}_{shopId}_{lang} |
The special case oxobject2category (which does not use a 2shop mapping table) is handled correctly.
Installation
Require via Composer:
composer require therealworld/langfallback-moduleActivate the module:
vendor/bin/oe-console oe:module:activate trwlangfallbackOn activation, the module automatically regenerates all database views with the COALESCE fallback applied.
Verify:
SHOW CREATE VIEW oxv_oxarticles_en\GYou should see
COALESCE(NULLIF(...), ...)expressions for all multilingual fields.
Configuration
After activation, navigate to Admin → Extensions → Modules → Language Fallback → Settings.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| Enable language fallback | Boolean | true | Master switch to enable/disable the fallback mechanism. When disabled, standard OXID views are used. |
| Fallback language ID | String | 0 | The language ID to fall back to. This is the "source of truth" language. Default 0 is typically the first/primary language (e.g. German). Set to 1 if your primary content is in the second language (e.g. English). |
| Tables with fallback | Array | (see below) | List of database tables that should use the fallback mechanism. One table name per line. |
Default Fallback Tables
The following tables are configured by default:
| Table | Content |
|---|---|
oxarticles | Article titles, descriptions, search keys, variant names, stock texts |
oxcategories | Category titles, descriptions, SEO data |
oxartextends | Long descriptions, meta tags |
oxattribute | Attribute titles |
oxmanufacturers | Manufacturer/publisher titles, descriptions |
oxcontents | CMS pages, terms, emails |
oxvendor | Vendor titles |
Adding Custom Tables
If you have custom multilingual tables (registered via OXID's aMultiLangTables config parameter), simply add them to the table list in the module settings. For example, to add fallback for a custom bsnews table, add bsnews as a new line in the table array.
Note: The table must be registered as a multilingual table in OXID (i.e., it must appear in Registry::getLang()->getMultiLangTables()). Tables not recognized as multilingual are silently skipped.
Technical Details
Architecture
The module extends OxidEsales\Eshop\Application\Model\Shop and overrides generateViews():
parent::generateViews()is called first — all standard views are created normally- The module then iterates over configured tables and non-fallback languages
- For each combination, the language-specific view is recreated with COALESCE-wrapped multilingual fields
- Non-multilingual fields remain untouched
- On EE, shop-specific views are also recreated with the same logic
Method Overview
| Method | Purpose |
|---|---|
generateViews() | Override — calls parent, then applies COALESCE post-processing |
buildCoalesceFieldList() | Builds SELECT field list with COALESCE for multilingual fields |
buildFallbackJoin() | Builds JOIN clause for target and fallback language tables |
buildShopJoin() | Builds EE-specific INNER JOIN to {table}2shop |
buildShopWhere() | Builds EE-specific WHERE clause for shop filtering |
executeViewQuery() | Executes CREATE OR REPLACE VIEW with error handling |
Language Table Handling
OXID stores multilingual fields using suffixed columns:
- Language 0 (primary):
OXTITLEin base table - Language 1:
OXTITLE_1in base table (for languages 0–7) - Language 8+:
OXTITLE_8in{table}_set2(separate table)
The module handles both cases:
- Languages 0–7: All fields are in the base table — no additional JOINs needed
- Languages 8+: Fields are in
_settables — the module adds the necessary LEFT JOINs for both the target language and the fallback language tables
Edge Cases
| Scenario | Behavior |
|---|---|
Field is empty string ('') | Falls back to source language (NULLIF converts '' to NULL) |
Field is NULL | Falls back to source language (standard COALESCE behavior) |
Numeric multilingual field (e.g. OXACTIVE_1 = 0) | No fallback — NULLIF(0, '') keeps the 0 value since 0 != '' |
| Fallback language = target language | Skipped (no self-referencing COALESCE) |
Table not in getMultiLangTables() | Silently skipped |
"All languages" view (oxv_oxarticles) | Not modified — contains all fields with their suffixes |
Performance
COALESCEandNULLIFare trivially cheap MySQL functions- No additional JOINs are needed for shops with fewer than 8 languages
- Views are only regenerated when triggered by OXID (module activation, admin action, CLI command) — not on every request
Events
| Event | Action |
|---|---|
onActivate | Regenerates all database views (applies COALESCE fallback) |
onDeactivate | Regenerates all database views (restores standard OXID views) |
Dependencies
oxid-esales/oxideshop-ce>= 7.2therealworld/tools-plugin>= 3.0- PHP >= 8.3
File Structure
langfallback-module/
├── composer.json # Package definition
├── metadata.php # OXID module registration
├── README.md # This file
├── src/
│ ├── Module.php # Module ID constant
│ ├── Application/
│ │ └── Model/
│ │ └── Shop.php # Core logic (view generation override)
│ └── Core/
│ └── LangFallbackEvents.php # Activation/deactivation events
└── views/
└── admin_twig/
├── de/
│ └── module_options.php # German setting labels
└── en/
└── module_options.php # English setting labels
Troubleshooting
Views don't contain COALESCE after activation:
Manually regenerate views via CLI:
vendor/bin/oe-console oe:database:generate-views
Fallback not working for a specific table:
- Check that the table is listed in the module's "Tables with fallback" setting
- Check that the table is registered as multilingual in OXID (
aMultiLangTablesconfig or core list) - Check the OXID log for error messages from
LangFallback
Content still shows empty after enabling:
Make sure the fallback language actually has content in the corresponding fields. The fallback language's own data must be populated.
License
GPL-3.0-only / proprietary
Author
Mario Lorenz — the-real-world.de