CakePHP 3.0.8翻译行为和数据验证(Requirementence,notEmpty)

CakePHP 3.0.8翻译行为和数据验证(Requirementence,notEmpty),php,validation,cakephp,internationalization,cakephp-3.0,Php,Validation,Cakephp,Internationalization,Cakephp 3.0,我的问题很简单,但我不知道如何解决它 我的网站是多语言的。我希望用户能够添加多种语言的文章,如果他愿意的话,同时需要输入他的语言(取决于他的语言环境) 问题是,根据CakePHP关于翻译的约定,所有输入都必须以字段名称结尾,不管是哪种语言。因此,所有字段对同一字段都有相同的规则。我不能让一个“名字”是必需的,而另一个用另一种语言的则不是必需的 例如,默认语言的输入为: <input type="text" name="name" required="required" maxlength=

我的问题很简单,但我不知道如何解决它

我的网站是多语言的。我希望用户能够添加多种语言的文章,如果他愿意的话,同时需要输入他的语言(取决于他的语言环境)

问题是,根据CakePHP关于翻译的约定,所有输入都必须以字段名称结尾,不管是哪种语言。因此,所有字段对同一字段都有相同的规则。我不能让一个“名字”是必需的,而另一个用另一种语言的则不是必需的

例如,默认语言的输入为:

<input type="text" name="name" required="required" maxlength="45" id="name">
注意:我必须在保存时将区域设置更改为默认设置(en_US),以便能够以多种语言+默认语言进行保存(否则,默认输入将保存在默认表和i18n表中)

编辑:下面是我保存时的完整代码(IngredientsController.php)

我设置的默认区域设置是bootstrap.php

/**
 * Set the default locale. This controls how dates, number and currency is
 * formatted and sets the default language to use for translations.
 */
ini_set('intl.default_locale', 'en_US');
Configure::write('Config.locales', ['fr_CA']);
我在AppController.php中确定用户的区域设置

public function add() {
    $ingredient = $this->Ingredients->newEntity();
    if ($this->request->is('post')) {
        $ingredient = $this->Ingredients->patchEntity($ingredient, $this->request->data);

        if(isset($this->request->data['locales'])) {
            foreach ($this->request->data['locales'] as $lang => $data) {
                $ingredient->translation($lang)->set($data, ['guard' => false]);
            }
        }

        $locale = I18n::locale(); // At this point the locale is fr_CA (not de default)
        I18n::locale('en_US'); // Change the locale to the default

        if ($this->Ingredients->save($ingredient)) {
            $this->Flash->success(__('The ingredient has been saved.'));
            I18n::locale($locale); // Put the locale back to the user's locale
            return $this->redirect(['action' => 'index']);
        } else {
            I18n::locale($locale);
            $this->Flash->error(__('The ingredient could not be saved. Please, try again.'));
        }
    }

    $this->set(compact('ingredient'));
    $this->set('_serialize', ['ingredient']);
}
public function beforeFilter(Event $event)
{
    $locales = Configure::read('Config.locales');
    $boom = explode(',', str_replace('-', '_', $_SERVER['HTTP_ACCEPT_LANGUAGE']));
    $user_lang = substr($boom[0], 0, 2);

    // This piece of code is only to change the locale to fr_CA even if the user's language is just fr or fr_FR
    if(in_array($user_lang, Configure::read('Config.langs'))) {
        if(in_array($boom[0], $locales)) {
            I18n::locale($boom[0]);
        } else {
            foreach ($locales as $locale) {
                if(substr($locale, 0, 2) == $user_lang) {
                    I18n::locale($locale);
                }
            }
        }
    }

    $this->set('locales', $locales);
    $this->set('locale', I18n::locale());
}
因此,如果我在不同于默认语言环境下保存,相同的默认输入将保存在配料表和翻译表中保存的fr_CA的i18n表中 默认语言的输入被存储在翻译表中,以防默认语言环境发生更改,这似乎是预期的行为,就像在读取数据时,它将根据当前语言环境检索数据一样,保存数据时也是如此

将区域设置更改为默认值是一种变通方法,但可能有点太过侵入性,因为它会干扰使用该值检查当前区域设置的任何代码。最好直接在表上设置所需的区域设置

$Ingredients->locale(I18n::defaultLocale());
或者,这是侵入性最小的选项,改为在主实体上

$ingredient->_locale = I18n::defaultLocale();
另外,前者是需要修复的LinkedDocs sesection正在描述但没有实际显示的内容

拾取“错误”验证规则的字段 虽然我可以理解为什么表单助手(分别是实体上下文)会为“错误”字段(即
xyz.name
字段)选择验证规则,但我无法判断这是否是有效的

由于它不会拾取嵌套的错误,我想这是预期的行为,但我不确定,因此我建议进行澄清。在任何情况下,都有各种方法可以解决此问题,例如通过重命名字段,或通过将
required
选项设置为
false

echo $this->Form->input('locales.fr_CA.name', [
    // ...
    'required' => false
]);
在您的示例中,这几乎只是一个前端问题,因为字段不会在服务器端进行实际验证

另一种选择是使用自定义翻译表类,该类具有特定于翻译的验证,实际应用于所使用的字段,但是这可能并不可取,除非您确实希望应用任何验证

将验证/应用规则应用于已翻译的列 为了完整起见,我们还将讨论验证/应用程序规则

为了实际应用验证和/或应用程序规则,并在表单中识别它们,必须使用保存规则的自定义翻译表类,并且必须使用翻译行为用于
hasMany
关联翻译表的实际属性名称,即
\u i18n

这里有一个例子

src/Model/Table/IngredientsI18nTable.php

namespace App\Model\Table;

use Cake\Datasource\EntityInterface;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

class IngredientsI18nTable extends Table
{
    public function initialize(array $config) {
        $this->entityClass('Ingredient');
        $this->table('i18n');
        $this->displayField('id');
        $this->primaryKey('id');
    }

    public function validationDefault(Validator $validator) {

        $validator
            ->allowEmpty('name')
            ->add('name', 'valid', [
                'rule' => function ($value, $context) {
                    return false;
                }
            ]);
        return $validator;
    }

    public function buildRules(RulesChecker $rules)
    {
        $rules->add(
            function (EntityInterface $entity, $options) {
                return false;
            },
            'i18nName',
            [
                'errorField' => 'name'
            ]
        );

        return $rules;
    }
}
InCreditStable

public function initialize(array $config) {
    // ...

    $this->addBehavior('Translate', [
        // ...
        'translationTable' => 'IngredientsI18n'
    ]);
}
echo $this->Form->hidden('_i18n.0.locale', ['value' => 'fr_FR']);
echo $this->Form->input('_i18n.0.name');

echo $this->Form->hidden('_i18n.1.locale', ['value' => 'da_DK']);
echo $this->Form->input('_i18n.1.name');

// ...
查看模板

public function initialize(array $config) {
    // ...

    $this->addBehavior('Translate', [
        // ...
        'translationTable' => 'IngredientsI18n'
    ]);
}
echo $this->Form->hidden('_i18n.0.locale', ['value' => 'fr_FR']);
echo $this->Form->input('_i18n.0.name');

echo $this->Form->hidden('_i18n.1.locale', ['value' => 'da_DK']);
echo $this->Form->input('_i18n.1.name');

// ...
现在,字段将选择正确的验证器,因此不会按要求标记。此外,在创建/修补实体时将应用验证,最后也将应用应用程序规则。但是,我不能保证这不会有任何副作用,因为内部的翻译行为似乎无法解释外部设置了
\u i18n
属性的情况

此外,为了正确保存翻译,您还必须使用
translations()
在实体上设置翻译

foreach ($this->request->data['_i18n'] as $translation) {
    $ingredient->translation($translation['locale'])->set('name', $translation['name']);
}

不幸的是,带有验证和形式的翻译似乎有点过于复杂;(无论如何,在不更改区域设置的情况下保存两次的默认语言听起来很可疑,你能添加完整的保存代码,以及为你的应用程序/用户设置/更改(默认)区域设置的代码吗?啊,好的,明白了。我稍后回到我的机器后会发布答案。。。
foreach ($this->request->data['_i18n'] as $translation) {
    $ingredient->translation($translation['locale'])->set('name', $translation['name']);
}