Skip to content

Eager-load translations on non-default locales - #124

Open
AIC-BV wants to merge 3 commits into
wintercms:mainfrom
AIC-BV:feat/eager-load-translations
Open

AIC-BV wants to merge 3 commits into
wintercms:mainfrom
AIC-BV:feat/eager-load-translations

Conversation

@AIC-BV

@AIC-BV AIC-BV commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Problem

TranslatableModel::loadTranslatableData() reads $this->model->translations per record and nothing eager-loads that relation. Any collection read in a non-default locale therefore fires one winter_translate_attributes query per translatable record (categories, tags, variants, settings, ...). On our French webshop listing that was 137 queries against 41 for the same page in the default locale.

The translations morphMany was introduced (rainlab/translate-plugin#504) so callers could ->with('translations'), but that only helps where every query site remembers to do it.

Change

The behavior registers a translatableEagerLoad global scope in its constructor. At query time it adds with('translations') when the active locale differs from the default:

$model::addGlobalScope('translatableEagerLoad', function ($query) {
    $translator = Translator::instance();
    if ($translator->getLocale() !== $translator->getDefaultLocale()) {
        $query->with('translations');
    }
});

Evaluating the locale inside the scope (rather than at construct time) keeps mid-request locale switches correct, e.g. mail rendered from a queue in another locale.

Opt out per model with public $translatableEagerLoad = false;, or per query with ->withoutGlobalScope('translatableEagerLoad').

Impact

  • Default locale: untouched. isTranslatable() already returns false there, so translations is never read and nothing extra is loaded.
  • Non-default locales: one extra query per collection instead of one per record. Same listing as above went from 137 to 92 queries; rendered output is byte-identical.
  • Aggregates (count() etc.) don't run eager loads, so they are unaffected.

Tests

testTranslationsAreEagerLoadedOnNonDefaultLocale asserts translated values with 2 queries in fr, 1 in en, and 4 with the scope removed. It seeds its own fr locale so it does not depend on test order.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Translations are now loaded automatically when viewing content in a non-default locale.
    • This behavior can be disabled for individual models or queries when needed.
    • Content in the default locale continues to load without additional translation queries.
  • Documentation

    • Added guidance for automatic translation loading and available opt-out options.
  • Tests

    • Added coverage verifying translated content and query behavior across locales.

loadTranslatableData reads $model->translations per record and nothing
eager-loads it, so any collection read in a non-default locale fires one
winter_translate_attributes query per record.

Register a `translatableEagerLoad` global scope from the behavior that
adds with('translations') whenever the active locale differs from the
default. The default locale never reads translations (isTranslatable
short-circuits), so it stays untouched. Opt out per model with
$translatableEagerLoad = false or per query with withoutGlobalScope().

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: af87ebf6-e80b-4d8c-ba2a-bfea1217e6d5

📥 Commits

Reviewing files that changed from the base of the PR and between e018a26 and 780c661.

📒 Files selected for processing (3)
  • README.md
  • behaviors/TranslatableModel.php
  • tests/unit/behaviors/TranslatableModelTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • behaviors/TranslatableModel.php
  • tests/unit/behaviors/TranslatableModelTest.php

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

TranslatableModel now registers the translatableEagerLoad global scope. When the active locale differs from the default locale, the scope eager-loads translations. Models and queries can disable this behavior. Documentation describes the options. Tests verify translated names, fallback names, and query counts.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 780c6

The locale-conditional eager loading, documented opt-outs, and covered query behavior are consistent and ready to merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 2 files. (1 skipped: 1… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: eager-loading translations for non-default locales.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 9 functions across 2 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Some tools did not complete. Review the errors below.

🔧 PHPStan (2.2.12)

Composer install failed: dependency resolution error. Check composer.json and composer.lock for version constraints.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mjauvin

mjauvin commented Sep 16, 2026

Copy link
Copy Markdown
Member

@AIC-BV translatableEagerLoad needs to be documented.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AIC-BV

AIC-BV commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

@mjauvin Done in 780c661. Added an "Eager loading translations" section to the README, next to "Fallback attribute values", covering what the scope does and both ways to opt out ($translatableEagerLoad = false on the model, withoutGlobalScope('translatableEagerLoad') per query).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants