CakePHP 3:Paginator按语言排序(具有i18n翻译行为)

时间:2015-12-23 00:05:57

标签: sorting cakephp pagination cakephp-3.1 cakephp-3.x

我有一个包含所有条目的表格,包括多种语言的所有翻译:如何在翻译的字段上创建分页排序链接? (蛋糕3.1.6)

摘要:这不起作用,我无法通过这种方式对翻译进行排序:

$this->Paginator->sort('_translations.es.title', 'Spanish')

长版:

| Title ENGLISH    | Title SPANISH    | Title GERMAN    |  = pagination sort links
| ---------------- | ---------------- | --------------- |
| Christmas        | Navidad          | Weihnachten     |
| Spring           | Primavera        | Frühling        |
| ...

所以这是我简化的测试设置:

文章只有一个要翻译的字段title i18n 表默认设置为described in the book

烘焙/src/Model/Table/ArticlesTable.php,添加翻译行为

public function initialize(array $config) {
  // ... default config (removed in this post to simplify code)
  $this->addBehavior('Translate', ['fields' => ['title']]);  // added this line
}

Baked 实体/src/Model/Entity/Article.php,添加 TranslateTrait

namespace App\Model\Entity;
use Cake\ORM\Behavior\Translate\TranslateTrait; // added this line
use Cake\ORM\Entity;
class Article extends Entity {
  protected $_accessible = [
    '*' => true,
    'id' => false,
  ];
  use TranslateTrait; // added this line
}

Baked 控制器 /src/Controller/ArticlesController.php,修改如下:

namespace App\Controller;
use App\Controller\AppController;
class ArticlesController extends AppController {
  public $paginate = [
    'sortWhitelist' => [  // Allow pagination sort on this fields:
      'title',
      '_translations.es.title',
      '_translations.de.title'
    ]
  ];
  public function index() {
    $query = $this->Articles->find('translations'); // Retrieve All Translations
    $this->set('articles', $this->paginate($query));
  }
}

查看 /src/Template/Articles/index.ctp,修改/简化:

<table>
  <tr>
    <th><?= $this->Paginator->sort('title', 'English') ?></th>
    <th><?= $this->Paginator->sort('_translations.es.title', 'Spanish') ?></th>
    <th><?= $this->Paginator->sort('_translations.de.title', 'German') ?></th>
  </tr>
<?php foreach ($articles as $article): ?>
  <tr>
    <td><?= h($article->title) ?></td>
    <td><?= h($article->_translations['es']->title) ?></td>
    <td><?= h($article->_translations['de']->title) ?></td>
  </tr>
<?php endforeach; ?>
</table>

表中的翻译正确显示,但无法按翻译的字段排序。当我点击翻译的分页链接时,我收到以下错误:

  

SQLSTATE [42S22]:未找到列:1054'order clause'中的未知列'_translations.es'

SQL Query:
SELECT Articles.id AS `Articles__id`,
       Articles.title AS `Articles__title`,
       Articles.created AS `Articles__created`,
       Articles.modified AS `Articles__modified`
FROM articles Articles 
ORDER BY _translations.es asc LIMIT 20 OFFSET 0

this similar question中使用格式Posts_title_translation.content - 我不知道它来自何处,但我也是这样尝试的(当然我还添加了字段名称的变体) paginator whitelist):

$this->Paginator->sort('Articles_title_translation', 'Spanish')
$this->Paginator->sort('Articles_title_translation.es', 'Spanish')
$this->Paginator->sort('Articles_title_translation.content', 'Spanish')
$this->Paginator->sort('Articles_title_translation.es.content', 'Spanish')

他们都没有工作......(显然)

如何通过标题字段的i18n翻译对表项进行排序?

1 个答案:

答案 0 :(得分:2)

对翻译的字段进行排序需要连接

通常,您只能对加入主查询的字段进行排序!

已翻译的字段仅与非默认的当前区域设置

一起加入

默认情况下,只有当前区域设置(I18n::locale())与默认区域设置(I18N::defaultLocale()intl.default_locale)不匹配时才会加入已翻译的字段,即实际存在时需要翻译一些东西。

将当前区域设置更改为非默认值

I18n::locale('de');
$query = $this->Articles->find('translations');
// ...

翻译行为将包含与翻译内容的关联,这是TableAlias_field_translation别名源自的地方,行为使用该命名方案为每个翻译字段创建hasOne关联。

然后可以将这些字段用于分页,但这一次只会加入一个区域设置!

确保paginator

使用了正确的字段

由于并不总是包含关联,因此您必须采取适当的措施来确保分页器根据区域设置使用正确的字段。这样的事情(请注意,这只是用于说明目的的未经测试的示例代码)

public function index()
{
    $sort = $this->request->query('sort');
    if ($sort) {
        $fieldMap = [
            'Articles_title_translation.content' => 'Articles.title'
        ];
        if (
            isset($fieldMap[$sort]) &&
            $this->Articles->locale() ===
                $this->Articles->behaviors()->get('Translate')->config('defaultLocale')
        ) {
            $this->request->query['sort'] = $fieldMap[$sort];
        }
    }

    $query = $this->Articles->find('translations');
    $this->set('articles', $this->paginate($query));
}

,如果不包含翻译,将映射字段以从翻译字段到原始字段进行排序。

加入并排序字段的所有翻译/语言

以上内容适用于单个字段的排序,可能会或可能不会被翻译。对可用于排序的字段的所有翻译超出了翻译行为范围。虽然行为 加载所有翻译,但它通过使用hasMany关联,即使用单独的查询来实现,因此它们不能用于排序。加入所有翻译需要手动完成。

这可能是功能请求的内容,我不确定如果这是一个可以证明此类核心修改的常见用例,您可能希望在GitHub上打开问题或询问IRC

话虽如此,这是一个基本的例子,一个扩展的翻译行为,它几乎完成了TranslateBehavior::setupFieldAssociations()TranslateBehavior::beforeFind()所做的事情,只是稍作修改。该行为采用locales选项,需要为所有应加入的区域设置提供,因为它们无法自动计算。

<强>的src /型号/表/ ArticlesTable.php

// Remove $this->addBehavior('Translate', ['fields' => ['title']]);
// and load the custom behavior instead (otherwise there will be an
// error about "duplicate translation finders"

$this->addBehavior('MyTranslate', [
    'fields' => ['title'],
    'locales' => ['es', 'de']
]);

<强>的src /型号/行为/ MyTranslateBehavior.php

namespace App\Model\Behavior;

use Cake\ORM\Behavior\TranslateBehavior;
use Cake\ORM\Query;
use Cake\ORM\Table;

class MyTranslateBehavior extends TranslateBehavior
{
    protected $_associations = [];

    public function __construct(Table $table, array $config)
    {
        $config += [
            'locales' => []
        ];

        parent::__construct($table, $config);
    }

    public function setupFieldAssociations($fields, $table, $model, $strategy)
    {
        parent::setupFieldAssociations($fields, $table, $model, $strategy);

        $alias = $this->_table->alias();
        $tableLocator = $this->tableLocator();
        $locales = $this->config('locales');

        $this->_associations = [];
        foreach ($fields as $field) {
            foreach ($locales as $locale) {
                $name = $alias . '_' . $field . '_translation_' . $locale;

                if (!$tableLocator->exists($name)) {
                    $fieldTable = $tableLocator->get($name, [
                        'className' => $table,
                        'alias' => $name,
                        'table' => $this->_translationTable->table()
                    ]);
                } else {
                    $fieldTable = $tableLocator->get($name);
                }

                $conditions = [
                    $name . '.locale' => $locale,
                    $name . '.model' => $model,
                    $name . '.field' => $field
                ];

                $this->_table->hasOne($name, [
                    'targetTable' => $fieldTable,
                    'foreignKey' => 'foreign_key',
                    'joinType' => 'LEFT',
                    'conditions' => $conditions,
                    'propertyName' => $field . '_translation_' . $locale
                ]);

                $this->_associations[] = $name;
            }
        }
    }

    public function findTranslations(Query $query, array $options)
    {
        $query->contain($this->_associations);
        return parent::findTranslations($query, $options);
    }
}

这应该相对容易理解,它只是为所有已配置的语言环境中的所有字段创建和包含hasOne关联。别名将使用格式TableAlias_field_translation_locale,例如Articles_title_translation_es,这是需要在排序白名单中使用的格式,以及分页器排序链接。

应该注意在加入新字段时可能需要默认排序顺序,因为它可能会对查询翻译时使用的子查询进行排序,而不是主查询,导致检索到错误的翻译!

public $paginate = [
    'order' => ['Articles.title' => 'ASC']
    'sortWhitelist' => [
        'Articles.title',
        'Articles_title_translation_de.content',
        'Articles_title_translation_es.content'
    ]
];
$this->Paginator->sort('Articles.title', 'English');
$this->Paginator->sort('Articles_title_translation_es.content', 'Spanish');
$this->Paginator->sort('Articles_title_translation_de.content', 'German');

另见