Index: modules/translation/translation.module =================================================================== RCS file: /cvs/drupal/drupal/modules/translation/translation.module,v retrieving revision 1.38 diff -u -r1.38 translation.module --- modules/translation/translation.module 22 Jan 2009 03:11:54 -0000 1.38 +++ modules/translation/translation.module 22 Feb 2009 23:46:52 -0000 @@ -359,3 +359,68 @@ } } } + +/** + * @ingroup field + * @{ + * Attach and process field translations to Drupal objects. + */ + +/** + * Implementation of hook_field_attach_load(). + * + * For multilingual fields, populate the value for each + * $object->field_name[$delta] with the best match for the current language + * or the site default. Since hook_field_attach_load() is not persistently + * cached, we can add language specific information here in the knowledge + * it will only affect the curent request. Field rendering and formatting is + * agnostic to whether a field is multilingual or not, however we leave the + * #languages array in situ to allow other modules to access it if necessary. + */ +function translation_field_attach_load($obj_type, $object) { + global $language; + + // Only operate on translatable fields. + $fields = array(); + list(, , $bundle) = field_attach_extract_ids($obj_type, $object); + $instances = field_info_instances($bundle); + foreach ($instances as $instance) { + $field = field_info_field($instance['field_name']); + if (!empty($field['translatable'])) { + $fields[] = $instance['field_name']; + } + } + if (empty($fields)) { + return; + } + + $default = language_default(); + foreach ($fields as $field_name) { + foreach ($object->$field_name as $delta => $item) { + $best_fit = array(); + // Use current language, if available. + if (isset($item['#languages'][$language->language])) { + $best_fit = $item['#languages'][$language->language]; + } + // Use the source translation language. + elseif (isset($object->language_default) && isset($item['#languages'][$object->language_default])) { + $best_fit = $item['#languages'][$object->language_default]; + } + // Use default language, if available. + elseif (isset($item['#languages'][$default->language])) { + $best_fit = $item['#languages'][$default->language]; + } + // Use language neutral values, if available. + elseif (isset($item['#languages'][''])) { + $best_fit = $item['#languages']['']; + } + + $object->{$field_name}[$delta] = array_merge($item, $best_fit); + } + } +} + +/** + * @} End of "ingroup field". + */ + Index: modules/field/field.install =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.install,v retrieving revision 1.3 diff -u -r1.3 field.install --- modules/field/field.install 11 Feb 2009 04:45:57 -0000 1.3 +++ modules/field/field.install 22 Feb 2009 23:46:51 -0000 @@ -53,6 +53,12 @@ 'not null' => TRUE, 'default' => 0, ), + 'translatable' => array( + 'type' => 'int', + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 0, + ), 'active' => array( 'type' => 'int', 'size' => 'tiny', @@ -116,6 +122,21 @@ 'widget_type' => array('widget_type'), ), ); + + $schema['field_object_language'] = array( + 'fields' => array( + 'type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, + 'description' => 'The name of a translatable object.'), + 'id' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, + 'description' => 'The id of the translatable object.', + ), + 'default' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, + 'description' => 'The source language of the translatable object.', + ), + ), + 'primary key' => array('type', 'id'), + ); + $schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache'); return $schema; Index: modules/field/field.attach.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.attach.inc,v retrieving revision 1.3 diff -u -r1.3 field.attach.inc --- modules/field/field.attach.inc 10 Feb 2009 03:16:14 -0000 1.3 +++ modules/field/field.attach.inc 22 Feb 2009 23:46:51 -0000 @@ -219,8 +219,10 @@ $queried_objects[$id] = $objects[$id]; } } + // Fetch other nodes from the database. if ($queried_objects) { + // We need the raw additions to be able to cache them, so // content_storage_load() and hook_field_load() must not alter // nodes directly but return their additions. @@ -230,7 +232,32 @@ $queried_objects[$id]->$key = $value; } } - + + // Fetch source language for translatable objects. + // @todo Performance optimization; cache field_info_instances() per bundle. + $translatable = FALSE; + foreach ($queried_objects as $id => $object) { + list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object); + // Check if any translatable field is defined for the current object. + $instances = field_info_instances($bundle); + foreach ($instances as $instance) { + $field = field_info_field($instance['field_name']); + if ($field['translatable']) { + $translatable = TRUE; + break; + } + } + } + + // Load the default language and store it as an addition in order to cache it. + if ($translatable) { + $result = db_query("SELECT id, `default` FROM {field_object_language} WHERE type = :type AND id IN (:ids)", array(':type' => $obj_type, ':ids' => array_keys($queried_objects))); + foreach ($result as $row) { + $queried_objects[$row->id]->language_default = $row->default; + $additions[$row->id]['language_default'] = $row->default; + } + } + // TODO D7 : to be consistent we might want to make hook_field_load() accept // multiple objects too. Which forbids going through _field_invoke(), but // requires manually iterating the instances instead. @@ -252,15 +279,6 @@ $additions[$id][$key] = $value; } - // Let other modules act on loading the object. - // TODO : this currently doesn't get cached (we cache $additions). - // This should either be called after we fetch from cache, or return an - // array of additions. - foreach (module_implements('field_attach_load') as $module) { - $function = $module . '_field_attach_load'; - $function($obj_type, $queried_objects[$id]); - } - // Cache the data. if ($cacheable) { $cid = "field:$obj_type:$id:$vid"; @@ -269,6 +287,14 @@ } } } + + // Let other modules act on loading the objects. + foreach ($objects as $object) { + foreach (module_implements('field_attach_load') as $module) { + $function = $module . '_field_attach_load'; + $function($obj_type, $object); + } + } } /** Index: modules/field/field.crud.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.crud.inc,v retrieving revision 1.4 diff -u -r1.4 field.crud.inc --- modules/field/field.crud.inc 10 Feb 2009 03:16:14 -0000 1.4 +++ modules/field/field.crud.inc 22 Feb 2009 23:46:51 -0000 @@ -48,6 +48,8 @@ * - cardinality (integer) * The number of values the field can hold. Legal values are any * positive integer or FIELD_CARDINALITY_UNLIMITED. + * - translatable (integer) + * Whether the field is translatable * - locked (integer) * TODO: undefined. * - module (string, read-only) @@ -195,6 +197,7 @@ $field += array( 'cardinality' => 1, + 'translatable' => 0, 'locked' => FALSE, 'settings' => array(), ); Index: modules/field/modules/field_sql_storage/field_sql_storage.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/field_sql_storage/field_sql_storage.module,v retrieving revision 1.4 diff -u -r1.4 field_sql_storage.module --- modules/field/modules/field_sql_storage/field_sql_storage.module 10 Feb 2009 03:16:14 -0000 1.4 +++ modules/field/modules/field_sql_storage/field_sql_storage.module 22 Feb 2009 23:46:52 -0000 @@ -128,9 +128,17 @@ 'not null' => TRUE, 'description' => 'The sequence number for this data item, used for multi-value fields', ), + // @todo Consider an integer field for 'language'. + 'language' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The language for this data item.', + ), ), - 'primary key' => array('etid', 'entity_id', 'deleted', 'delta'), - // TODO : index on 'bundle' + 'primary key' => array('etid', 'entity_id', 'deleted', 'delta', 'language'), + // @todo Index on 'bundle'. ); // Add field columns. @@ -144,7 +152,7 @@ $revision = $current; $revision['description'] = 'Revision archive storage for field ' . $field['field_name']; $revision['revision_id']['description'] = 'The entity revision id this data is attached to'; - $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta'); + $revision['primary key'] = array('etid', 'revision_id', 'deleted', 'delta', 'language'); return array( _field_sql_storage_tablename($field['field_name']) => $current, @@ -212,7 +220,7 @@ ->execute(); foreach ($results as $row) { - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) { + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || !empty($field['translatable']) || $delta_count[$row->entity_id][$field_name] < $field['cardinality']) { $item = array(); // For each column declared by the field, populate the item // from the prefixed database column. @@ -220,8 +228,16 @@ $item[$column] = $row->{_field_sql_storage_columnname($field_name, $column)}; } - // Add the item to the field values for the entity. - $additions[$row->entity_id][$field_name][] = $item; + // For translatable fields, put the multilingual values for each delta + // into a '#languages' property. This information can be safely cached + // in the field and object caches. + if ($field['translatable']) { + $additions[$row->entity_id][$field_name][$row->delta]['#languages'][$row->language] = $item; + } + else { + // Add the item to the field values for the entity. + $additions[$row->entity_id][$field_name][] = $item; + } $delta_count[$row->entity_id][$field_name]++; } } @@ -256,7 +272,7 @@ if ($object->$field_name) { // Prepare the multi-insert query. - $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta'); + $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); foreach ($field['columns'] as $column => $attributes) { $columns[] = _field_sql_storage_columnname($field_name, $column); } @@ -273,6 +289,7 @@ 'revision_id' => $vid, 'bundle' => $bundle, 'delta' => $delta, + 'language' => !empty($item->language) ? $item->language : '', ); foreach ($field['columns'] as $column => $attributes) { $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL; @@ -282,7 +299,7 @@ $revision_query->values($record); } - if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { + if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && empty($field['translatable']) && ++$delta_count == $field['cardinality']) { break; } } @@ -385,4 +402,4 @@ ->condition('bundle', $bundle_old) ->execute(); } -} \ No newline at end of file +}