Моя проблема проста, но я не могу понять, как ее решить.
Мой сайт многоязычный. Я хочу, чтобы пользователь мог добавлять статью на нескольких языках, если захочет, требуя ввода своего языка (в зависимости от его локали).
Проблема в том, что с соглашениями CakePHP о переводе все входные данные должны заканчиваться именем поля, независимо от того, на каком языке. Таким образом, все поля имеют одно и то же правило для одного и того же поля. Я не могу сделать одно «имя», требуемое, а другое на другом языке не требуется.
Например, входные данные по умолчанию:
<input type="text" name="name" required="required" maxlength="45" id="name">
И ниже этого, ввод другого языка для одного и того же поля:
<input type="text" name="locales[fr_CA][name]" required="required" maxlength="45" id="locales-fr-ca-name">
«Обязательный» атрибут автоматически добавляется к обоим из-за этих правил:
$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).
if ($this->request->is('post')) { I18n::locale('en_US'); // ......
EDIT: Итак, вот полный фрагмент кода, когда я сохраняю (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
/** * 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 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()); }
Поэтому, если я сохраню, находясь в другом регионе, чем по умолчанию, те же самые значения по умолчанию будут сохранены в таблице ингредиентов И в таблице i18n в fr_CA
Тот факт, что ввод для языка по умолчанию хранится в таблице переводов в случае изменения локали по умолчанию, кажется ожидаемым поведением, точно так же, как при чтении данных, где он будет извлекать данные по отношению к текущему языку, то же самое при сохранении данных.
Cookbook> Доступ к базе данных и ORM> Поведение> Перевод> Сохранение на другом языке
Изменение языкового стандарта по умолчанию является обходным путем, но оно может быть слишком инвазивным, поскольку оно будет мешать любому коду, использующему это значение, для проверки текущей локали. Лучше прямо установить желаемый язык на столе
$Ingredients->locale(I18n::defaultLocale());
или, что является наименее инвазивным вариантом, вместо основного
$ingredient->_locale = I18n::defaultLocale();
Также первое – это то, что описана связанная документация, но на самом деле не показана, которая должна быть исправлена.
Хотя я могу понять, почему хелпер формы, соответственно контекст сущности, подбирает правила проверки для «неправильных» полей, то xyz.name
поля xyz.name
берут те поля для поля name
, я не могу сказать, так ли это, работать.
Поскольку он не будет воспринимать вложенные ошибки, я предполагаю, что это ожидаемое поведение, но я не уверен, поэтому я бы предложил создать проблему в GitHub для уточнения. В любом случае существуют различные способы обойти это, например, путем переименования полей или путем установки required
параметра в значение false
.
echo $this->Form->input('locales.fr_CA.name', [ // ... 'required' => false ]);
В вашем примере это в значительной степени просто проблема с интерфейсом, так как поля на самом деле не проверяются на стороне сервера.
Другим вариантом было бы использовать собственный класс таблицы трансляции с валидацией, специфичной для переводов, которая действительно применяется к используемым полям, однако это, вероятно, не так целесообразно, если вы действительно не хотите применять какую-либо проверку вообще.
Ради завершения, давайте рассмотрим также правила проверки / применения.
Чтобы действительно применять правила проверки и / или применения, и распознавать их в формах, вы должны использовать собственный класс таблицы трансляции, который содержит правила, и вы должны использовать фактическое имя свойства, которое использует поведение перевода для hasMany
связанной таблицы переводов, которая равна _i18n
.
Вот пример.
SRC / Модель / Таблица / 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; } }
на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; } }
IngredientsTable
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'); // ...
Теперь поля подберут правильный валидатор и, следовательно, не будут отмечены как требуется. Кроме того, валидация будет применяться при создании / исправлении объектов, и, наконец, применяются правила приложения. Однако я не могу гарантировать, что это не имеет каких-либо побочных эффектов, поскольку внутреннее поведение Translate не _i18n
ситуацию, _i18n
свойство _i18n
было установлено извне!
Также вам нужно будет установить переводы на сущности, используя translations()
чтобы переводы сохранялись правильно!
foreach ($this->request->data['_i18n'] as $translation) { $ingredient->translation($translation['locale'])->set('name', $translation['name']); }