From bd3e702d77484ecb50f648a71572428238ab1cda Mon Sep 17 00:00:00 2001 From: phpnut Date: Tue, 18 Sep 2007 04:16:04 +0000 Subject: [PATCH] changed i18n schema to use only one db table for translations changed fieldname 'row_id' to 'foreign_key' now allows fallbacks for not-existing translations by setting model's locale to array of locales model used for hasMany associations and for saving/deleting of translation records is configurable by Model::$translateModel property, db field 'i18n.field' is configurable by property $displayField of this custom model, instance of this model is obtainable by $this->ModelName->translateModel() Added tests for translate behavior. Added i18n shell script Correcting translation function calls in various files git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@5669 3807eeeb-6ff5-0310-8944-8be069107fe0 --- app/config/sql/i18n.sql | 24 +- cake/console/libs/i18n.php | 151 ++++++++ cake/console/libs/schema.php | 2 +- cake/console/libs/shell.php | 2 +- cake/console/libs/tasks/project.php | 2 +- cake/libs/controller/components/acl.php | 2 +- cake/libs/controller/components/auth.php | 10 +- cake/libs/i18n.php | 41 +- cake/libs/l10n.php | 81 +++- cake/libs/model/behaviors/translate.php | 365 +++++++++--------- .../libs/model/behaviors/translate.test.php | 302 +++++++++++++++ cake/tests/fixtures/translate_fixture.php | 65 ++++ .../fixtures/translated_item_fixture.php | 45 +++ 13 files changed, 840 insertions(+), 252 deletions(-) create mode 100644 cake/console/libs/i18n.php create mode 100644 cake/tests/cases/libs/model/behaviors/translate.test.php create mode 100644 cake/tests/fixtures/translate_fixture.php create mode 100644 cake/tests/fixtures/translated_item_fixture.php diff --git a/app/config/sql/i18n.sql b/app/config/sql/i18n.sql index 26ef91d4f..40d87622e 100644 --- a/app/config/sql/i18n.sql +++ b/app/config/sql/i18n.sql @@ -11,20 +11,18 @@ CREATE TABLE i18n ( id int(10) NOT NULL auto_increment, locale varchar(6) NOT NULL, - i18n_content_id int(10) NOT NULL, model varchar(255) NOT NULL, - row_id int(10) NOT NULL, + foreign_key int(10) NOT NULL, field varchar(255) NOT NULL, + content mediumtext, PRIMARY KEY (id), - KEY locale (locale), - KEY i18n_content_id (i18n_content_id), - KEY row_id (row_id), - KEY model (model), - KEY field (field) +# UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field), +# INDEX I18N_LOCALE_ROW(locale, model, foreign_key), +# INDEX I18N_LOCALE_MODEL(locale, model), +# INDEX I18N_FIELD(model, foreign_key, field), +# INDEX I18N_ROW(model, foreign_key), + INDEX locale (locale), + INDEX model (model), + INDEX row_id (foreign_key), + INDEX field (field) ); - -CREATE TABLE i18n_content ( - id int(10) NOT NULL auto_increment, - content text, - PRIMARY KEY (id) -); \ No newline at end of file diff --git a/cake/console/libs/i18n.php b/cake/console/libs/i18n.php new file mode 100644 index 000000000..52905aad4 --- /dev/null +++ b/cake/console/libs/i18n.php @@ -0,0 +1,151 @@ + + * Copyright 2005-2007, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2007, Cake Software Foundation, Inc. + * @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project + * @package cake + * @subpackage cake.cake.console.libs + * @since CakePHP(tm) v 1.2.0.5669 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ +/** + * Shell for I18N management. + * + * @package cake + * @subpackage cake.cake.console.libs + */ +class I18nShell extends Shell { +/** + * Contains database source to use + * + * @var string + * @access public + */ + var $dataSource = 'default'; +/** + * Contains tasks to load and instantiate + * + * @var array + * @access public + */ + var $tasks = array('DbConfig', 'Extract'); + +/** + * Override startup of the Shell + * + * @access public + */ + function startup() { + if (isset($this->params['datasource'])) { + $this->dataSource = $this->params['datasource']; + } + + if ($this->command && !in_array($this->command, array('help'))) { + if (!config('database')) { + $this->out(__('Your database configuration was not found. Take a moment to create one.', true), true); + return $this->DbConfig->execute(); + } + } + } +/** + * Override main() for help message hook + * + * @access public + */ + function main() { + $this->out(__('I18n Shell', true)); + $this->hr(); + $this->out(__('[E]xtract POT file from sources', true)); + $this->out(__('[I]nitialize i18n database table', true)); + $this->out(__('[H]elp', true)); + $this->out(__('[Q]uit', true)); + + $choice = strtoupper($this->in(__('What would you like to do?', true), array('E', 'I', 'H', 'Q'))); + switch($choice) { + case 'E': + $this->Extract->execute(); + break; + case 'I': + $this->initdb(); + break; + case 'H': + $this->help(); + break; + case 'Q': + exit(0); + break; + default: + $this->out(__('You have made an invalid selection. Please choose a command to execute by entering E, I, H, or Q.', true)); + } + $this->hr(); + $this->main(); + } +/** + * Initialize I18N database. + * + * @access public + */ + function initdb() { + $db =& ConnectionManager::getDataSource($this->dataSource); + $this->out(__('Initializing Database...', true), true); + $this->out(__('Creating i18n table ...', true), true); + $sql = ' CREATE TABLE '.$db->fullTableName('i18n').' ( + '.$db->name('id').' '.$db->column($db->columns['primary_key']).', + '.$db->name('locale').' '.$db->column(array('name' => 'varchar', 'limit' => 6)).' NOT NULL, + '.$db->name('model').' '.$db->column($db->columns['string']).' NOT NULL, + '.$db->name('foreign_key').' '.$db->column($db->columns['integer']).' NOT NULL, + '.$db->name('field').' '.$db->column($db->columns['string']).' NOT NULL, + '.$db->name('content').' '.$db->column($db->columns['text']).', + PRIMARY KEY ('.$db->name('id').'), + INDEX locale ('.$db->name('locale').'), + INDEX model ('.$db->name('model').'), + INDEX foreign_key ('.$db->name('foreign_key').'), + INDEX field ('.$db->name('field').') + )'; + if ($db->query($sql) === false) { + die('Error: ' . $db->lastError()); + } + + $this->out(__('Done.', true), true); + } +/** + * Show help screen. + * + * @access public + */ + function help() { + $this->hr(); + $this->out(__('I18n Shell:', true)); + $this->hr(); + $this->out(__('I18n Shell initializes i18n database table for your application', true)); + $this->out(__('and generates .pot file(s) with translations.', true)); + $this->hr(); + $this->out(__('usage:', true)); + $this->out(' cake i18n help'); + $this->out(' cake i18n initdb [-datasource custom]'); + $this->out(''); + $this->hr(); + + $this->Extract->help(); + } +} + +?> \ No newline at end of file diff --git a/cake/console/libs/schema.php b/cake/console/libs/schema.php index dc55e9365..238299eca 100644 --- a/cake/console/libs/schema.php +++ b/cake/console/libs/schema.php @@ -126,7 +126,7 @@ class SchemaShell extends Shell { } $File = new File($this->Schema->path . DS . $write, true); if($File->write($contents)) { - $this->out(__('SQL dump file created in '. $File->pwd(), true)); + $this->out(sprintf(__('SQL dump file created in %s', true), $File->pwd())); exit(); } else { $this->err(__('SQL dump could not be created', true)); diff --git a/cake/console/libs/shell.php b/cake/console/libs/shell.php index dfaf5c25b..f2f1a00e3 100644 --- a/cake/console/libs/shell.php +++ b/cake/console/libs/shell.php @@ -398,7 +398,7 @@ class Shell extends Object { */ function createFile ($path, $contents) { $path = str_replace(DS . DS, DS, $path); - $this->out("\n".__(sprintf("Creating file %s", $path), true)); + $this->out("\n" . sprintf(__("Creating file %s", true), $path)); if (is_file($path) && $this->interactive === true) { $key = $this->in(__("File exists, overwrite?", true). " {$path}", array('y', 'n', 'q'), 'n'); if (low($key) == 'q') { diff --git a/cake/console/libs/tasks/project.php b/cake/console/libs/tasks/project.php index d6556d123..adaa7d22f 100644 --- a/cake/console/libs/tasks/project.php +++ b/cake/console/libs/tasks/project.php @@ -147,7 +147,7 @@ class ProjectTask extends Shell { if ($Folder->copy($path)) { $path = $Folder->slashTerm($path); $this->hr(); - $this->out(__(sprintf("Created: %s in %s", $app, $path), true)); + $this->out(sprintf(__("Created: %s in %s", true), $app, $path)); $this->hr(); if ($this->createHome($path, $app)) { diff --git a/cake/libs/controller/components/acl.php b/cake/libs/controller/components/acl.php index 07d67aa12..422e5a498 100644 --- a/cake/libs/controller/components/acl.php +++ b/cake/libs/controller/components/acl.php @@ -61,7 +61,7 @@ class AclComponent extends Object { } $name .= 'Component'; } else { - trigger_error(__(sprintf('Could not find %s.', $name), true), E_USER_WARNING); + trigger_error(sprintf(__('Could not find %s.', true), $name), E_USER_WARNING); } } $this->_instance =& new $name(); diff --git a/cake/libs/controller/components/auth.php b/cake/libs/controller/components/auth.php index bbb7c4855..03400d6cc 100644 --- a/cake/libs/controller/components/auth.php +++ b/cake/libs/controller/components/auth.php @@ -418,7 +418,7 @@ class AuthComponent extends Object { case 'crud': $this->mapActions(); if (!isset($this->actionMap[$this->params['action']])) { - trigger_error(__(sprintf('Auth::startup() - Attempted access of un-mapped action "%s" in controller "%s"', $this->params['action'], $this->params['controller']), true), E_USER_WARNING); + trigger_error(sprintf(__('Auth::startup() - Attempted access of un-mapped action "%s" in controller "%s"', true), $this->params['action'], $this->params['controller']), E_USER_WARNING); } else { $valid = $this->Acl->check($user, $this->action(':controller'), $this->actionMap[$this->params['action']]); } @@ -434,13 +434,13 @@ class AuthComponent extends Object { $action = $this->action(':action'); } if (empty($object)) { - trigger_error(__(sprintf('Could not find %s. Set AuthComponent::$object in beforeFilter() or pass a valid object', get_class($object)), true), E_USER_WARNING); + trigger_error(sprintf(__('Could not find %s. Set AuthComponent::$object in beforeFilter() or pass a valid object', true), get_class($object)), E_USER_WARNING); return; } if (method_exists($object, 'isAuthorized')) { $valid = $object->isAuthorized($user, $this->action(':controller'), $action); } elseif ($object){ - trigger_error(__(sprintf('%s::isAuthorized() is not defined.', get_class($object)), true), E_USER_WARNING); + trigger_error(sprintf(__('%s::isAuthorized() is not defined.', true), get_class($object)), E_USER_WARNING); } break; case null: @@ -669,7 +669,7 @@ class AuthComponent extends Object { } if (!ClassRegistry::isKeySet($name)) { if (!loadModel(Inflector::underscore($name))) { - trigger_error(__(sprintf('Auth::getModel() - %s is not set or could not be found', $name), true), E_USER_WARNING); + trigger_error(sprintf(__('Auth::getModel() - %s is not set or could not be found', true), $name), E_USER_WARNING); return $model; } else { $model = new $name(); @@ -685,7 +685,7 @@ class AuthComponent extends Object { } if (empty($model)) { - trigger_error(__(sprintf('Auth::getModel() - %s is not set or could not be found', $name), true) . $name, E_USER_WARNING); + trigger_error(sprintf(__('Auth::getModel() - %s is not set or could not be found', true), $name), E_USER_WARNING); return null; } diff --git a/cake/libs/i18n.php b/cake/libs/i18n.php index 8ed274e36..d09209c77 100644 --- a/cake/libs/i18n.php +++ b/cake/libs/i18n.php @@ -27,8 +27,8 @@ * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ /** - * Included libraries. - */ + * Included libraries. + */ uses('l10n'); /** * Short description for file. @@ -43,16 +43,9 @@ class I18n extends Object { * Instance of the I10n class for localization * * @var object - * @access private - */ - var $__l10n = null; -/** - * The locale for current translation - * - * @var string * @access public */ - var $locale = null; + var $l10n = null; /** * Translation strings for a specific domain read from the .mo or .po files * @@ -86,7 +79,13 @@ class I18n extends Object { static $instance = array(); if (!$instance) { $instance[0] =& new I18n(); - $instance[0]->__l10n =& new L10n(); + $instance[0]->l10n =& new L10n(); + + $language = Configure::read('Config.language'); + if ($language === null && !empty($_SESSION['Config']['language'])) { + $language = $_SESSION['Config']['language']; + } + $instance[0]->l10n->get($language); } return $instance[0]; } @@ -107,16 +106,6 @@ class I18n extends Object { $_this =& I18n::getInstance(); $_this->category = $_this->__categories[$category]; - if ($_this->__l10n->found === false) { - $language = Configure::read('Config.language'); - - if ($language === null && !empty($_SESSION['Config']['language'])) { - $language = $_SESSION['Config']['language']; - } - $_this->__l10n->get($language); - $_this->locale = $_this->__l10n->locale; - } - if (is_null($domain)) { if (preg_match('/views{0,1}\\'.DS.'([^\/]*)/', $directory, $regs)) { $domain = $regs[1]; @@ -168,7 +157,7 @@ class I18n extends Object { return($plural); } return($singular); - } + } /** * Attempts to find the plural form of a string. * @@ -215,7 +204,7 @@ class I18n extends Object { switch ($type) { case -1: - return (0); + return (0); case 1: if ($n != 1) { return (1); @@ -227,7 +216,7 @@ class I18n extends Object { } return (0); case 7: - return ($n); + return ($n); case 21: if (($n % 10 == 1) && ($n % 100 != 11)) { return (0); @@ -316,7 +305,7 @@ class I18n extends Object { } foreach ($searchPath as $directory) { - foreach ($_this->__l10n->languagePath as $lang) { + foreach ($_this->l10n->languagePath as $lang) { $file = $directory . DS . $lang . DS . $_this->category . DS . $domain; $default = APP . 'locale'. DS . $lang . DS . $_this->category . DS . 'default'; $core = CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'locale'. DS . $lang . DS . $_this->category . DS . 'core'; @@ -493,4 +482,4 @@ class I18n extends Object { return($domain); } } -?> \ No newline at end of file +?> diff --git a/cake/libs/l10n.php b/cake/libs/l10n.php index 7bec57538..edf17ab64 100644 --- a/cake/libs/l10n.php +++ b/cake/libs/l10n.php @@ -118,8 +118,8 @@ class L10n extends Object { /* French (Standard) */ 'fre' => 'fr', /* Gaelic (Scots) */ 'gla' => 'gd', /* Galician */ 'glg' => 'gl', - /* German (Standard) */ 'deu' => 'de', - /* German (Standard) */ 'ger' => 'de', + /* German (Standard) */ 'deu' => 'de', + /* German (Standard) */ 'ger' => 'de', /* Greek */ 'ell' => 'el', /* Greek */ 'gre' => 'el', /* Hebrew */ 'heb' => 'he', @@ -135,7 +135,7 @@ class L10n extends Object { /* Latvian */ 'lav' => 'lv', /* Lithuanian */ 'lit' => 'lt', /* Macedonian */ 'mac' => 'mk', - /* Macedonian */ 'mkd' => 'mk', + /* Macedonian */ 'mkd' => 'mk', /* Malaysian */ 'may' => 'ms', /* Malaysian */ 'msa' => 'ms', /* Maltese */ 'mlt' => 'mt', @@ -158,8 +158,8 @@ class L10n extends Object { /* Sorbian */ 'wen' => 'sb', /* Spanish (Spain - Traditional) */ 'spa' => 'es', /* Swedish */ 'swe' => 'sv', - /* Thai */ 'tha' => 'th', - /* Tsonga */ 'tso' => 'ts', + /* Thai */ 'tha' => 'th', + /* Tsonga */ 'tso' => 'ts', /* Tswana */ 'tsn' => 'tn', /* Turkish */ 'tur' => 'tr', /* Ukrainian */ 'ukr' => 'uk', @@ -168,7 +168,7 @@ class L10n extends Object { /* Vietnamese */ 'vie' => 'vi', /* Xhosa */ 'xho' => 'xh', /* Yiddish */ 'yid' => 'yi', - /* Zulu */ 'zul' => 'zu'); + /* Zulu */ 'zul' => 'zu'); /** * HTTP_ACCEPT_LANGUAGE catalog * @@ -200,12 +200,12 @@ class L10n extends Object { 'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8'), 'cs' => array('language' => 'Czech', 'locale' => 'cze', 'localeFallback' => 'cze', 'charset' => 'utf-8'), 'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8'), - 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8'), - 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8'), - 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8'), - 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8'), - 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8'), - 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8'), + 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8'), + 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8'), + 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8'), + 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8'), + 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8'), + 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8'), 'e' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8'), 'el' => array('language' => 'Greek', 'locale' => 'gre', 'localeFallback' => 'gre', 'charset' => 'utf-8'), 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8'), @@ -409,5 +409,62 @@ class L10n extends Object { } return false; } +/** + * Attempts to find locale for language, or language for locale + * + * @param mixed $mixed 2/3 char string (language/locale), array of those strings, or null + * @return mixed string language/locale, array of those values, whole map as an array, or false when language/locale doesn't exist + * @access public + */ + function map($mixed = null) { + if (is_array($mixed)) { + $result = array(); + foreach ($mixed as $_mixed) { + if ($_result = $this->map($_mixed)) { + $result[$_mixed] = $_result; + } + } + return $result; + } + + if (is_string($mixed)) { + if (2 == strlen($mixed)) { + if (in_array($mixed, $this->__l10nMap)) { + return array_search($mixed, $this->__l10nMap); + } + } else { + if (isset($this->__l10nMap[$mixed])) { + return $this->__l10nMap[$mixed]; + } + } + return false; + } + return $this->__l10nMap; + } +/** + * Attempts to find catalog record for requested language + * + * @param mixed $language string requested language, array of requested languages, or null for whole catalog + * @return mixed array catalog record for requested language, array of catalog records, whole catalog, or false when language doesn't exist + */ + function catalog($language = null) { + if (is_array($language)) { + $result = array(); + foreach ($language as $_language) { + if ($_result = $this->catalog($_language)) { + $result[$_language] = $_result; + } + } + return $result; + } + + if (is_string($language)) { + if (isset($this->__l10nCatalog[$language])) { + return $this->__l10nCatalog[$language]; + } + return false; + } + return $this->__l10nCatalog; + } } ?> \ No newline at end of file diff --git a/cake/libs/model/behaviors/translate.php b/cake/libs/model/behaviors/translate.php index e9209b036..431795213 100644 --- a/cake/libs/model/behaviors/translate.php +++ b/cake/libs/model/behaviors/translate.php @@ -39,19 +39,6 @@ class TranslateBehavior extends ModelBehavior { * Used for runtime configuration of model */ var $runtime = array(); -/** - * Instance of I18nModel class, used internally - */ - var $_model = null; -/** - * Constructor - */ - function __construct() { - parent::__construct(); - - $this->_model =& new I18nModel(); - ClassRegistry::addObject('I18nModel', $this->_model); - } /** * Callback * @@ -66,44 +53,45 @@ class TranslateBehavior extends ModelBehavior { * bindTranslation() method */ function setup(&$model, $config = array()) { - $this->settings[$model->name] = array(); - $this->runtime[$model->name] = array('fields' => array()); $db =& ConnectionManager::getDataSource($model->useDbConfig); - if (!$db->connected) { - trigger_error('Datasource '.$model->useDbConfig.' for I18nBehavior of model '.$model->name.' is not connected', E_USER_ERROR); + trigger_error('Datasource '.$model->useDbConfig.' for TranslateBehavior of model '.$model->name.' is not connected', E_USER_ERROR); return false; } - $this->runtime[$model->name]['tablePrefix'] = $db->config['prefix']; - return $this->bindTranslation($model, $config, false); + + $this->settings[$model->name] = array(); + $this->runtime[$model->name] = array('fields' => array()); + $this->translateModel($model); + return $this->bindTranslation($model, null, false); } /** * Callback */ function beforeFind(&$model, $query) { $locale = $this->_getLocale($model); + $db =& ConnectionManager::getDataSource($model->useDbConfig); + $tablePrefix = $db->config['prefix']; + $RuntimeModel =& $this->translateModel($model); - if (is_string($query['fields']) && 'COUNT(*) AS count' == $query['fields']) { + if (is_string($query['fields']) && 'COUNT(*) AS '.$db->name('count') == $query['fields']) { $this->runtime[$model->name]['count'] = true; - $db =& ConnectionManager::getDataSource($model->useDbConfig); - $tablePrefix = $this->runtime[$model->name]['tablePrefix']; - + if (empty($locale)) { + return $query; + } $query['fields'] = 'COUNT(DISTINCT('.$db->name($model->name).'.'.$db->name($model->primaryKey).')) ' . $db->alias . 'count'; $query['joins'][] = array( - 'type' => 'INNER', - 'alias' => 'I18nModel', - 'table' => $tablePrefix . 'i18n', - 'conditions' => array( - $model->name.'.id' => '{$__cakeIdentifier[I18nModel.row_id]__$}', - 'I18nModel.model' => $model->name, - 'I18nModel.locale' => $locale - ) - ); + 'type' => 'INNER', + 'alias' => $RuntimeModel->name, + 'table' => $db->name($tablePrefix . 'i18n'), + 'conditions' => array( + $model->name.'.id' => '{$__cakeIdentifier['.$RuntimeModel->name.'.foreign_key]__$}', + $RuntimeModel->name.'.model' => $model->name, + $RuntimeModel->name.'.locale' => $locale)); return $query; } - if (empty($locale) || is_array($locale)) { + if (empty($locale)) { return $query; } $autoFields = false; @@ -125,42 +113,53 @@ class TranslateBehavior extends ModelBehavior { } $autoFields = true; } - $fields = am($this->settings[$model->name], $this->runtime[$model->name]['fields']); - $tablePrefix = $this->runtime[$model->name]['tablePrefix']; $addFields = array(); - if (is_array($query['fields'])) { - if (in_array($model->name.'.*', $query['fields'])) { - foreach ($fields as $key => $value) { - $addFields[] = ife(is_numeric($key), $value, $key); - } - } else { - foreach ($fields as $key => $value) { - $field = ife(is_numeric($key), $value, $key); - if ($autoFields || in_array($model->name.'.'.$field, $query['fields']) || in_array($field, $query['fields'])) { - $addFields[] = $field; - } + foreach ($fields as $key => $value) { + $field = ife(is_numeric($key), $value, $key); + + if (in_array($model->name.'.*', $query['fields']) || $autoFields || in_array($model->name.'.'.$field, $query['fields']) || in_array($field, $query['fields'])) { + $addFields[] = $field; } } } if ($addFields) { - $db =& ConnectionManager::getDataSource($model->useDbConfig); - foreach ($addFields as $field) { - $key = array_search($model->name.'.'.$field, $query['fields']); - if (false !== $key) { - unset($query['fields'][$key]); + foreach (array($field, $model->name.'.'.$field) as $_field) { + $key = array_search($_field, $query['fields']); + + if ($key !== false) { + unset($query['fields'][$key]); + } } - $query['fields'][] = 'I18n__'.$field.'.content'; - - $query['joins'][] = 'LEFT JOIN '.$db->name($tablePrefix.'i18n').' AS '.$db->name('I18n__'.$field.'Model').' ON '.$db->name($model->name.'.id').' = '.$db->name('I18n__'.$field.'Model.row_id'); - $query['joins'][] = 'LEFT JOIN '.$db->name($tablePrefix.'i18n_content').' AS '.$db->name('I18n__'.$field).' ON '.$db->name('I18n__'.$field.'Model.i18n_content_id').' = '.$db->name('I18n__'.$field.'.id'); - $query['conditions'][$db->name('I18n__'.$field.'Model.model')] = $model->name; - $query['conditions'][$db->name('I18n__'.$field.'Model.field')] = $field; - $query['conditions'][$db->name('I18n__'.$field.'Model`.`locale')] = $locale; + if (is_array($locale)) { + foreach ($locale as $_locale) { + $query['fields'][] = 'I18n__'.$field.'__'.$_locale.'.content'; + $query['joins'][] = array( + 'type' => 'LEFT', + 'alias' => 'I18n__'.$field.'__'.$_locale, + 'table' => $db->name($tablePrefix . 'i18n'), + 'conditions' => array( + $model->name.'.id' => '{$__cakeIdentifier[I18n__'.$field.'__'.$_locale.'.foreign_key]__$}', + 'I18n__'.$field.'__'.$_locale.'.model' => $model->name, + 'I18n__'.$field.'__'.$_locale.'.'.$RuntimeModel->displayField => $field, + 'I18n__'.$field.'__'.$_locale.'.locale' => $_locale)); + } + } else { + $query['fields'][] = 'I18n__'.$field.'.content'; + $query['joins'][] = array( + 'type' => 'LEFT', + 'alias' => 'I18n__'.$field, + 'table' => $db->name($tablePrefix . 'i18n'), + 'conditions' => array( + $model->name.'.id' => '{$__cakeIdentifier[I18n__'.$field.'.foreign_key]__$}', + 'I18n__'.$field.'.model' => $model->name, + 'I18n__'.$field.'.'.$RuntimeModel->displayField => $field)); + $query['conditions'][$db->name('I18n__'.$field.'.locale')] = $locale; + } } } $query['fields'] = am($query['fields']); @@ -178,31 +177,27 @@ class TranslateBehavior extends ModelBehavior { $this->runtime[$model->name]['fields'] = array(); $locale = $this->_getLocale($model); - if (empty($locale) || empty($results)) { + if (empty($locale) || empty($results) || empty($this->runtime[$model->name]['beforeFind'])) { return $results; } + $beforeFind = $this->runtime[$model->name]['beforeFind']; - if (is_array($locale)) { - $fields = am($this->settings[$model->name], $this->runtime[$model->name]['fields']); - $emptyFields = array('locale' => ''); + foreach ($results as $key => $row) { + $results[$key][$model->name]['locale'] = ife(is_array($locale), @$locale[0], $locale); - foreach ($fields as $key => $value) { - $field = ife(is_numeric($key), $value, $key); - $emptyFields[$field] = ''; - } - unset($fields); + foreach ($beforeFind as $field) { + if (is_array($locale)) { + foreach ($locale as $_locale) { + if (!isset($results[$key][$model->name][$field]) && !empty($results[$key]['I18n__'.$field.'__'.$_locale]['content'])) { + $results[$key][$model->name][$field] = $results[$key]['I18n__'.$field.'__'.$_locale]['content']; + } + unset($results[$key]['I18n__'.$field.'__'.$_locale]); + } - foreach ($results as $key => $row) { - $results[$key][$model->name] = am($results[$key][$model->name], $emptyFields); - } - unset($emptyFields); - } elseif (!empty($this->runtime[$model->name]['beforeFind'])) { - $beforeFind = $this->runtime[$model->name]['beforeFind']; - - foreach ($results as $key => $row) { - $results[$key][$model->name]['locale'] = $locale; - - foreach ($beforeFind as $field) { + if (!isset($results[$key][$model->name][$field])) { + $results[$key][$model->name][$field] = ''; + } + } else { $value = ife(empty($results[$key]['I18n__'.$field]['content']), '', $results[$key]['I18n__'.$field]['content']); $results[$key][$model->name][$field] = $value; unset($results[$key]['I18n__'.$field]); @@ -210,7 +205,7 @@ class TranslateBehavior extends ModelBehavior { } } return $results; - } + } /** * Callback */ @@ -232,89 +227,47 @@ class TranslateBehavior extends ModelBehavior { } } $this->runtime[$model->name]['beforeSave'] = $tempData; - $this->runtime[$model->name]['ignoreUserAbort'] = ignore_user_abort(); - @ignore_user_abort(true); return true; } /** * Callback */ function afterSave(&$model, $created) { - $locale = $this->_getLocale($model); - - if (empty($locale) || is_array($locale) || empty($this->runtime[$model->name]['beforeSave'])) { + if (!isset($this->runtime[$model->name]['beforeSave'])) { return true; } + $locale = $this->_getLocale($model); $tempData = $this->runtime[$model->name]['beforeSave']; unset($this->runtime[$model->name]['beforeSave']); + $conditions = array('locale' => $locale, 'model' => $model->name, 'foreign_key' => $model->id); + $RuntimeModel =& $this->translateModel($model); - $conditions = array('locale' => $locale, - 'model' => $model->name, - 'row_id' => $model->id); + if (empty($created)) { + $translations = $RuntimeModel->generateList(am($conditions, array($RuntimeModel->displayField => array_keys($tempData)))); - if ($created) { - foreach ($tempData as $field => $value) { - $this->_model->Content->create(); - $this->_model->Content->save(array('I18nContent' => array('content' => $value))); - - $this->_model->create(); - $this->_model->save(array('I18nModel' => am($conditions, array( - 'i18n_content_id' => $this->_model->Content->getInsertID(), - 'field' => $field)))); - } - } else { - $this->_model->recursive = -1; - $translations = $this->_model->findAll($conditions, array('field', 'i18n_content_id')); - $fields = Set::extract($translations, '{n}.I18nModel.field'); - $ids = Set::extract($translations, '{n}.I18nModel.i18n_content_id'); - - foreach ($fields as $key => $field) { - if (array_key_exists($field, $tempData)) { - $this->_model->Content->create(); - $this->_model->Content->save(array('I18nContent' => array( - 'id' => $ids[$key], - 'content' => $tempData[$field]))); + if ($translations) { + foreach ($translations as $id => $field) { + $RuntimeModel->create(); + $RuntimeModel->save(array($RuntimeModel->name => array('id' => $id, 'content' => $tempData[$field]))); + unset($tempData[$field]); } } } - @ignore_user_abort((bool) $this->runtime[$model->name]['ignoreUserAbort']); - unset($this->runtime[$model->name]['ignoreUserAbort']); - } -/** - * Callback - */ - function beforeDelete(&$model) { - $this->runtime[$model->name]['ignoreUserAbort'] = ignore_user_abort(); - @ignore_user_abort(true); - return true; + + if (!empty($tempData)) { + foreach ($tempData as $field => $value) { + $RuntimeModel->create(am($conditions, array($RuntimeModel->displayField => $field, 'content' => $value))); + $RuntimeModel->save(); + } + } } /** * Callback */ function afterDelete(&$model) { - $this->_model->recursive = -1; - $conditions = array('model' => $model->name, 'row_id' => $model->id); - $translations = $this->_model->findAll($conditions, array('i18n_content_id')); - $ids = Set::extract($translations, '{n}.I18nModel.i18n_content_id'); - - $db =& ConnectionManager::getDataSource($model->useDbConfig); - $db->delete($this->_model->Content, array('id' => $ids)); - $db->delete($this->_model, $conditions); - - @ignore_user_abort((bool) $this->runtime[$model->name]['ignoreUserAbort']); - unset($this->runtime[$model->name]['ignoreUserAbort']); - } -/** - * Autodetects locale for application - * - * @todo - * @return string - */ - function _autoDetectLocale() { - // just fast hack to obtain selected locale - __d('core', 'Notice', true); - $I18n =& I18n::getInstance(); - return $I18n->locale; + $RuntimeModel =& $this->translateModel($model); + $conditions = array('model' => $model->name, 'foreign_key' => $model->id); + $RuntimeModel->deleteAll($conditions); } /** * Get selected locale for model @@ -323,36 +276,67 @@ class TranslateBehavior extends ModelBehavior { */ function _getLocale(&$model) { if (!isset($model->locale) || is_null($model->locale)) { - $model->locale = $this->_autoDetectLocale(); + $I18n =& I18n::getInstance(); + $model->locale = $I18n->l10n->locale; } return $model->locale; } +/** + * Get instance of model for translations + * + * @return object + */ + function &translateModel(&$model) { + if (!isset($this->runtime[$model->name]['model'])) { + if (!isset($model->translateModel) || empty($model->translateModel)) { + $className = 'I18nModel'; + } else { + $className = $model->translateModel; + if (!class_exists($className) && !loadModel($className)) { + return $this->cakeError('missingModel', array(array('className' => $className))); + } + } + if (ClassRegistry::isKeySet($className)) { + if (PHP5) { + $this->runtime[$model->name]['model'] = ClassRegistry::getObject($className); + } else { + $this->runtime[$model->name]['model'] =& ClassRegistry::getObject($className); + } + } else { + if (PHP5) { + $this->runtime[$model->name]['model'] = new $className(); + } else { + $this->runtime[$model->name]['model'] =& new $className(); + } + ClassRegistry::addObject($className, $this->runtime[$model->name]['model']); + ClassRegistry::map($className, $className); + } + } + return $this->runtime[$model->name]['model']; + } /** * Bind translation for fields, optionally with hasMany association for * fake field * * @param object instance of model - * @param mixed string with field or array(field1, field2=>AssocName, field3) + * @param mixed string with field, or array(field1, field2=>AssocName, field3), or null for bind all original translations * @param boolead $reset * @return boolean */ - function bindTranslation(&$model, $fields, $reset = true) { + function bindTranslation(&$model, $fields = null, $reset = true) { if (empty($fields)) { - return true; + return $this->bindTranslation($model, $model->actsAs['Translate'], $reset); } if (is_string($fields)) { $fields = array($fields); } - $settings =& $this->settings[$model->name]; - $runtime =& $this->runtime[$model->name]['fields']; $associations = array(); - - $default = array('className' => 'I18nModel', 'foreignKey' => 'row_id'); + $RuntimeModel =& $this->translateModel($model); + $default = array('className' => $RuntimeModel->name, 'foreignKey' => 'foreign_key'); foreach ($fields as $key => $value) { - if (is_numeric($key)) { $field = $value; $association = null; @@ -361,29 +345,32 @@ class TranslateBehavior extends ModelBehavior { $association = $value; } - if (in_array($field, $settings)) { - $this->settings[$model->name] = array_diff_assoc($settings, array($field)); - } elseif (array_key_exists($field, $settings)) { - unset($settings[$field]); + if (array_key_exists($field, $this->settings[$model->name])) { + unset($this->settings[$model->name][$field]); + + } elseif (in_array($field, $this->settings[$model->name])) { + $this->settings[$model->name] = am(array_diff_assoc($this->settings[$model->name], array($field))); } - if (in_array($field, $runtime)) { - $this->runtime[$model->name]['fields'] = array_diff_assoc($runtime, array($field)); - } elseif (array_key_exists($field, $runtime)) { - unset($runtime[$field]); + if (array_key_exists($field, $this->runtime[$model->name]['fields'])) { + unset($this->runtime[$model->name]['fields'][$field]); + + } elseif (in_array($field, $this->runtime[$model->name]['fields'])) { + $this->runtime[$model->name]['fields'] = am(array_diff_assoc($this->runtime[$model->name]['fields'], array($field))); } if (is_null($association)) { if ($reset) { - $runtime[] = $field; + $this->runtime[$model->name]['fields'][] = $field; } else { - $settings[] = $field; + $this->settings[$model->name][] = $field; } } else { + if ($reset) { - $runtime[$field] = $association; + $this->runtime[$model->name]['fields'][$field] = $association; } else { - $settings[$field] = $association; + $this->settings[$model->name][$field] = $association; } foreach (array('hasOne', 'hasMany', 'belongsTo', 'hasAndBelongsToMany') as $type) { @@ -393,8 +380,8 @@ class TranslateBehavior extends ModelBehavior { } } $associations[$association] = am($default, array('conditions' => array( - 'model' => $model->name, - 'field' => $field))); + 'model' => $model->name, + $RuntimeModel->displayField => $field))); } } @@ -408,21 +395,19 @@ class TranslateBehavior extends ModelBehavior { * fake field * * @param object instance of model - * @param mixed string with field or array(field1, field2=>AssocName, field3) + * @param mixed string with field, or array(field1, field2=>AssocName, field3), or null for unbind all original translations * @return boolean */ - function unbindTranslation(&$model, $fields) { + function unbindTranslation(&$model, $fields = null) { if (empty($fields)) { - return true; + return $this->unbindTranslation($model, $model->actsAs['Translate']); } if (is_string($fields)) { $fields = array($fields); } - $settings =& $this->settings[$model->name]; - $runtime =& $this->runtime[$model->name]['fields']; - - $default = array('className' => 'I18nModel', 'foreignKey' => 'row_id'); + $RuntimeModel =& $this->translateModel($model); + $default = array('className' => $RuntimeModel->name, 'foreignKey' => 'foreign_key'); $associations = array(); foreach ($fields as $key => $value) { @@ -434,16 +419,18 @@ class TranslateBehavior extends ModelBehavior { $association = $value; } - if (in_array($field, $settings)) { - $this->settings[$model->name] = array_diff_assoc($settings, array($field)); - } elseif (array_key_exists($field, $settings)) { - unset($settings[$field]); + if (array_key_exists($field, $this->settings[$model->name])) { + unset($this->settings[$model->name][$field]); + + } elseif (in_array($field, $this->settings[$model->name])) { + $this->settings[$model->name] = am(array_diff_assoc($this->settings[$model->name], array($field))); } - if (in_array($field, $runtime)) { - $this->runtime[$model->name]['fields'] = array_diff_assoc($runtime, array($field)); - } elseif (array_key_exists($field, $runtime)) { - unset($runtime[$field]); + if (array_key_exists($field, $this->runtime[$model->name]['fields'])) { + unset($this->runtime[$model->name]['fields'][$field]); + + } elseif (in_array($field, $this->runtime[$model->name]['fields'])) { + $this->runtime[$model->name]['fields'] = am(array_diff_assoc($this->runtime[$model->name]['fields'], array($field))); } if (!is_null($association) && (isset($model->hasMany[$association]) || isset($model->__backAssociation['hasMany'][$association]))) { @@ -457,21 +444,15 @@ class TranslateBehavior extends ModelBehavior { return true; } } +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { /** * @package cake * @subpackage cake.cake.libs.model.behaviors */ -class I18nContent extends AppModel { - var $name = 'I18nContent'; - var $useTable = 'i18n_content'; -} -/** - * @package cake - * @subpackage cake.cake.libs.model.behaviors - */ -class I18nModel extends AppModel { - var $name = 'I18nModel'; - var $useTable = 'i18n'; - var $belongsTo = array('Content' => array('className' => 'I18nContent', 'foreignKey' => 'i18n_content_id')); + class I18nModel extends AppModel { + var $name = 'I18nModel'; + var $useTable = 'i18n'; + var $displayField = 'field'; + } } ?> \ No newline at end of file diff --git a/cake/tests/cases/libs/model/behaviors/translate.test.php b/cake/tests/cases/libs/model/behaviors/translate.test.php new file mode 100644 index 000000000..2869fb00e --- /dev/null +++ b/cake/tests/cases/libs/model/behaviors/translate.test.php @@ -0,0 +1,302 @@ + + * Copyright 2005-2007, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2007, Cake Software Foundation, Inc. + * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests + * @package cake.tests + * @subpackage cake.tests.cases.libs.model.behaviors + * @since CakePHP(tm) v 1.2.0.5669 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { + define('CAKEPHP_UNIT_TEST_EXECUTION', 1); +} +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class TranslateTestModel extends CakeTestModel { + var $name = 'TranslateTestModel'; + var $useTable = 'i18n'; + var $displayField = 'field'; +} +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class TranslatedItem extends CakeTestModel { + var $name = 'TranslatedItem'; + var $cacheQueries = false; + var $actsAs = array('Translate' => array('content', 'title')); + var $translateModel = 'TranslateTestModel'; +} +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.cases.libs.model.behaviors + */ +class TranslateTest extends CakeTestCase { + var $fixtures = array('core.translated_item', 'core.translate'); + var $Model = null; + + function startCase() { + $this->db->fullDebug = true; + $this->Model =& new TranslatedItem(); + $this->I18nModel =& ClassRegistry::getObject('TranslateTestModel'); + } + + function testLocaleFalsePlain() { + $this->Model->locale = false; + + $result = $this->Model->read(null, 1); + $expected = array('TranslatedItem' => array('id' => 1, 'slug' => 'first_translated')); + $this->assertEqual($result, $expected); + + $result = $this->Model->findAll(null, array('slug')); + $expected = array( + array('TranslatedItem' => array('slug' => 'first_translated')), + array('TranslatedItem' => array('slug' => 'second_translated')), + array('TranslatedItem' => array('slug' => 'third_translated'))); + $this->assertEqual($result, $expected); + } + + function testLocaleFalseAssociations() { + $this->Model->locale = false; + $this->Model->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $this->Model->bindTranslation($translations, false); + + $result = $this->Model->read(null, 1); + $expected = array( + 'TranslatedItem' => array('id' => 1, 'slug' => 'first_translated'), + 'Title' => array( + array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), + array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), + array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1')), + 'Content' => array( + array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), + array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), + array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Obsah #1'))); + $this->assertEqual($result, $expected); + + $this->Model->hasMany['Title']['fields'] = $this->Model->hasMany['Content']['fields'] = array('content'); + $this->Model->hasMany['Title']['conditions']['locale'] = $this->Model->hasMany['Content']['conditions']['locale'] = 'eng'; + + $result = $this->Model->findAll(null, array('TranslatedItem.slug')); + $expected = array( + array('TranslatedItem' => array('id' => 1, 'slug' => 'first_translated'), + 'Title' => array(array('foreign_key' => 1, 'content' => 'Title #1')), + 'Content' => array(array('foreign_key' => 1, 'content' => 'Content #1'))), + array('TranslatedItem' => array('id' => 2, 'slug' => 'second_translated'), + 'Title' => array(array('foreign_key' => 2, 'content' => 'Title #2')), + 'Content' => array(array('foreign_key' => 2, 'content' => 'Content #2'))), + array('TranslatedItem' => array('id' => 3, 'slug' => 'third_translated'), + 'Title' => array(array('foreign_key' => 3, 'content' => 'Title #3')), + 'Content' => array(array('foreign_key' => 3, 'content' => 'Content #3')))); + $this->assertEqual($result, $expected); + + $this->Model->hasMany['Title']['fields'] = $this->Model->hasMany['Content']['fields'] = ''; + unset($this->Model->hasMany['Title']['conditions']['locale']); + unset($this->Model->hasMany['Content']['conditions']['locale']); + $this->Model->unbindTranslation($translations); + $this->Model->bindTranslation(null, false); + } + + function testLocaleSingle() { + $this->Model->locale = 'eng'; + + $result = $this->Model->read(null, 1); + $expected = array('TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1')); + $this->assertEqual($result, $expected); + + $result = $this->Model->findAll(); + $expected = array( + array('TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1')), + array('TranslatedItem' => array( + 'id' => 2, + 'slug' => 'second_translated', + 'locale' => 'eng', + 'title' => 'Title #2', + 'content' => 'Content #2')), + array('TranslatedItem' => array( + 'id' => 3, + 'slug' => 'third_translated', + 'locale' => 'eng', + 'title' => 'Title #3', + 'content' => 'Content #3'))); + $this->assertEqual($result, $expected); + } + + function testLocaleSingleAssociations() { + $this->Model->locale = 'eng'; + $this->Model->unbindTranslation(); + $translations = array('title' => 'Title', 'content' => 'Content'); + $this->Model->bindTranslation($translations, false); + + $result = $this->Model->read(null, 1); + $expected = array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'Title #1', + 'content' => 'Content #1'), + 'Title' => array( + array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), + array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), + array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1')), + 'Content' => array( + array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), + array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), + array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Obsah #1'))); + $this->assertEqual($result, $expected); + + $this->Model->hasMany['Title']['fields'] = $this->Model->hasMany['Content']['fields'] = array('content'); + $this->Model->hasMany['Title']['conditions']['locale'] = $this->Model->hasMany['Content']['conditions']['locale'] = 'eng'; + + $result = $this->Model->findAll(null, array('TranslatedItem.title')); + $expected = array( + array('TranslatedItem' => array('id' => 1, 'locale' => 'eng', 'title' => 'Title #1'), + 'Title' => array(array('foreign_key' => 1, 'content' => 'Title #1')), + 'Content' => array(array('foreign_key' => 1, 'content' => 'Content #1'))), + array('TranslatedItem' => array('id' => 2, 'locale' => 'eng', 'title' => 'Title #2'), + 'Title' => array(array('foreign_key' => 2, 'content' => 'Title #2')), + 'Content' => array(array('foreign_key' => 2, 'content' => 'Content #2'))), + array('TranslatedItem' => array('id' => 3, 'locale' => 'eng', 'title' => 'Title #3'), + 'Title' => array(array('foreign_key' => 3, 'content' => 'Title #3')), + 'Content' => array(array('foreign_key' => 3, 'content' => 'Content #3')))); + $this->assertEqual($result, $expected); + + $this->Model->hasMany['Title']['fields'] = $this->Model->hasMany['Content']['fields'] = ''; + unset($this->Model->hasMany['Title']['conditions']['locale']); + unset($this->Model->hasMany['Content']['conditions']['locale']); + + $this->Model->unbindTranslation($translations); + $this->Model->bindTranslation(array('title', 'content'), false); + } + + function testLocaleMultiple() { + $this->Model->locale = array('deu', 'eng', 'cze'); + + $delete = array( + array('locale' => 'deu'), + array('foreign_key' => 1, 'field' => 'title', 'locale' => 'eng'), + array('foreign_key' => 1, 'field' => 'content', 'locale' => 'cze'), + array('foreign_key' => 2, 'field' => 'title', 'locale' => 'cze'), + array('foreign_key' => 2, 'field' => 'content', 'locale' => 'eng'), + array('foreign_key' => 3, 'field' => 'title')); + $this->I18nModel->deleteAll(array('or' => $delete)); + + $result = $this->Model->read(null, 1); + $expected = array( + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'deu', + 'title' => 'Titulek #1', + 'content' => 'Content #1')); + $this->assertEqual($result, $expected); + + $result = $this->Model->findAll(null, array('slug', 'title', 'content')); + $expected = array( + array('TranslatedItem' => array( + 'slug' => 'first_translated', + 'locale' => 'deu', + 'title' => 'Titulek #1', + 'content' => 'Content #1')), + array('TranslatedItem' => array( + 'slug' => 'second_translated', + 'locale' => 'deu', + 'title' => 'Title #2', + 'content' => 'Obsah #2')), + array('TranslatedItem' => array( + 'slug' => 'third_translated', + 'locale' => 'deu', + 'title' => '', + 'content' => 'Content #3'))); + $this->assertEqual($result, $expected); + } + + function testReadSelectedFields() { + $this->Model->locale = 'eng'; + + $result = $this->Model->findAll(null, array('slug', 'TranslatedItem.content')); + $expected = array( + array('TranslatedItem' => array('slug' => 'first_translated', 'locale' => 'eng', 'content' => 'Content #1')), + array('TranslatedItem' => array('slug' => 'second_translated', 'locale' => 'eng', 'content' => 'Content #2')), + array('TranslatedItem' => array('slug' => 'third_translated', 'locale' => 'eng', 'content' => 'Content #3'))); + $this->assertEqual($result, $expected); + + $result = $this->Model->findAll(null, array('TranslatedItem.slug', 'content')); + $this->assertEqual($result, $expected); + + $this->Model->locale = array('eng', 'deu', 'cze'); + $delete = array(array('locale' => 'deu'), array('field' => 'content', 'locale' => 'eng')); + $this->I18nModel->deleteAll(array('or' => $delete)); + + $result = $this->Model->findAll(null, array('title', 'content')); + $expected = array( + array('TranslatedItem' => array('locale' => 'eng', 'title' => 'Title #1', 'content' => 'Obsah #1')), + array('TranslatedItem' => array('locale' => 'eng', 'title' => 'Title #2', 'content' => 'Obsah #2')), + array('TranslatedItem' => array('locale' => 'eng', 'title' => 'Title #3', 'content' => 'Obsah #3'))); + $this->assertEqual($result, $expected); + } + + function testSaveCreate() { + $this->Model->locale = 'spa'; + $data = array('slug' => 'fourth_translated', 'title' => 'Leyenda #4', 'content' => 'Contenido #4'); + $this->Model->create($data); + $this->Model->save(); + $result = $this->Model->read(); + $expected = array('TranslatedItem' => am($data, array('id' => $this->Model->id, 'locale' => 'spa'))); + $this->assertEqual($result, $expected); + } + + function testSaveUpdate() { + $this->Model->locale = 'spa'; + $oldData = array('slug' => 'fourth_translated', 'title' => 'Leyenda #4'); + $this->Model->create($oldData); + $this->Model->save(); + $id = $this->Model->id; + $newData = array('id' => $id, 'content' => 'Contenido #4'); + $this->Model->create($newData); + $this->Model->save(); + $result = $this->Model->read(null, $id); + $expected = array('TranslatedItem' => am($oldData, $newData, array('locale' => 'spa'))); + $this->assertEqual($result, $expected); + } +} +?> \ No newline at end of file diff --git a/cake/tests/fixtures/translate_fixture.php b/cake/tests/fixtures/translate_fixture.php new file mode 100644 index 000000000..b6622cbb7 --- /dev/null +++ b/cake/tests/fixtures/translate_fixture.php @@ -0,0 +1,65 @@ + + * Copyright 2005-2007, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2007, Cake Software Foundation, Inc. + * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests + * @package cake.tests + * @subpackage cake.tests.fixtures + * @since CakePHP(tm) v 1.2.0.5669 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.fixtures + */ +class TranslateFixture extends CakeTestFixture { + var $name = 'Translate'; + var $table = 'i18n'; + var $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary', 'extra'=> 'auto_increment'), + 'locale' => array('type' => 'string', 'length' => 6, 'null' => false), + 'model' => array('type' => 'string', 'null' => false), + 'foreign_key' => array('type' => 'integer', 'null' => false), + 'field' => array('type' => 'string', 'null' => false), + 'content' => array('type' => 'text')); + var $records = array( + array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), + array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Content #1'), + array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), + array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Inhalt #1'), + array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titulek #1'), + array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'content', 'content' => 'Obsah #1'), + array('id' => 7, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title #2'), + array('id' => 8, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Content #2'), + array('id' => 9, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Titel #2'), + array('id' => 10, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Inhalt #2'), + array('id' => 11, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Titulek #2'), + array('id' => 12, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 2, 'field' => 'content', 'content' => 'Obsah #2'), + array('id' => 13, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title #3'), + array('id' => 14, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Content #3'), + array('id' => 15, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Titel #3'), + array('id' => 16, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Inhalt #3'), + array('id' => 17, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Titulek #3'), + array('id' => 18, 'locale' => 'cze', 'model' => 'TranslatedItem', 'foreign_key' => 3, 'field' => 'content', 'content' => 'Obsah #3')); +} +?> \ No newline at end of file diff --git a/cake/tests/fixtures/translated_item_fixture.php b/cake/tests/fixtures/translated_item_fixture.php new file mode 100644 index 000000000..ab1963e0f --- /dev/null +++ b/cake/tests/fixtures/translated_item_fixture.php @@ -0,0 +1,45 @@ + + * Copyright 2005-2007, Cake Software Foundation, Inc. + * 1785 E. Sahara Avenue, Suite 490-204 + * Las Vegas, Nevada 89104 + * + * Licensed under The Open Group Test Suite License + * Redistributions of files must retain the above copyright notice. + * + * @filesource + * @copyright Copyright 2005-2007, Cake Software Foundation, Inc. + * @link https://trac.cakephp.org/wiki/Developement/TestSuite CakePHP(tm) Tests + * @package cake.tests + * @subpackage cake.tests.fixtures + * @since CakePHP(tm) v 1.2.0.5669 + * @version $Revision$ + * @modifiedby $LastChangedBy$ + * @lastmodified $Date$ + * @license http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License + */ +/** + * Short description for class. + * + * @package cake.tests + * @subpackage cake.tests.fixtures + */ +class TranslatedItemFixture extends CakeTestFixture { + var $name = 'TranslatedItem'; + var $fields = array( + 'id' => array('type' => 'integer', 'key' => 'primary', 'extra'=> 'auto_increment'), + 'slug' => array('type' => 'string', 'null' => false)); + var $records = array( + array('id' => 1, 'slug' => 'first_translated'), + array('id' => 2, 'slug' => 'second_translated'), + array('id' => 3, 'slug' => 'third_translated')); +} +?> \ No newline at end of file