Skip to content

Multi-Language (MUI) Translation Pattern

Flow ID: SY-23 | Type: Shared Pattern | Complexity: Medium Last Updated: 2026-05-12

Business Overview

The MUI (Multi-User Interface / Multi-Language) pattern is the platform's standard approach for storing and retrieving translated content. Every entity that needs localized text -- product names, category descriptions, badge labels, SEO metadata, blog articles -- follows the same companion-table pattern: a master table holds language-independent data, and a _mui companion table holds one row per language per master record.

This pattern is used by 38+ domain entities across the codebase, spanning both the legacy HMVC layer and the modern Domain/REST layer. Any admin flow that manages translatable content relies on MUI.

Why a shared pattern doc: Rather than repeating the MUI mechanics in every flow document, individual flow docs (AD-02, AD-05, AD-25, etc.) reference this document for the translation lifecycle details.


Architecture

Database Schema Convention

Every MUI-enabled entity follows the same two-table structure:

shop_product_badges          (master table)
  id            PK
  name          language-independent field
  badge         language-independent field

shop_product_badges_mui      (companion table)
  id            PK
  badge_id      FK -> shop_product_badges.id
  badge_text    translated field
  lang          VARCHAR(2), language code (e.g. 'el', 'en')

Naming rules:

  • Companion table name = {master_table}_mui
  • Foreign key column name = {singular_entity}_id (e.g. badge_id, category_id, product_id)
  • The lang column stores ISO 639-1 two-letter language codes
  • Composite index on ({fk_column}, lang) for efficient lookups

Field distribution: Language-independent data (IDs, prices, flags, sort orders, dates, foreign keys, image filenames) stays in the master table. Translated text (names, descriptions, slugs, SEO metadata, URLs) goes in the _mui table.

Entities With Complex MUI Tables

Simple entities like Badges have only one translated field (badge_text). Content-rich entities have many:

EntityMUI Fields
Productname, slug, description, short_description, ingredients, usage, extra_description, meta_title, meta_keywords, meta_description, url, redirect_url, video
Categoryname, slug, fulltext, extra_description, meta_title, meta_keywords, meta_description, url, redirect_url
Blog Articleslug, title, description, small_description, meta_title, meta_keywords, meta_description, url, redirect_url, builder_block_id
Badgebadge_text
Subcontentname, fulltext

Implementation Layers

The MUI pattern is implemented in two parallel stacks that share the same database tables:

                    +-----------------------+
                    |    Database Tables     |
                    |  master + _mui tables  |
                    +-----------+-----------+
                                |
              +-----------------+------------------+
              |                                    |
   Legacy HMVC Layer                    Modern Domain + REST Layer
   (ecommercen/)                        (src/)
              |                                    |
   Adv_base_model                       BaseRepository
   updateOrInsertMui()                  + MuiRepository
   masterWithDetails()                  + MuiWriteRepository
              |                         + MuiWriteData
   Legacy Models                        + MuiEntity
   (per-entity)                         + WriteService
              |                                    |
   Legacy Controllers                   REST Controllers
   (admin CRUD views)                   (JSON API)

Legacy Layer (HMVC)

Base Model Methods

Adv_base_model (ecommercen/core/models/Adv_base_model.php) provides two methods used by all legacy MUI models:

updateOrInsertMui($table, $masterField, $masterValue, $lang, $data) -- Upsert pattern for a single language row. Checks if a row exists for the given master ID + language; updates if found, inserts if not. Used during updates to handle languages being added after initial creation.

masterWithDetails($master, $details) -- Merges a master record with its MUI rows into a single object with a captions property keyed by language abbreviation. Used when loading records for admin editing forms.

Legacy CRUD Lifecycle

Create:

php
// In model (e.g. Adv_categories_model)
function add_record($data, $data_mui) {
    $this->db->insert($this->table, $data);
    $id = $this->db->insert_id();
    foreach ($data_mui as $lang => $q) {
        $q['category_id'] = $id;
        $this->db->insert($this->tableMui, $q);
    }
    return $id;
}

Update:

php
function update_record($data, $data_mui, $id) {
    $this->db->where('id', $id)->update($this->table, $data);
    foreach ($data_mui as $lang => $q) {
        $q['lang'] = $lang;
        $this->updateOrInsertMui($this->tableMui, 'category_id', $id, $lang, $q);
    }
}

Read (admin edit form):

php
function getAdminRecord($id) {
    $master = $this->db->from($this->table)->where('id', $id)->get()->row();
    $muiRecs = $this->db->from($this->tableMui)->where('badge_id', $id)->get()->result();
    foreach ($muiRecs as $row) {
        $master->{$row->lang} = $row;   // Keyed by language on master object
    }
    return $master;
}

Read (storefront list):

php
// JOIN to _mui with current language filter
$this->db->select("{$this->table}.*, {$this->tableMui}.badge_text")
    ->join($this->tableMui, "{$this->table}.id = {$this->tableMui}.badge_id")
    ->where("{$this->tableMui}.lang", $this->languageAbbr);

Delete:

php
function deleteRecord($id) {
    $this->db->delete($this->table, ['id' => $id]);
    $this->db->delete($this->tableMui, ['badge_id' => $id]);
}

Language Configuration

Languages are configured in application/config/languages.php:

Config KeyPurposeExample
language_abbrCurrent request language'el'
lang_uri_abbrAll available storefront languages['el' => 'greek', 'en' => 'english']
lang_descDisplay labels for storefront['el' => 'EL', 'en' => 'EN']
admin_languagesLanguages available in admin panel['el' => 'EL', 'en' => 'EN']

Helper functions:

  • get_languages() -- returns lang_desc (storefront languages)
  • getAdminLanguages() -- returns admin_languages (admin panel languages)
  • config_item('lang_uri_abbr') -- used by the modern layer's HasLocales trait

Modern Domain Layer

File Structure Per Entity

Each MUI-enabled domain entity produces these files (using Badge as example):

src/Domains/Product/Badge/
  Repository/
    Entity.php                    # Master entity
    Repository.php                # Master read repository
    RepositoryConfigurator.php    # Declares 'translations' relation
    MuiEntity.php                 # Translation entity
    MuiRepository.php             # Translation read repository
    WriteRepository.php           # Master write repository
    MuiWriteRepository.php        # Translation write repository
  Service.php                     # Read service
  WriteService.php                # Write service (CRUD with MUI)
  WriteData.php                   # Master field DTO
  MuiWriteData.php                # Translation field DTO
  Validator.php                   # Validation for master + MUI
  ListRequest.php                 # Filter/sort definitions

Entity

The master entity implements FilterTranslation and uses the FiltersTranslation trait to allow extracting a specific language's translation at runtime:

php
class Entity extends BaseEntity implements FilterTranslation
{
    use FiltersTranslation;
    public ?string $repository = Repository::class;
}

The translation() method searches the loaded translations relation by language code:

php
// Get the Greek translation for a product
$greekTranslation = $product->translation('el');
// Returns the MuiEntity for lang='el', or null

MuiEntity

A simple BaseEntity subclass mapping the _mui table columns:

php
/**
 * @property int $id
 * @property int $badge_id
 * @property string $badge_text
 * @property string $lang
 */
class MuiEntity extends BaseEntity
{
    public ?string $repository = MuiRepository::class;
}

MuiRepository (Read)

Extends BaseRepository pointing at the _mui table. Has its own RelationConfigurator -- usually NullRelationConfigurator (no sub-relations), but some entities like Cms\Page have MUI-level relations (e.g. builder):

php
class MuiRepository extends BaseRepository
{
    protected string $table = 'shop_product_badges_mui';
    protected string $entity = MuiEntity::class;

    public function __construct(
        RelationConfigurator $configurator,
        ?CI_DB_query_builder $db = null,
    ) {
        parent::__construct($configurator, $db);
    }
}

The $db = null default triggers the di()->get(CI_DB_query_builder::class) fallback inside BaseRepository::__construct() (src/Domains/Support/Repository/BaseRepository.php:24-28), so subclasses that only declare RelationConfigurator continue to work unchanged -- the change is fully backward-compatible.

RepositoryConfigurator (Relation Wiring)

The master entity's configurator declares the translations relation as ONE_TO_MANY pointing to the MuiRepository:

php
class RepositoryConfigurator implements RelationConfigurator
{
    public function getRelations(): array
    {
        return [
            'translations' => new Relation(
                Relation::ONE_TO_MANY,
                MuiRepository::class,
                'badge_id',             // FK column in the _mui table
                scopableColumn: 'lang'
            ),
        ];
    }
}

This enables ?with=translations (all locales) and ?with=translations[el] (scoped to one locale) on REST API calls, and $repository->get($id, ['translations']) in code. See Per-Relation Language Scoping for the full URL grammar and data-flow details. The scopableColumn: 'lang' named argument opts this relation into locale scoping; relations without it are never filtered regardless of request params.

MuiWriteData (DTO)

Immutable value object for translation input. Accepts both snake_case (DB columns) and camelCase (API) field names. Annotated with OpenAPI for API documentation:

php
class MuiWriteData
{
    public function __construct(
        public readonly string $lang,
        public readonly ?string $badgeText = null
    ) {}

    public static function fromArray(array $data): self { /* dual-format parsing */ }
    public function toArray(bool $excludeNull = false): array { /* snake_case output */ }
}

Category B field handling (null → ''): Some _mui columns are NOT NULL with no DEFAULT value and no required validation — for example, url, image, and descr on slide_mui, or name and fulltext on categories_mui (Cms\Page). When the API consumer omits these fields, the constructor sets them to null. Because BaseWriteRepository::insert() now strips null values (see below), omitting a Category B column would cause a MySQL error. To prevent this, toArray() in the affected MuiWriteData files converts null → '' for these columns:

php
// src/Domains/Slider/Slide/MuiWriteData.php (representative example)
'url'   => $this->url   ?? '',
'image' => $this->image ?? '',
'descr' => $this->descr ?? '',

This matches the legacy admin panel's behavior, where $this->input->post() always returned '' for empty form fields. The nine affected DTOs and their Category B fields are:

FileFields
src/Domains/Slider/Slide/MuiWriteData.phpurl, image, descr
src/Domains/Cms/Page/MuiWriteData.phpname, fulltext
src/Domains/Cms/Document/MuiWriteData.phpfile
src/Domains/Cms/Subcontent/MuiWriteData.phpname, fulltext
src/Domains/Map/Location/MuiWriteData.phptitle
src/Domains/Map/Map/MuiWriteData.phptitle
src/Domains/Product/ProductList/MuiWriteData.phpdescription
src/Domains/Product/Line/MuiWriteData.phpdescription
src/Domains/Product/Promo/MuiWriteData.phpdescription

MuiWriteRepository (Write)

Extends BaseWriteRepository with three standard methods. The FK column name is the only thing that varies between entities:

MethodPurposeStrategy
insertForEntity($id, $translations)Create translations for a new master recordLoop + $this->insert() with FK (null-filtered)
replaceForEntity($id, $translations)Update translations for an existing master recordDELETE all + re-INSERT
deleteForEntity($id)Remove all translations when master is deletedDELETE WHERE fk = id

insertForEntity() delegates to $this->insert() (inherited from BaseWriteRepository) rather than calling $this->db->insert() directly. This means every MUI row insert goes through the null-filtering step in BaseWriteRepository::insert() (see step 6 of the Create flow below). The replaceForEntity strategy (delete-all + re-insert) ensures clean handling of language additions, removals, and field changes without partial-update complexity.

WriteService (CRUD Orchestration)

The WriteService orchestrates master and MUI operations within a database transaction:

Create flow:

1. Parse master fields -> WriteData::fromArray()
2. Validate master fields -> Validator::validateForCreate()
3. Parse translations -> MuiWriteData::fromArray() per language
4. Validate each translation -> Validator::validateTranslations()
5. BEGIN TRANSACTION
6.   INSERT master row -> WriteRepository::insert()
       BaseWriteRepository::insert() strips null values via array_filter()
       before the DB call; MySQL applies column DEFAULTs for omitted columns.
       -> returns $id
7.   INSERT MUI rows -> MuiWriteRepository::insertForEntity($id, $translations)
       Each translation row also goes through BaseWriteRepository::insert(),
       inheriting the same null filtering. Category B fields that are NOT NULL
       with no DEFAULT are coerced to '' in MuiWriteData::toArray() before
       this point (see MuiWriteData section above).
8.   Reload entity with translations -> Repository::get($id, ['translations'])
9. COMMIT
10. Return complete entity

Update flow:

1. Parse master fields -> WriteData::fromArray()
2. Validate master fields -> Validator::validateForUpdate()
3. Parse translations -> MuiWriteData::fromArray() per language
4. Validate each translation -> Validator::validateTranslations()
5. BEGIN TRANSACTION
6.   UPDATE master row (non-null fields only) -> WriteRepository::update()
7.   REPLACE MUI rows -> MuiWriteRepository::replaceForEntity($id, $translations)
8.   Reload entity with translations -> Repository::get($id, ['translations'])
9. COMMIT
10. Return complete entity

Delete flow:

1. BEGIN TRANSACTION
2.   DELETE MUI rows first -> MuiWriteRepository::deleteForEntity($id)
3.   DELETE master row -> WriteRepository::delete($id)
4. COMMIT

Key detail: on update, translations are only replaced if data['translations'] is present in the input. If the key is absent, MUI rows are left untouched. This allows updating only master fields without affecting translations.

Validator

Each entity's Validator has a validateTranslations() method that checks the MUI-specific rules. At minimum, lang is required on every translation:

php
public function validateTranslations(array $translations): void
{
    foreach ($translations as $i => $translation) {
        if (empty($translation->lang)) {
            $errors["translations.$i.lang"] = 'Language is required for each translation.';
        }
    }
}

Entities with slugs or names may add additional required-field checks.


REST API Layer

Resources

Each MUI entity has a MuiResource and MuiCollection pair that transforms DB rows to camelCase JSON:

php
// MuiResource -- transforms one translation row
class MuiResource extends BaseResource {
    protected function resource(): array {
        return [
            'id' => $this->resource->id,
            'badgeId' => $this->resource->badge_id,
            'badgeText' => $this->resource->badge_text ?? '',
            'lang' => $this->resource->lang ?? 'en',
        ];
    }
}

// MuiCollection -- wraps an array of MuiResource
class MuiCollection extends BaseCollection {
    public string $collects = MuiResource::class;
}

Master Resource Integration

The master Resource includes translations via addCollectionToData():

php
class Resource extends BaseResource {
    protected function resource(): array {
        return $this->addRelationToData([
            'id' => $this->resource->id,
            'name' => $this->resource->name,
        ]);
    }

    protected function addRelationToData(array $data): array {
        return $this->addCollectionToData($data, 'translations', MuiCollection::class);
    }
}

Translations are included in the response only when the relation is loaded (via ?with=translations).

API Request/Response Format

Read (GET with translations):

GET /rest/product/badge/1?with=translations

Response:
{
  "success": true,
  "data": {
    "id": 1,
    "name": "New Product",
    "badge": "/files/badges/new.png",
    "translations": [
      {"id": 10, "badgeId": 1, "badgeText": "Νέο Προϊόν", "lang": "el"},
      {"id": 11, "badgeId": 1, "badgeText": "New Product", "lang": "en"}
    ]
  }
}

Create (POST with translations):

POST /rest/product/badge
Content-Type: application/json

{
  "name": "Sale",
  "badge": "sale.png",
  "translations": [
    {"lang": "el", "badgeText": "Εκπτωση"},
    {"lang": "en", "badgeText": "Sale"}
  ]
}

Update (POST with translations):

POST /rest/product/badge/1
Content-Type: application/json

{
  "name": "Updated Name",
  "translations": [
    {"lang": "el", "badgeText": "Updated Greek"},
    {"lang": "en", "badgeText": "Updated English"}
  ]
}

Note: Omitting "translations" from the update payload leaves existing translations untouched. Including it replaces all translations (delete + re-insert).

Per-Relation Language Scoping

A REST caller can request only a subset of locale rows by appending bracket-enclosed locale codes to a relation name. The feature is opt-in at the RepositoryConfigurator level via scopableColumn: 'lang'; relations that do not declare this field are never filtered.

URL grammar (src/Domains/Support/Request/WithParser.php):

URL parameterEffect
?with=translationsLoad all locales (unchanged behavior)
?with=translations[el]Load Greek only
?with=translations[el,en]Load Greek and English (CSV inside brackets)
?with=translations[el],parentScoped translations + unscoped parent (, outside brackets is the relation separator)
?with=children.translations[el]Nested relation; leaf is scoped; relationParams key is the full dot-path children.translations

Opt-in declaration (src/Domains/Support/Repository/Relation.php):

php
public ?string $scopableColumn;

Each RepositoryConfigurator that participates declares scopableColumn: 'lang' on the Relation constructor call (see RepositoryConfigurator (Relation Wiring) above). All 38 translation-bearing configurators follow this pattern.

Parser: WithParser::parse(string $with): array is a static helper. It returns:

php
[
    'relations'      => ['translations' => [], 'parent' => [], ...],
    'relationParams' => ['translations' => ['el'], 'children.translations' => ['el']],
]

The bracket-aware top-level splitter treats , inside [...] as a value separator and , outside as a relation separator.

Data flow through the stack:

  1. URL parsed by WithParser::parse(), called from GenerateListRequest::generateRelationParams() (src/Domains/Support/Request/QueryListBuilder/GenerateListRequest.php)
  2. ListRequest::$relationParams carries the resulting map (src/Domains/Support/Request/QueryListBuilder/ListRequest.php)
  3. HandlesRestfulActions::show() passes $listRequest->relationParams as the 4th argument to Service::get() (src/Rest/Support/Controllers/HandlesRestfulActions.php)
  4. Service::get() forwards it to BaseRepository::get(), which calls loadRelations($resultAsArray, $relations, $relationSorts, '', $relationParams) (src/Domains/Support/Repository/BaseRepository.php)
  5. BaseRepository::loadRelations() extracts $params = $relationParams[$relationPath] ?? [] and forwards to loadRelation() and then to the loader
  6. AbstractRelationLoader::applyScopableParams() (src/Domains/Support/Repository/RelationLoader/AbstractRelationLoader.php): when $params is non-empty and $relation->scopableColumn is set, emits WHERE {table}.{scopableColumn} IN (...) on the related repository's DB builder
  7. The collection in the response contains only the scoped rows; the array shape is unchanged (still an array even for a single locale)

For index() and item() endpoints, HandlesRestfulActions::index() uses $listRequest (which already carries relationParams) → Service::all()buildSpecifications()new WithRelations($relations, $listRequest->getRelationSorts(), $listRequest->relationParams) — the same scoping applies.

For internal callers (jobs, services, legacy bridges) pass $relationParams directly:

php
// Repository::get()
$repo->get($id, ['translations'], [], ['translations' => ['el']]);

// Repository::loadRelations()
$repo->loadRelations($items, ['translations'], [], '', ['translations' => ['el']]);

// WithRelations specification
new WithRelations(['translations' => []], [], ['translations' => ['el']]);

Backward compatibility: an empty $relationParams = [] (the default for all existing callers) produces no IN (...) filter, so all locales are returned — identical behavior to before the feature was introduced.

OpenAPI: all 38 translation-bearing controllers' x-relations entries carry 'scopableColumn' => 'lang'. The canonical documentation is in the central OA\Info ## Relations block in src/Rest/OpenApi.php.

Filtering and Sorting by Translation Fields

The ListRequest class registers per-locale filter and sort entries for MUI fields using the HasLocales trait:

php
// In ListRequest::setAllowedFilters()
foreach ($this->getLocales() as $locale) {
    $this->allowedFilters["name.$locale"] = [
        'relation' => 'translations',
        'field' => 'shop_product_mui.name',
        'type' => FilterRequestType::Partial,
        'language' => $locale,
    ];
}

API usage: GET /rest/product?filter[name.el]=Προϊόν&sort=name.el

Sort by translation: Uses a SortByTranslation specification that LEFT JOINs the _mui table filtered by locale, then orders by the translated field:

php
class SortByTranslation implements Specification {
    public function apply(CI_DB_query_builder $builder) {
        $builder->join("{$muiTable} AS {$alias}", "{$alias}.{$fk} = {$table}.id AND {$alias}.lang = '{$locale}'", 'left');
        $builder->order_by("{$alias}.{$field}", $this->direction);
    }
}

Filter by translation: Uses a FilterByTranslation specification with a subquery:

php
// Generates: WHERE shop_product.id IN (SELECT product_id FROM shop_product_mui WHERE lang = 'el' AND name LIKE '%term%')

DI Container Registration

MUI components are registered in each domain's container.php. The pattern differs based on whether the MuiRepository has its own relations:

Without MUI-level relations (most entities):

php
$services->set(Badge\Repository\MuiRepository::class)
    ->arg('$configurator', new Reference(NullRelationConfigurator::class));
$services->set(Badge\Repository\MuiWriteRepository::class);

With MUI-level relations (e.g. Product, Page):

php
$services->set(Product\Repository\MuiRepositoryConfigurator::class);
$services->set(Product\Repository\MuiRepository::class)
    ->arg('$configurator', new Reference(Product\Repository\MuiRepositoryConfigurator::class));
$services->set(Product\Repository\MuiWriteRepository::class);

Slug and URL Handling

Entities with public-facing URLs (products, categories, blog articles, pages, events, offers, videos) store slug and optionally url and redirect_url in the _mui table. This means each language can have its own URL-friendly slug.

Slug generation is handled by the url_title() helper (application/helpers/MY_url_helper.php), which extends CI's default with full Greek character support -- converting accented Greek characters to their base forms, lowercasing via mb_strtolower, and replacing spaces with hyphens.

Slug generation typically happens in the admin frontend (Vue components or form handlers) rather than in the backend model layer. The backend stores whatever slug value is submitted.


Complete Entity Inventory

All 38 entities currently using the MUI pattern:

ContextEntityMUI TableNotable MUI Fields
AiPositionai_positions_muitext fields
CmsBlog Articleblog_muislug, title, description, SEO
CmsBlog Authorblog_author_muiname, slug, SEO
CmsBlog Categoryblog_category_muiname, slug, SEO
CmsBlog Tagblog_tags_muiname, slug, SEO
CmsDocumentdocuments_muiname, slug
CmsOfferoffers_muiname, slug, description, SEO
CmsOffer Categoryoffers_category_muiname, slug, SEO
CmsPagecategories_muiname, slug, fulltext, SEO
CmsSubcontentcms_subcontent_muiname, fulltext
CmsVideovideo_muiname, slug, description, SEO
CountryCountrycountry_muiname
CountryCountycounty_muiname
EventCategoryevent_categories_muiname, slug, description, SEO
EventEventevents_muiname, slug, description, SEO
MapLocationmaps_locations_muiname, description, address
MapMapmaps_muiname, description
OrderStorestores_muiname, address
ProductAttributeshop_product_attribute_values_muiname
ProductAttribute Groupshop_product_attribute_groups_muiname
ProductBadgeshop_product_badges_muibadge_text
ProductCategoryshop_product_category_muiname, slug, fulltext, SEO
ProductLineshop_product_lines_muiname, slug, SEO
ProductProductshop_product_muiname, slug, description, SEO, usage, ingredients
ProductProduct Listshop_product_lists_muiname, slug, description, SEO
ProductPromoshop_product_promo_muiname, slug, description, SEO
ProductRelated Groupshop_product_related_groups_muiname
ProductSuppliershop_product_supplier_muiname, slug
ProductTagshop_product_tags_muiname, slug
ProductTag Categoryshop_product_tag_categories_muiname, slug
ProductVendorshop_vendor_muiname, vendor_slug, SEO
ProductVariationshop_product_variations_muiname
ProductVariation Groupshop_product_variation_groups_muiname
ProductVariation Valueshop_product_variation_values_muiname
PromotionGiftshop_gift_rules_muititle, description, SEO
SliderSlideslide_muititle, description, link
SliderSlidersliders_group_muiname
TransporterTransportershop_transporters_muiname

Comparison: Legacy vs. Modern

AspectLegacy (HMVC)Modern (Domain/REST)
MUI save strategy (update)updateOrInsertMui() per language (upsert)replaceForEntity() (delete-all + re-insert)
MUI save strategy (create)Loop INSERT per languageinsertForEntity() loop INSERT
Transaction handlingNo explicit transactionstransactional() wrapper
MUI data structure on read$master->{$lang} = $row (keyed by lang)$entity->translations[] array of MuiEntity
Filtering by languageJOIN + WHERE langSubquery or JOIN via Specification
Input format$_POST form data keyed by languageJSON translations[] array
ValidationCI form_validationDedicated Validator class
Slug generationFrontend / url_title() helperFrontend / API consumer

Known Issues & Security Gaps

  • fromArray() duplicate-key lookups in 7 MuiWriteData files — The snake_case fallback to camelCase in fromArray() is silently broken in 7 files: both sides of the ?? operator use the same array key (e.g. $data['url'] ?? $data['url'] ?? null), meaning the camelCase variant never fires. Affected: src/Domains/Slider/Slide/MuiWriteData.php:46-55, src/Domains/Map/Location/MuiWriteData.php:36-38, src/Domains/Map/Map/MuiWriteData.php:30-31, src/Domains/Product/ProductList/MuiWriteData.php:30-31, src/Domains/Product/Line/MuiWriteData.php:41-43, src/Domains/Product/Promo/MuiWriteData.php:42-46, src/Domains/Cms/Document/MuiWriteData.php:30-31. These fields only accept snake_case input from the API despite the dual-format design intent.

References

  • Base Model: ecommercen/core/models/Adv_base_model.php -- updateOrInsertMui(), masterWithDetails()
  • Support Classes: src/Domains/Support/Repository/BaseRepository.php, BaseWriteRepository.php
  • Relation System: src/Domains/Support/Repository/Relation.php, RelationConfigurator.php
  • With/Scoping Parser: src/Domains/Support/Request/WithParser.php -- parse() static helper, bracket-aware grammar
  • REST Relation Helpers: src/Domains/Support/Request/GetsRequestRelations.php -- trait that wires WithParser into REST controllers
  • Translation Filtering: src/Domains/Support/Model/FilterTranslation.php, FiltersTranslation.php
  • Specification Factories: src/Domains/Support/Repository/Specification/NotEmptySpecificationFactory.php
  • REST Support: src/Rest/Support/Resources/BaseResource.php -- addCollectionToData()
  • Language Config: application/config/languages.php
  • Language Helpers: application/helpers/MY_language_helper.php
  • Slug Helper: application/helpers/MY_url_helper.php -- url_title() with Greek support
  • Container Pattern: src/Domains/Product/container.php (reference for MUI DI wiring)