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

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

My problem is simple, yet I can't figure out how to solve it.

我的网站是多语言的.我希望用户能够根据需要添加多种语言的文章,同时要求输入他的语言(取决于他的语言环境).

My website is multilanguage. I want the user to be able to add an article in multiple language if he wants, while requiring the inputs of his language (depending on his locale).

问题是,根据 CakePHP 的翻译约定,所有输入都必须以字段名称结尾,无论是什么语言.因此,所有字段对同一字段具有相同的规则.我不能要求一个名字",而不需要另一种语言的另一个.

Problem is, with CakePHP's conventions about translation, all the inputs must end with the field's name, no matter what language. So all the fields has the same rule for the same field. I can't make one "name" required while another in another language not required.

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

For example, the default language's input would be:

<input type="text" name="name" required="required" maxlength="45" id="name">

在此之下,同一字段的另一种语言输入:

And below that, another language's input for the same field:

<input type="text" name="locales[fr_CA][name]" required="required" maxlength="45" id="locales-fr-ca-name">

由于这些规则,required"属性会自动添加到两者中:

The "required" attribute is automatically added to both because of these rules:

$validator
    ->requirePresence('name', 'create')
    ->notEmpty('name')
    ->add('name', [
        'length' => [
            'rule' => ['minLength', 10],
            'message' => 'The title needs to be at least 10 characters long.',
        ]
    ]);

注意:我必须在保存时将区域设置更改为默认 (en_US) 才能保存为多种语言 + 默认语言(否则默认输入会保存在默认表和 i18n 表中).

Note: I have to change the locale to the default (en_US) when I save to be able to save in multiple languages + the default language (otherwise the default inputs are saved in the default table AND in the i18n table).

if ($this->request->is('post')) {
    I18n::locale('en_US');
    // ......

这是我保存时的完整代码段 (IngredientsController.php)

So here's the complete piece of code when I save (IngredientsController.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']);
}

我设置的默认语言环境是 bootstrap.php

I set the default locale is the 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 中确定用户的语言环境

I determine the user's locale in the AppController.php

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 表中

So if I save while being in a different locale than the default, the same default inputs will be saved in the ingredients table AND in the i18n table in fr_CA

推荐答案

默认保存在转换表中

默认语言的输入被存储在翻译表中,以防默认语言环境发生变化,这一事实似乎是预期的行为,就像读取数据时,它将检索关于当前语言环境,保存数据时同样适用.

Defaults saved in translation table

The fact that the input for the default language is being stored in the translation table in case the default locale has been changed, seems to be the expected behavior, just like when reading data where it will retrieve the data with respect to the current locale, the same applies when saving data.

食谱 > 数据库访问&ORM > 行为 > 翻译 > 用另一种语言保存

将区域设置更改为默认设置是一种解决方法,但它可能有点过于具有侵入性,因为它会干扰使用该值检查当前区域设置的任何代码.最好直接在表上设置想要的locale

Changing the locale to the default is a workaround, but it might be a little too invasive, as it will interfer with any code that uses that value to check the current locale. It's better to directly set the desired locale on the table

$Ingredients->locale(I18n::defaultLocale());

或者,这是侵入性最小的选项,改为在主实体上

or, which is the least invasive option, on the main entity instead

$ingredient->_locale = I18n::defaultLocale();

同样,前者是链接文档部分所描述的,但实际上并未显示,需要修复.

Also the former is what the linked docs sesction is describing, but not actually showing, that needs to be fixed.

虽然我可以理解为什么表单助手(分别是实体上下文)为错误"字段选择验证规则,即 xyz.name 字段为name<选择这些规则/code> 字段,我不知道这是否是它的工作方式.

While I can see why the form helper, respectively the entity context, picks up validation rules for the "wrong" fields, ie xyz.name fields pick up those for the name field, I can't tell whether this is how it is ment to work.

  • https:///github.com/cakephp/cakephp/blob/3.0.10/src/View/Form/EntityContext.php#L394
  • https:///github.com/cakephp/cakephp/blob/3.0.10/src/View/Form/EntityContext.php#L439

由于它不会发现嵌套错误,我想这是预期的行为,但我不确定,所以我建议 在 GitHub 上创建问题 以进行澄清.无论如何,有多种方法可以解决此问题,例如通过重命名字段,或将 required 选项设置为 false.

Since it wouldn't pick up nested errors, I guess this is the expected behavior, but I'm not sure, so I'd suggest to create an issue over at GitHub for clarification. In any case, there are various ways to work around this, for example by renaming the fields, or by setting the required option to false.

echo $this->Form->input('locales.fr_CA.name', [
    // ...
    'required' => false
]);

在您的示例中,这几乎只是一个前端问题,因为这些字段实际上不会在服务器端进行验证.

In your example this is pretty much just a frontend issue, as the fields are not going to be actually validated on the server side.

另一种选择是使用自定义翻译表类,对翻译进行特定的验证,实际上适用于使用的字段,但是这可能不是那个可取的,除非你真的想应用任何完全验证.

Another option would be to use a custom translation table class, with validation specific to translations, that actually apply to the used fields, however this is probably not that advisable unless you actually want to apply any validation at all.

为了完成起见,让我们也介绍一下验证/应用规则.

For the sake of completion, let's cover validation/application rules too.

为了实际应用验证和/或应用程序规则,并在表单中识别它们,您将使用包含规则的自定义转换表类,并且必须使用转换行为使用的实际属性名称对于hasMany关联的转换表,即_i18n.

In order to actually apply validation and/or application rules, and have them recognized in forms, you'll have use a custom translation table class that holds the rules, and you must use the actual property name that the translate behavior uses for the hasMany associated translation table, which is _i18n.

这是一个例子.

src/Model/Table/IngredientsI18nTable.php

namespace AppModelTable;

use CakeDatasourceEntityInterface;
use CakeORMRulesChecker;
use CakeORMTable;
use CakeValidationValidator;

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;
    }
}

配料表

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');

// ...

现在这些字段将选择正确的验证器,因此不会被标记为必需的.在创建/修补实体时也将应用验证,最后也将应用应用程序规则.但是我不能保证这没有任何副作用,因为内部的翻译行为似乎没有考虑到 _i18n 属性已在外部设置的情况!

Now the fields will pick up the correct validator, and thus are not being marked as required. Also validation will be applied when creating/patching entities, and finally application rules are being applied too. However I can't guarantee that this doesn't have any side effects, as the Translate behavior internally doesn't seem to account for the situation that the _i18n property has been set externally!

此外,您仍然需要使用 translations() 在实体上设置翻译,以便正确保存翻译!

Also you'll still have to set the translations on the entity using translations() in order for the translations to be saved correctly!

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

相关文章