Translate

class Cake\ORM\Behavior\TranslateBehavior

翻译行为允许您以多种语言创建和检索实体的翻译副本。

警告

TranslateBehavior目前不支持复合主键。

翻译策略

该行为提供了两种关于如何存储翻译的策略。

  1. 影子表策略:这种策略为每个Table对象使用一个单独的“影子表”来存储该表所有翻译字段的翻译。这是默认策略。

  2. Eav策略:这种策略使用一个i18n表,在该表中存储任何给定Table对象的每个字段的翻译,该对象与它绑定。

影子表策略

假设我们有一个articles表,我们希望它的titlebody字段被翻译。为此,我们创建了一个影子表articles_translations

CREATE TABLE `articles_translations` (
    `id` int(11) NOT NULL,
    `locale` varchar(5) NOT NULL,
    `title` varchar(255),
    `body` text,
    PRIMARY KEY (`id`,`locale`)
);

影子表需要idlocale列,它们一起构成主键,以及其他与主表同名的需要翻译的列。

关于语言缩写的说明:翻译行为对语言标识符没有限制,可能的取值仅受locale列类型/大小的限制。 locale在您想要使用像es-419(拉丁美洲西班牙语,带有地区代码的语言缩写UN M.49)这样的缩写时被定义为varchar(6)

提示

明智的做法是使用与国际化和本地化所需的相同语言缩写。这样您就可以保持一致,并且语言切换对于Translate行为和Internationalization and Localization都具有相同的效果。

因此,建议使用语言的两位字母ISO代码(例如enfrde)或完整的区域设置名称(例如fr_FRes_ARda_DK),其中包含语言和使用该语言的国家/地区。

Eav策略

为了使用Eav策略,您需要使用正确的模式创建一个i18n表。目前,加载i18n表的唯一方法是手动在您的数据库中运行以下SQL脚本

CREATE TABLE i18n (
    id int NOT NULL auto_increment,
    locale varchar(6) NOT NULL,
    model varchar(255) NOT NULL,
    foreign_key int(10) NOT NULL,
    field varchar(255) NOT NULL,
    content text,
    PRIMARY KEY     (id),
    UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field),
    INDEX I18N_FIELD(model, foreign_key, field)
);

该模式也以SQL文件的形式存在于/config/schema/i18n.sql中。

将翻译行为附加到您的表格

可以在表格类中的initialize()方法中附加该行为

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        // By default ShadowTable will be used.
        $this->addBehavior('Translate', ['fields' => ['title', 'body']]);
    }
}

对于影子表策略,指定fields键是可选的,因为该行为可以从影子表的列推断出字段。

如果您想使用EavStrategy,则可以将该行为配置为

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'strategyClass' => \Cake\ORM\Behavior\Translate\EavStrategy::class,
            'fields' => ['title', 'body'],
        ]);
    }
}

对于EavStrategy,您需要在配置数组中传递fields键。这个字段列表是告诉该行为哪些列可以存储翻译所需的。

默认情况下,App.defaultLocale配置中指定的区域设置用作TranslateBehavior的默认区域设置。您可以通过设置该行为的defaultLocale配置来覆盖它

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'defaultLocale' => 'en_GB',
        ]);
    }
}

快速浏览

无论您选择哪种数据结构策略,该行为都提供相同的API来管理翻译。

现在,通过更改应用程序语言来选择用于检索实体的语言,这将影响所有翻译

// In the Articles controller. Change the locale to Spanish, for example
I18n::setLocale('es');

然后,获取一个现有实体

$article = $this->Articles->get(12);
echo $article->title; // Echoes 'A title', not translated yet

接下来,翻译您的实体

$article->title = 'Un Artículo';
$this->Articles->save($article);

现在您可以尝试再次获取您的实体

$article = $this->Articles->get(12);
echo $article->title; // Echoes 'Un Artículo', yay piece of cake!

通过在实体类中使用一个特殊的特质来处理多个翻译

use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;

class Article extends Entity
{
    use TranslateTrait;
}

现在您可以找到单个实体的所有翻译

$article = $this->Articles->find('translations')->first();
echo $article->translation('es')->title; // 'Un Artículo'

echo $article->translation('en')->title; // 'An Article';

并一次保存多个翻译

$article->translation('es')->title = 'Otro Título';
$article->translation('fr')->title = 'Un autre Titre';
$this->Articles->save($article);

如果您想深入了解它是如何工作的或如何为您的需求调整该行为,请继续阅读本章的剩余部分。

为Eav策略使用单独的翻译表

如果您希望使用除i18n之外的表格来翻译特定存储库,您可以在该行为的配置中指定自定义表格的表格类名。当您有多个表格需要翻译,并且希望更清晰地分离为每个不同表格存储的数据时,这很常见

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'fields' => ['title', 'body'],
            'translationTable' => 'ArticlesI18n',
        ]);
    }
}

您需要确保使用的任何自定义表格都具有列fieldforeign_keylocalemodel

读取翻译内容

如上所示,您可以使用setLocale()方法来选择要加载的实体的活动翻译

// Load I18n core functions at the beginning of your Articles Controller:
use Cake\I18n\I18n;

// Then you can change the language in your action:
I18n::setLocale('es');

// All entities in results will contain spanish translation
$results = $this->Articles->find()->all();

此方法适用于表格中的任何查找器。例如,您可以将TranslateBehavior与find('list')一起使用

I18n::setLocale('es');
$data = $this->Articles->find('list')->toArray();

// Data will contain
[1 => 'Mi primer artículo', 2 => 'El segundo artículo', 15 => 'Otro articulo' ...]

// Change the locale to french for a single find call
$data = $this->Articles->find('list', locale: 'fr')->toArray();

检索实体的所有翻译

在构建用于更新翻译内容的界面时,同时显示一个或多个翻译通常很有用。您可以使用translations查找器来实现此目的

// Find the first article with all corresponding translations
$article = $this->Articles->find('translations')->first();

在上面的示例中,您将获得一个实体列表,这些实体都设置了_translations属性。此属性将包含一个翻译数据实体列表。例如,以下属性可以访问

// Outputs 'en'
echo $article->_translations['en']->locale;

// Outputs 'title'
echo $article->_translations['en']->field;

// Outputs 'My awesome post!'
echo $article->_translations['en']->body;

处理此数据的更优雅方法是在用于表格的实体类中添加一个特质

use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;

class Article extends Entity
{
    use TranslateTrait;
}

这个特质包含一个名为translation的方法,该方法允许您随时访问或创建新的翻译实体

// Outputs 'title'
echo $article->translation('en')->title;

// Adds a new translation data entity to the article
$article->translation('de')->title = 'Wunderbar';

限制要检索的翻译

您可以限制从数据库中为特定记录集获取的语言

$results = $this->Articles->find('translations', locales: ['en', 'es']);
$article = $results->first();
$spanishTranslation = $article->translation('es');
$englishTranslation = $article->translation('en');

防止检索空翻译

翻译记录可以包含任何字符串,如果记录被翻译并存储为空字符串(''),则翻译行为将获取并使用它来覆盖原始字段值。

如果这是不希望的,您可以使用allowEmptyTranslations配置键忽略空翻译

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'fields' => ['title', 'body'],
            'allowEmptyTranslations' => false
        ]);
    }
}

以上操作将只加载具有内容的翻译数据。

检索关联的所有翻译

还可以通过一次查找操作找到任何关联的翻译

$article = $this->Articles->find('translations')->contain([
    'Categories' => function ($query) {
        return $query->find('translations');
    }
])->first();

// Outputs 'Programación'
echo $article->categories[0]->translation('es')->name;

这假设Categories已附加TranslateBehavior。它只是使用关联的contain子句的查询构建器函数来使用translations自定义查找器。

在不使用I18n::setLocale的情况下检索一种语言

调用 I18n::setLocale('es'); 会更改所有翻译查找的默认语言环境,在某些情况下您可能希望在不修改应用程序状态的情况下检索翻译后的内容。对于这些情况,请使用行为的 setLocale() 方法

I18n::setLocale('en'); // reset for illustration

// specific locale.
$this->Articles->setLocale('es');

$article = $this->Articles->get(12);
echo $article->title; // Echoes 'Un Artículo', yay piece of cake!

请注意,这只会更改文章表的语言环境,不会影响关联数据的语言。要影响关联数据,需要在每个表上调用该方法,例如

I18n::setLocale('en'); // reset for illustration

$this->Articles->setLocale('es');
$this->Articles->Categories->setLocale('es');

$data = $this->Articles->find('all', contain: ['Categories']);

此示例还假设 Categories 附加了 TranslateBehavior。

查询翻译字段

TranslateBehavior 默认情况下不会替换查找条件。您需要使用 translationField() 方法来组合翻译字段的查找条件

$this->Articles->setLocale('es');
$query = $this->Articles->find()->where([
    $this->Articles->translationField('title') => 'Otro Título'
]);

以其他语言保存

TranslateBehavior 背后的理念是,您有一个表示默认语言的实体,以及可以覆盖该实体中某些字段的多个翻译。牢记这一点,您可以直观地保存任何给定实体的翻译。例如,假设以下设置

// in src/Model/Table/ArticlesTable.php
class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', ['fields' => ['title', 'body']]);
    }
}

// in src/Model/Entity/Article.php
class Article extends Entity
{
    use TranslateTrait;
}

// In the Articles Controller
$article = new Article([
    'title' => 'My First Article',
    'body' => 'This is the content',
    'footnote' => 'Some afterwords'
]);

$this->Articles->save($article);

因此,在保存第一篇文章后,现在可以为其保存翻译,有两种方法可以做到。第一种方法是将语言直接设置到实体中

$article->_locale = 'es';
$article->title = 'Mi primer Artículo';

$this->Articles->save($article);

保存实体后,翻译字段也将被持久化,需要注意的是,未被覆盖的默认语言值将被保留

// Outputs 'This is the content'
echo $article->body;

// Outputs 'Mi primer Artículo'
echo $article->title;

一旦覆盖了值,该字段的翻译将被保存,并可以像往常一样检索

$article->body = 'El contendio';
$this->Articles->save($article);

在其他语言中保存实体的第二种方法是将默认语言直接设置为表

$article->title = 'Mi Primer Artículo';

$this->Articles->setLocale('es');
$this->Articles->save($article);

在表中直接设置语言对于需要检索和保存相同语言的实体或需要一次保存多个实体时很有用。

保存多个翻译

能够同时为任何数据库记录添加或编辑多个翻译是一个常见的要求。可以使用 TranslateTrait 来实现这一点

use Cake\ORM\Behavior\Translate\TranslateTrait;
use Cake\ORM\Entity;

class Article extends Entity
{
    use TranslateTrait;
}

现在,您可以在保存翻译之前填充它们

$translations = [
    'fr' => ['title' => "Un article"],
    'es' => ['title' => 'Un artículo'],
];

foreach ($translations as $lang => $data) {
    $article->translation($lang)->set($data, ['guard' => false]);
}

$this->Articles->save($article);

并为翻译字段创建表单控件

// In a view template.
<?= $this->Form->create($article); ?>
<fieldset>
    <legend>French</legend>
    <?= $this->Form->control('_translations.fr.title'); ?>
    <?= $this->Form->control('_translations.fr.body'); ?>
</fieldset>
<fieldset>
    <legend>Spanish</legend>
    <?= $this->Form->control('_translations.es.title'); ?>
    <?= $this->Form->control('_translations.es.body'); ?>
</fieldset>

在您的控制器中,您可以像往常一样编组数据

$article = $this->Articles->newEntity($this->request->getData());
$this->Articles->save($article);

这将导致您的文章、法语和西班牙语翻译都被持久化。您需要记住将 _translations 添加到实体的 $_accessible 字段中。

验证翻译后的实体

TranslateBehavior 附加到模型时,您可以在行为在 newEntity()patchEntity() 期间创建/修改翻译记录时定义要使用的验证器

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Translate', [
            'fields' => ['title'],
            'validator' => 'translated',
        ]);
    }
}

以上将使用 validationTranslated 创建的验证器来验证翻译后的实体。