CakePhp TranslateBehavior,验证并保存多个语言环境

发布于 2024-12-29 01:26:55 字数 5856 浏览 1 评论 0原文

上下文: 我想使用 CakePhp 创建一个应该可翻译的 Web 应用程序。我想以一种形式保存同一字段的多个翻译。

问题: 我尝试了十几种方法来让它发挥作用,而且我做到了。但我最终使用了两个自定义 SQL 查询,这真的感觉不像 cakePhp 解决方案。

问题: 有谁知道更好的方法来达到相同的结果?

我尝试过的:

  • 为表单字段指定一个类似“Model.fieldName.locale”的名称,这在输入元素的名称属性中给出了正确的格式,但我的验证却没有识别字段名称。但保存有效。

  • 为表单字段指定“modelLocale”之类的名称,并传入名称 attr“data[Model][field][locale]”,在这种情况下,除了 isUnique 之外,验证有效,但保存到数据库不起作用.

  • 此的更多变体,但不值得一提。

我将在下面添加我的视图和模型:(如果您想查看更多代码或需要更多信息,请询问)

/App/View/Category/add.ctp

<?php echo $this->Form->create(); ?>
<?php echo $this->Form->input('title|dut'); ?>
<?php echo $this->Form->input('title|eng'); ?>
<?php echo $this->Form->input('title|fre'); ?>
<?php echo $this->Form->input('description|dut', array('type'=>'textarea')); ?>
<?php echo $this->Form->input('description|eng', array('type'=>'textarea')); ?>
<?php echo $this->Form->input('description|fre', array('type'=>'textarea')); ?>
<?php echo $this->Form->end('add'); ?>

/App/Model/ AppModel.php

<?php
App::uses('Model', 'Model');
class AppModel extends Model {

  /**
   * Check Unique
   *
   * Searches the i18n table to determine wetter a field is unique or not.
   * Expects field name to be as following: "fieldname|locale".
   * 
   * @param array $data     The data of the field, automatically passed trough by cakePhp.
   * @param string $field   The name of the field, which should match the one in the view.
   * @returns boolean
   */
  public function checkUnique($data, $field) {
    // Seperate the field key and locale which are seperated by "|".
    $a = preg_split('/[|]/', $field, 2);
    // If field key and locale are found...
    if (is_array($a) || count($a) === 2) {
      $q = sprintf("SELECT * FROM i18n WHERE i18n.locale = '%s' AND i18n.model = '%s' AND i18n.field = '%s' AND i18n.content = '%s' LIMIT 1",
        Sanitize::escape($a[1]),
        Sanitize::escape(strtolower($this->name)),
        Sanitize::escape($a[0]),
        Sanitize::escape($data[$field])
      );
      if ($this->query($q)) {
        return false;
      }
      return true;
    }
  }

  /**
   *  Setup Translation
   *
   *  Loops trough the fields. If a field is translatable
   *  (which it will know by it's structure [fieldname]|[locale])
   *  and has the default locale. Then it's value will be stored
   *  in the array where cake expects it 
   *  (data[Model][fieldname] instead of data[Model][fieldname|defaultLocale])
   *  so that cake will save it to the database.
   * 
   *  In the afterSave method the translations will be saved, for then we know
   *  the lastInsertId which is also the foreign_key of the i18n table.
   */
  public function _setupTranslations() {
    foreach($this->data[$this->name] as $key => $value) {
      $a = preg_split('/[|]/', $key, 2);
      if (is_array($a) && count($a) === 2) {
        $languages = Configure::read('Config.languages');
        if ($a[1] === $languages[Configure::read('Config.defaultLanguage')]['locale']) {
          $this->data[$this->name][$a[0]] = $value;
        }
      }
    }
  }

  /**
   *  Save Translations
   *  
   *  Saves the translations to the i18n database.
   *  Expects form fields with translations to have
   *  following structure: [fieldname]|[locale] (ex. title|eng, title|fre, ...).
   */
  public function _saveTranslations() {
    foreach($this->data[$this->name] as $key => $value) {
      $a = preg_split('/[|]/', $key, 2);
      if (is_array($a) && count($a) === 2) {
        $q = sprintf("INSERT INTO i18n (locale, model, foreign_key, field, content) VALUES ('%s', '%s', '%s', '%s', '%s')",
          Sanitize::escape($a[1]),
          Sanitize::escape(strtolower($this->name)),
          Sanitize::escape($this->id),
          Sanitize::escape($a[0]),
          Sanitize::escape($value)
        );
        $this->query($q);
      }
    }
  }

  /**
   * Before Save
   */
  public function beforeSave() { 
    $this->_setupTranslations();
    return true;
  }

  /**
   * After Save
   */
  public function afterSave() {
    $this->_saveTranslations();
    return true;
  }
}

/App/Model/Category.php

<?php
class Category extends AppModel {
  public $name = 'Category';
  public $hasMany = array(
    'Item'=>array(
      'className'=>'Item',
      'foreignKey'=>'category_id',
      'order'=>'Item.title ASC'
    )
  );
  var $actsAs = array(
    'Translate'=>array(
      'title',
      'description'
    )
  );
  public $validate = array(
    'title|dut'=>array(
      'required'=>array(
        'rule'=>'notEmpty',
        'message'=>'Veld verplicht'
      ),
      'unique'=>array(
        'rule'=>array('checkUnique', 'title|dut'),
        'message'=>'Titel reeds in gebruik'
      ),
    ),
    'title|eng'=>array(
      'required'=>array(
        'rule'=>'notEmpty',
        'message'=>'Veld verplicht'
      ),
      'unique'=>array(
        'rule'=>array('checkUnique', 'title|eng'),
        'message'=>'Titel reeds in gebruik'
      ),
    ),
    'title|fre'=>array(
      'required'=>array(
        'rule'=>'notEmpty',
        'message'=>'Veld verplicht'
      ),
      'unique'=>array(
        'rule'=>array('checkUnique', 'title|fre'),
        'message'=>'Titel reeds in gebruik'
      ),
    ),
  );
}
?>

注意: 关于这个主题的信息并不多...我有很多有关翻译行为的更多问题,例如获取递归结果也在正确的区域设置中,...任何人都知道好的图解或信息来源(食谱非常有限)

感谢阅读!!

Context:
I Want to create a web application using CakePhp which should be translatable. I want to save multiple translations for the same field in one form.

Problem:
I've tried a dozen ways to get this to work and I did. But I ended up using two custom SQL queries which really doesn't feel like a cakePhp solution.

Question:
Does anybody know a better way to achieve the same result?

What I tried:

  • Giving the form fields a name like 'Model.fieldName.locale', which gives it the right format in the name attr of the input element but then my validation doesn't recognize the field name. But saving works.

  • Giving the form fields a name like 'modelLocale' and pass in a name attr 'data[Model][field][locale]', in this case the validation works exept for isUnique but saving to the database doesn't work.

  • More variations of this but not worth mentioning.

I'll add my view and model below: (if u want to see more code or need more info just ask)

/App/View/Category/add.ctp

<?php echo $this->Form->create(); ?>
<?php echo $this->Form->input('title|dut'); ?>
<?php echo $this->Form->input('title|eng'); ?>
<?php echo $this->Form->input('title|fre'); ?>
<?php echo $this->Form->input('description|dut', array('type'=>'textarea')); ?>
<?php echo $this->Form->input('description|eng', array('type'=>'textarea')); ?>
<?php echo $this->Form->input('description|fre', array('type'=>'textarea')); ?>
<?php echo $this->Form->end('add'); ?>

/App/Model/AppModel.php

<?php
App::uses('Model', 'Model');
class AppModel extends Model {

  /**
   * Check Unique
   *
   * Searches the i18n table to determine wetter a field is unique or not.
   * Expects field name to be as following: "fieldname|locale".
   * 
   * @param array $data     The data of the field, automatically passed trough by cakePhp.
   * @param string $field   The name of the field, which should match the one in the view.
   * @returns boolean
   */
  public function checkUnique($data, $field) {
    // Seperate the field key and locale which are seperated by "|".
    $a = preg_split('/[|]/', $field, 2);
    // If field key and locale are found...
    if (is_array($a) || count($a) === 2) {
      $q = sprintf("SELECT * FROM i18n WHERE i18n.locale = '%s' AND i18n.model = '%s' AND i18n.field = '%s' AND i18n.content = '%s' LIMIT 1",
        Sanitize::escape($a[1]),
        Sanitize::escape(strtolower($this->name)),
        Sanitize::escape($a[0]),
        Sanitize::escape($data[$field])
      );
      if ($this->query($q)) {
        return false;
      }
      return true;
    }
  }

  /**
   *  Setup Translation
   *
   *  Loops trough the fields. If a field is translatable
   *  (which it will know by it's structure [fieldname]|[locale])
   *  and has the default locale. Then it's value will be stored
   *  in the array where cake expects it 
   *  (data[Model][fieldname] instead of data[Model][fieldname|defaultLocale])
   *  so that cake will save it to the database.
   * 
   *  In the afterSave method the translations will be saved, for then we know
   *  the lastInsertId which is also the foreign_key of the i18n table.
   */
  public function _setupTranslations() {
    foreach($this->data[$this->name] as $key => $value) {
      $a = preg_split('/[|]/', $key, 2);
      if (is_array($a) && count($a) === 2) {
        $languages = Configure::read('Config.languages');
        if ($a[1] === $languages[Configure::read('Config.defaultLanguage')]['locale']) {
          $this->data[$this->name][$a[0]] = $value;
        }
      }
    }
  }

  /**
   *  Save Translations
   *  
   *  Saves the translations to the i18n database.
   *  Expects form fields with translations to have
   *  following structure: [fieldname]|[locale] (ex. title|eng, title|fre, ...).
   */
  public function _saveTranslations() {
    foreach($this->data[$this->name] as $key => $value) {
      $a = preg_split('/[|]/', $key, 2);
      if (is_array($a) && count($a) === 2) {
        $q = sprintf("INSERT INTO i18n (locale, model, foreign_key, field, content) VALUES ('%s', '%s', '%s', '%s', '%s')",
          Sanitize::escape($a[1]),
          Sanitize::escape(strtolower($this->name)),
          Sanitize::escape($this->id),
          Sanitize::escape($a[0]),
          Sanitize::escape($value)
        );
        $this->query($q);
      }
    }
  }

  /**
   * Before Save
   */
  public function beforeSave() { 
    $this->_setupTranslations();
    return true;
  }

  /**
   * After Save
   */
  public function afterSave() {
    $this->_saveTranslations();
    return true;
  }
}

/App/Model/Category.php

<?php
class Category extends AppModel {
  public $name = 'Category';
  public $hasMany = array(
    'Item'=>array(
      'className'=>'Item',
      'foreignKey'=>'category_id',
      'order'=>'Item.title ASC'
    )
  );
  var $actsAs = array(
    'Translate'=>array(
      'title',
      'description'
    )
  );
  public $validate = array(
    'title|dut'=>array(
      'required'=>array(
        'rule'=>'notEmpty',
        'message'=>'Veld verplicht'
      ),
      'unique'=>array(
        'rule'=>array('checkUnique', 'title|dut'),
        'message'=>'Titel reeds in gebruik'
      ),
    ),
    'title|eng'=>array(
      'required'=>array(
        'rule'=>'notEmpty',
        'message'=>'Veld verplicht'
      ),
      'unique'=>array(
        'rule'=>array('checkUnique', 'title|eng'),
        'message'=>'Titel reeds in gebruik'
      ),
    ),
    'title|fre'=>array(
      'required'=>array(
        'rule'=>'notEmpty',
        'message'=>'Veld verplicht'
      ),
      'unique'=>array(
        'rule'=>array('checkUnique', 'title|fre'),
        'message'=>'Titel reeds in gebruik'
      ),
    ),
  );
}
?>

NOTE: There isn't that much information out there on this subject... I have a lot more questions about the translation behavior like getting the recursive results also in the correct locale, ... Anybody know a good tut or source of info (cookbook is quite limited)

Thanks for reading!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

眼眸 2025-01-05 01:26:55

看来您可能正在构建某种 CRM,允许用户根据他们设置的语言建立读取到网站中的内容。我会使用内置的 i18n 和 l10n 。它使它变得非常简单,但这可能不是动态内容的解决方案。

话虽如此,我能想到的唯一其他方法是非常乏味的。我将构建一个带有语言标识符下拉菜单的屏幕。因此,我不会尝试将所有语言塞满在同一个屏幕中,并为每种语言设置一个测试框,而是创建一个表单,然后使用该语言的下拉菜单。

您的模型使用列来定义行所属的语言。您创建的表格在一行中表达了所有语言。因此,如果您要查看显示记录的索引页面,您当然会看到:

title 1 eng
title 1 dut
title 1 fre
title 2 eng
title 2 dut
title 2 fre
...

此外,如果您要添加新语言,则必须修改模型和表单中的验证。

但是,如果您决定这样做,请更改 |到_然后就出发了。但随后您需要将所有数据存储在单个记录中。因此,当您查看记录索引时,您将看到:

title 1 end dut fre
title 2 end dut fre
...

我的建议:

1) 使用 .po / .pot 文件使用内置 i18n / l10n。

2)如果内容会经常更改,并且需要存储在数据库中,以便可以轻松地动态更改/频繁更新,则使用下拉菜单。

Language: dropdown
Title: text_field

It appears you may be building a CRM of sorts that allows the users to establish content that is read into the site based on the language they have set. I would use the built in i18n and l10n. It makes it really simple, but this is probably not a solution for dynamic content.

Having said that, the only other way I can think of doing this is very tedious. I would build a single screen with a language identifier drop down. So instead of trying to cram ALL languages in the same screen with a test box for each language, I would create one form and then use a drop down for the language.

Your model is using a column to define with language the row belongs to. The form you have created is expressing all languages in a single row. So if you were to view the Index page showing the records, of course you would see:

title 1 eng
title 1 dut
title 1 fre
title 2 eng
title 2 dut
title 2 fre
...

Further more, if you were ever to add a new language, you will have to modify the validation in the model and the form.

However, if you are set on doing it this way, change the | to _ and off you go. But then you will need to store all of the data in a single record. So when you look at the Index for the records, you will see:

title 1 end dut fre
title 2 end dut fre
...

My Advice:

1) Use the built in i18n / l10n using .po / .pot files.

2) If the content will be changing frequently and required to be stored in the database so it can be easily changed / updated frequently on the fly, then use a drop down.

Language: dropdown
Title: text_field
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文