therealworld/langfallback-module

Language fallback for multilingual database views. Shows fallback language content when translations are empty.

Maintainers

Package info

bitbucket.org/therealworld/langfallback-module/

Homepage

Type:oxideshop-module

pkg:composer/therealworld/langfallback-module

Statistics

Installs: 6

Dependents: 0

Suggesters: 0

v1.0.1 2026-03-30 12:02 UTC

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 — maps OXTITLEoxarticles.OXTITLE
  • oxv_oxarticles_en — maps OXTITLEoxarticles.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_1 is 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.

EditionViews Modified
CEoxv_{table}_{lang}
PE / EEoxv_{table}_{lang} and oxv_{table}_{shopId}_{lang}

The special case oxobject2category (which does not use a 2shop mapping table) is handled correctly.

Installation

  1. Require via Composer:

    composer require therealworld/langfallback-module
    
  2. Activate the module:

    vendor/bin/oe-console oe:module:activate trwlangfallback
    

    On activation, the module automatically regenerates all database views with the COALESCE fallback applied.

  3. Verify:

    SHOW CREATE VIEW oxv_oxarticles_en\G
    

    You should see COALESCE(NULLIF(...), ...) expressions for all multilingual fields.

Configuration

After activation, navigate to Admin → Extensions → Modules → Language Fallback → Settings.

Settings

SettingTypeDefaultDescription
Enable language fallbackBooleantrueMaster switch to enable/disable the fallback mechanism. When disabled, standard OXID views are used.
Fallback language IDString0The 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 fallbackArray(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:

TableContent
oxarticlesArticle titles, descriptions, search keys, variant names, stock texts
oxcategoriesCategory titles, descriptions, SEO data
oxartextendsLong descriptions, meta tags
oxattributeAttribute titles
oxmanufacturersManufacturer/publisher titles, descriptions
oxcontentsCMS pages, terms, emails
oxvendorVendor 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():

  1. parent::generateViews() is called first — all standard views are created normally
  2. The module then iterates over configured tables and non-fallback languages
  3. For each combination, the language-specific view is recreated with COALESCE-wrapped multilingual fields
  4. Non-multilingual fields remain untouched
  5. On EE, shop-specific views are also recreated with the same logic

Method Overview

MethodPurpose
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): OXTITLE in base table
  • Language 1: OXTITLE_1 in base table (for languages 0–7)
  • Language 8+: OXTITLE_8 in {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 _set tables — the module adds the necessary LEFT JOINs for both the target language and the fallback language tables

Edge Cases

ScenarioBehavior
Field is empty string ('')Falls back to source language (NULLIF converts '' to NULL)
Field is NULLFalls 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 languageSkipped (no self-referencing COALESCE)
Table not in getMultiLangTables()Silently skipped
"All languages" view (oxv_oxarticles)Not modified — contains all fields with their suffixes

Performance

  • COALESCE and NULLIF are 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

EventAction
onActivateRegenerates all database views (applies COALESCE fallback)
onDeactivateRegenerates all database views (restores standard OXID views)

Dependencies

  • oxid-esales/oxideshop-ce >= 7.2
  • therealworld/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:

  1. Check that the table is listed in the module's "Tables with fallback" setting
  2. Check that the table is registered as multilingual in OXID (aMultiLangTables config or core list)
  3. 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