PHPackages                             ad7six/shadow-translate - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. ad7six/shadow-translate

AbandonedArchivedCakephp-plugin[Database &amp; ORM](/categories/database)

ad7six/shadow-translate
=======================

Manage translated content with shadow tables

1.0.1(6y ago)4167.6k↓29.2%8MITPHP

Since Feb 27Pushed 3y ago6 watchersCompare

[ Source](https://github.com/AD7six/cakephp-shadow-translate)[ Packagist](https://packagist.org/packages/ad7six/shadow-translate)[ RSS](/packages/ad7six-shadow-translate/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (8)Dependencies (5)Versions (15)Used By (0)

Note:
=====

[](#note)

As of CakePHP 4.0 the ability to use shadow tables for storing translated content is available through core's `Translate` behavior itself. Check the CakePHP [manual](https://book.cakephp.org/4/en/orm/behaviors/translate.html#shadow-table-strategy) for more details.

Shadow translate #ARCHIVED#
===========================

[](#shadow-translate-archived)

[![Build Status](https://camo.githubusercontent.com/12d4cfd1d95b2a0085679c8d4d44934101b87d6283257579933980f056bb8160/68747470733a2f2f696d672e736869656c64732e696f2f7472617669732f4144377369782f63616b657068702d736861646f772d7472616e736c6174652f6d61737465722e7376673f7374796c653d666c61742d737175617265)](https://travis-ci.org/AD7six/cakephp-shadow-translate)[![Coverage Status](https://camo.githubusercontent.com/1edd914432c558051e642b25e4a317c4e7520a4ff1513baeda32de5da0540472/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f4144377369782f63616b657068702d736861646f772d7472616e736c6174652e7376673f7374796c653d666c61742d737175617265)](https://codecov.io/github/AD7six/cakephp-shadow-translate)[![Total Downloads](https://camo.githubusercontent.com/f3c6719475e8e4e60197dfb635766a23382d21f8aa15e2c19f66887161a12c3f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6164377369782f736861646f772d7472616e736c6174652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/ad7six/shadow-translate)[![License](https://camo.githubusercontent.com/942e017bf0672002dd32a857c95d66f28c5900ab541838c6c664442516309c8a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e7376673f7374796c653d666c61742d737175617265)](LICENSE.txt)

This plugin uses a shadow table for translated content, instead of the core's more flexible (but also potentially quite inefficient) EAV-style translate behavior. The shadow translate behavior is designed to have the same API as the core's translate behavior making it a drop-in replacement in terms of usage.

Quickstart
----------

[](#quickstart)

First install the plugin for your app using composer:

`php composer.phar require ad7six/shadow-translate`

Load the plugin by adding following statement to your app's `config/bootstrap.php`:

```
Plugin::load('ShadowTranslate');
```

The shadow translate behavior expects each table to have its own translation table. Taking the blog tutorial as a start point, the following table would already exist:

```
CREATE TABLE posts (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(50),
    body TEXT,
    created DATETIME DEFAULT NULL,
    modified DATETIME DEFAULT NULL
);
```

To prepare for using the shadow translate behavior, the following table would be created:

```
CREATE TABLE posts_translations (
    id INT UNSIGNED,
    locale VARCHAR(5),
    title VARCHAR(50) DEFAULT NULL,
    body TEXT DEFAULT NULL,
    PRIMARY KEY (id, locale)
);
```

Note that the id is the same type as the posts table - but the primary key is a compound key using both id and locale.

Usage is very similar to the core's behavior so e.g.:

```
class PostsTable extends Table {

    public function initialize(array $config) {
        $this->addBehavior('ShadowTranslate.ShadowTranslate');
    }
}
```

You can specify the fields in the translation table - but if you don't they are derived from the translation table schema. From this point forward, see [the documentation for the core translate behavior](http://book.cakephp.org/3.0/en/orm/behaviors/translate.html), the shadow translate behavior should act the same, and if it doesn't, well, see below.

Why use Shadow Translate
------------------------

[](#why-use-shadow-translate)

The standard translate behavior uses an [EAV](https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model)style translations table, and one join per field. By default all translations are stored in [the same translation table](https://github.com/cakephp/app/blob/master/config/schema/i18n.sql). To give an example, the core translation behavior generates sql of the form:

```
SELECT
    posts.*,
    posts_title_translations.title,
    posts_title_translations.content,
    etc.
FROM
    posts
LEFT JOIN
    i18n as posts_title_translations ON (
        posts_title_translations.locale = "xx" AND
        posts_title_translations.model = "Posts" AND
        posts_title_translations.foreign_key = posts.id AND
        posts_title_translations.field = 'title'
   )
LEFT JOIN
    i18n as posts_body_translations ON (
        posts_body_translations.locale = "xx" AND
        posts_body_translations.model = "Posts" AND
        posts_body_translations.foreign_key = posts.id AND
        posts_body_translations.field = 'body'
   )
etc.
```

There is very little setup for the core translate behavior, but the cost for no-setup is sql complexity, and it is more complex for each translated field. Depending on how much data there is being translated - it's quite possible for this data structure to cause slow queries; it also complicates finding records by translated field values.

Key points:

- Easy to setup
- All translated content stored in the same table
- One join per translated field when querying
- Less efficient queries - more joins and one index for all content
- Harder to find by translated content

By contrast, the shadow translate behavior does not use an EAV style translation table, the translations are stored in a *copy* of the main data table. This permits much less complex sql at the cost of having *some* setup steps per table. The shadow translate behavior generates sql of the form:

```
SELECT
    posts.*,
    posts_translations.*
FROM
    posts
LEFT JOIN
    posts_translations ON (
        posts_translations.locale = "xx" AND
        posts_translations.id = posts.id
)
// no etc.
```

Key points:

- (Slightly) more work to setup
- Translated content is stored in a copy of the main data table
- One join per translated table
- More efficient queries - less joins and indexes per translated field are possible
- Easier to find by translated content

Roadmap
-------

[](#roadmap)

The initial release is only the behavior, it is planned for the future to add:

- A shell to create shadow tables (migration based)
- A shell to import from the core's i18n table
- A shell to export to the core's i18n table

Bugs
----

[](#bugs)

Most likely!

If you happen to stumble upon a bug, please feel free to create a pull request with a fix (optionally with a test), and a description of the bug and how it was resolved.

You can also create an issue with a description to raise awareness of the bug.

Support / Questions
-------------------

[](#support--questions)

If you have a problem, if no one else can help, and if you can find them, maybe you can find help on IRC in the #FriendsOfCake channel.

###  Health Score

42

—

FairBetter than 90% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity42

Moderate usage in the ecosystem

Community19

Small or concentrated contributor base

Maturity71

Established project with proven stability

 Bus Factor1

Top contributor holds 69.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~143 days

Recently: every ~242 days

Total

13

Last Release

2377d ago

Major Versions

0.6.2 → 1.0.02018-04-29

### Community

Maintainers

![](https://www.gravatar.com/avatar/64bf8425f779c4ff29be15dbccca72c7753719e7a3eca4c24cf5ca4ec515e695?d=identicon)[AD7six](/maintainers/AD7six)

---

Top Contributors

[![AD7six](https://avatars.githubusercontent.com/u/33387?v=4)](https://github.com/AD7six "AD7six (152 commits)")[![ADmad](https://avatars.githubusercontent.com/u/142658?v=4)](https://github.com/ADmad "ADmad (55 commits)")[![greew](https://avatars.githubusercontent.com/u/189321?v=4)](https://github.com/greew "greew (8 commits)")[![burzum](https://avatars.githubusercontent.com/u/162789?v=4)](https://github.com/burzum "burzum (1 commits)")[![joshk](https://avatars.githubusercontent.com/u/8701?v=4)](https://github.com/joshk "joshk (1 commits)")[![Noodlex](https://avatars.githubusercontent.com/u/14205390?v=4)](https://github.com/Noodlex "Noodlex (1 commits)")[![segy](https://avatars.githubusercontent.com/u/1355459?v=4)](https://github.com/segy "segy (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/ad7six-shadow-translate/health.svg)

```
[![Health](https://phpackages.com/badges/ad7six-shadow-translate/health.svg)](https://phpackages.com/packages/ad7six-shadow-translate)
```

###  Alternatives

[doctrine/orm

Object-Relational-Mapper for PHP

10.2k285.3M6.2k](/packages/doctrine-orm)[josegonzalez/cakephp-upload

CakePHP plugin to handle file uploading sans ridiculous automagic

5451.3M9](/packages/josegonzalez-cakephp-upload)[muffin/trash

Adds soft delete support to CakePHP ORM tables.

851.3M11](/packages/muffin-trash)[muffin/webservice

Simplistic webservices for CakePHP

88191.0k13](/packages/muffin-webservice)[admad/cakephp-sequence

Sequence plugin for CakePHP to maintain ordered list of records

46489.9k6](/packages/admad-cakephp-sequence)[riesenia/cakephp-duplicatable

CakePHP ORM plugin for duplicating entities (including related entities)

51384.5k4](/packages/riesenia-cakephp-duplicatable)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
