Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.246
diff -u -r1.246 locale.module
--- modules/locale/locale.module 2 Aug 2009 15:44:08 -0000 1.246
+++ modules/locale/locale.module 3 Aug 2009 18:19:37 -0000
@@ -312,6 +312,7 @@
'#value' => $default->language
);
}
+ $form['#submit'][] = 'locale_field_node_form_submit';
}
}
Index: modules/locale/locale.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.info,v
retrieving revision 1.11
diff -u -r1.11 locale.info
--- modules/locale/locale.info 8 Jun 2009 09:23:52 -0000 1.11
+++ modules/locale/locale.info 3 Aug 2009 18:19:36 -0000
@@ -6,4 +6,5 @@
core = 7.x
files[] = locale.module
files[] = locale.install
+files[] = locale.field.inc
files[] = locale.test
Index: modules/entity_translation/entity_translation.info
===================================================================
RCS file: modules/entity_translation/entity_translation.info
diff -N modules/entity_translation/entity_translation.info
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/entity_translation/entity_translation.info 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,9 @@
+; $Id$
+name = Entity translation API
+description = Expose an API to allow fieldable entities to be translated into different languages.
+dependencies[] = locale
+package = Core
+version = VERSION
+core = 7.x
+files[] = entity_translation.module
+files[] = entity_translation.install
Index: modules/entity_translation/entity_translation.module
===================================================================
RCS file: modules/entity_translation/entity_translation.module
diff -N modules/entity_translation/entity_translation.module
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/entity_translation/entity_translation.module 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,116 @@
+fields('et')
+ ->condition('etid', $etid)
+ ->condition('entity_id', $id)
+ ->orderBy('created')
+ ->execute();
+
+ $translations = array();
+ foreach ($results as $row) {
+ $translations[$row->language] = $row;
+ }
+
+ // The source translation is the first one created.
+ list ($source, ) = each($translations);
+ return (object) array('source' => $source, 'data' => $translations);
+}
+
+function entity_translation_save_translation($obj_type, $object, $translation) {
+ if (isset($translation['source']) && $translation['language'] == $translation['source']) {
+ throw new Exception('Invalid translation language');
+ }
+
+ // Save field translations.
+ field_attach_update($obj_type, $object);
+
+ $obj_info = field_info_fieldable_types($obj_type);
+ $translations = isset($object->{$obj_info['translations key']}) ? $object->{$obj_info['translations key']} : NULL;
+ $language = $translation['language'];
+
+ if (isset($translations->data[$language])) {
+// unset($translation['source']);
+ $translation = array_merge((array) $translations->data[$language], $translation);
+ $translation['changed'] = REQUEST_TIME;
+ _entity_translation_write_translation($obj_type, $object, $translation);
+ }
+ else {
+ _entity_translation_write_translation($obj_type, $object, $translation, TRUE);
+ }
+}
+
+function entity_translation_delete_translation($obj_type, $object, $language = NULL) {
+ $etid = _field_sql_storage_etid($obj_type);
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+
+ $query = db_delete('entity_translation')
+ ->condition('etid', $etid)
+ ->condition('entity_id', $id);
+
+ if (!empty($language)) {
+ // Remove field translations.
+ foreach (field_info_instances($bundle) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ if ($field['translatable']) {
+ $object->{$field_name}[$language] = array();
+ }
+ }
+ field_attach_update($obj_type, $object);
+
+ $query->condition('language', $language);
+ }
+
+ return $query->execute();
+}
+
+function entity_translation_change_source_language($obj_type, $object, $language) {
+ $etid = _field_sql_storage_etid($obj_type);
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ $obj_info = field_info_fieldable_types($obj_type);
+ $translations = isset($object->{$obj_info['translations key']}) ? $object->{$obj_info['translations key']} : NULL;
+ return db_update('entity_translation')
+ ->fields(array('language'=> $language))
+ ->condition('etid', $etid)
+ ->condition('entity_id', $id)
+ ->condition('language', $translations->source)
+ ->execute();
+}
+
+function entity_translation_flag_outdated($obj_type, $object) {
+ $etid = _field_sql_storage_etid($obj_type);
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ $obj_info = field_info_fieldable_types($obj_type);
+ $translations = isset($object->{$obj_info['translations key']}) ? $object->{$obj_info['translations key']} : NULL;
+ return db_update('entity_translation')
+ ->fields(array('translate' => TRUE))
+ ->condition('etid', $etid)
+ ->condition('entity_id', $id)
+ ->condition('language', $translations->source, '<>')
+ ->execute();
+}
+
+function _entity_translation_write_translation($obj_type, $object, $translation, $create = FALSE) {
+ global $user;
+ list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+
+ $translation = $translation + array(
+ 'etid' => _field_sql_storage_etid($obj_type),
+ 'entity_id' => $id,
+ 'source' => '',
+ 'uid' => $user->uid,
+ 'translate' => FALSE,
+ 'status' => FALSE,
+ 'created' => REQUEST_TIME,
+ 'changed' => REQUEST_TIME,
+ );
+
+ $primary_keys = $create ? NULL : array('etid', 'entity_id', 'language');
+ return drupal_write_record('entity_translation', $translation, $primary_keys);
+}
Index: modules/translation_field/translation_field.module
===================================================================
RCS file: modules/translation_field/translation_field.module
diff -N modules/translation_field/translation_field.module
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/translation_field/translation_field.module 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,299 @@
+ 'Field Translation',
+ 'page callback' => 'translation_field_node_overview',
+ 'page arguments' => array(1),
+ 'access callback' => '_translation_field_tab_access',
+ 'access arguments' => array(1),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 2,
+ );
+ $items['node/%node/field-translation/edit/%/%'] = array(
+ 'title' => 'Field Translation',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('translation_field_translation_form', 1, 4, 5),
+ 'access callback' => '_translation_field_tab_access',
+ 'access arguments' => array(1),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+function translation_field_fieldable_info_alter(&$info) {
+ $info['node']['translation_handlers']['translation_field'] = TRUE;
+ $info['node']['translations key'] = 'translations';
+}
+
+function translation_field_permission() {
+ return array(
+ 'translate node fields' => array(
+ 'title' => t('Translate node fields'),
+ 'description' => t('Translate website node field content.'),
+ ),
+ );
+}
+
+function translation_field_form_alter(&$form, $form_state) {
+ if ($form['#id'] == 'node-form' && isset($form['#node']->translations)) {
+ $translations = $form['#node']->translations;
+
+ // We need $node->translations on node save.
+ $form['translations'] = array(
+ '#type' => 'value',
+ '#value' => $translations,
+ );
+
+ $form['previous_language'] = array(
+ '#type' => 'value',
+ '#value' => $form['#node']->language,
+ );
+
+ // Remove existing translation language codes from the node language selector.
+ foreach ($translations->data as $language => $translation) {
+ if ($language != $translations->source) {
+ unset($form['language']['#options'][$language]);
+ }
+ }
+
+ $form['field_translation'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Field translation'),
+ '#collapsible' => TRUE,
+ '#group' => 'additional_settings',
+ '#tree' => TRUE,
+ '#weight' => 100,
+ );
+ $form['field_translation']['retranslate'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Flag translations as outdated'),
+ '#default_value' => 0,
+ '#description' => t('If you made a significant change, which means translations should be updated, you can flag all translations of this post as outdated. This will not change any other property of those posts, like whether they are published or not.'),
+ );
+ }
+}
+
+function translation_field_translation_form($form_state, $node, $source, $language) {
+
+ $source = field_multilingual_valid_language($source);
+ $language = field_multilingual_valid_language($language);
+ $languages = language_list('language', LANGUAGE_TYPE_CONTENT);
+ drupal_set_title($node->title . ' [' . t('!language translation', array('!language' => t($languages[$language]->name))) . ']');
+
+ $form = array(
+ '#node' => $node,
+ '#source' => $source,
+ '#language' => $language,
+ );
+
+ $form['translation'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Translation settings'),
+ '#access' => user_access('translate content'),
+ '#collapsible' => TRUE,
+ '#collapsed' => !$node->translate,
+ '#tree' => TRUE,
+ '#weight' => -4,
+ );
+ $form['translation']['status'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('This translation is published'),
+ '#default_value' => isset($node->translations->data[$language]) && $node->translations->data[$language]->status,
+ '#description' => t('When this option is unchecked, this translation will not be visible for non-administrators.'),
+ );
+ $form['translation']['translate'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('This translation needs to be updated'),
+ '#default_value' => $translate = isset($node->translations->data[$language]) && $node->translations->data[$language]->translate,
+ '#description' => t('When this option is checked, this translation needs to be updated because the source post has changed. Uncheck when the translation is up to date again.'),
+ '#disabled' => !$translate,
+ );
+
+ field_attach_form('node', $node, $form, $form_state, $language);
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save translation'),
+ );
+
+ $form['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete translation'),
+ );
+
+ return $form;
+}
+
+function translation_field_translation_form_submit($form, &$form_state) {
+ $node = (object) array_merge((array) $form['#node'], $form_state['values']);
+ if ($form_state['clicked_button']['#value'] == t('Save translation')) {
+ $translation = array(
+ 'translate' => $node->translation['translate'],
+ 'status' => $node->translation['status'],
+ 'language' => $form['#language'],
+ 'source' => $form['#source'],
+ );
+ entity_translation_save_translation('node', $node, $translation, $node->translations);
+ }
+ else {
+ entity_translation_delete_translation('node', $node, $form['#language']);
+ }
+ $form_state['redirect'] = 'node/' . $node->nid . '/field-translation';
+}
+
+function translation_field_node_load(&$nodes, $types) {
+ // TODO: entity_translation_get_all (multiple), translation_supported_type($type)
+ foreach ($nodes as $delta => $node) {
+ $nodes[$delta]->translations = entity_translation_get_all('node', $node);
+ }
+}
+
+function translation_field_node_insert($node) {
+ _translation_field_create_source_translation($node);
+}
+
+function translation_field_node_update($node) {
+ // Only create a translation on edit if the translation set is empty:
+ // the node might have been created with language set to "language neutral".
+ if (empty($node->translations->data)) {
+ _translation_field_create_source_translation($node);
+ }
+ elseif (!empty($node->language)) {
+ if ($node->previous_language != $node->language) {
+ entity_translation_change_source_language('node', $node, $node->language);
+ }
+ if (!empty($node->field_translation['retranslate'])) {
+ entity_translation_flag_outdated('node', $node);
+ }
+ }
+}
+
+function translation_field_node_delete($node) {
+ entity_translation_delete_translation('node', $node);
+}
+
+function _translation_field_create_source_translation($node) {
+ if (!empty($node->language)) {
+ $translation = array(
+ 'language' => $node->language,
+ 'status' => $node->status,
+ );
+ entity_translation_save_translation('node', $node, $translation);
+ }
+}
+
+function translation_field_node_overview($node) {
+ $translations = $node->translations->data;
+ $header = array(t('Language'), t('Title'), t('Status'), t('Operations'));
+ $languages = language_list('language', LANGUAGE_TYPE_CONTENT);
+ $source = isset($_GET['source']) ? field_multilingual_valid_language($_GET['source']) : $node->translations->source;
+ $source_selector = FALSE;
+
+ foreach ($languages as $language) {
+ $options = array();
+ $language_name = $language->name;
+ $langcode = $language->language;
+ if (isset($translations[$langcode])) {
+ // Existing translation in the translation set: display status.
+ $is_source = $langcode == $node->translations->source;
+ $translation = $translations[$langcode];
+ $path = 'node/' . $node->nid . '/field-translation/edit/' . $translation->source . '/' . $langcode;
+ // TODO: title as translatable field and content language negotiation
+ $title = l(t('view'), 'node/'. $node->nid, array('query' => 'content_language=' . $langcode));
+ if (node_access('update', $node)) {
+ $options[] = l(t('edit'), $is_source ? 'node/'. $node->nid . '/edit' : $path);
+ }
+ $status = $translation->status ? t('Published') : t('Not published');
+ $status .= $translation->translate ? ' - ' . t('outdated') . '' : '';
+ if ($is_source) {
+ $language_name = t('@language_name (source)', array('@language_name' => $language_name));
+ }
+ }
+ else {
+ // No such translation in the set yet: help user to create it.
+ $title = t('n/a');
+ $path = 'node/' . $node->nid . '/field-translation/edit/' . $source . '/' . $langcode;
+ if (node_access('create', $node) && $source != $langcode) {
+ $options[] = l(t('add translation'), $path);
+ }
+ $status = t('Not translated');
+ $source_selector = TRUE;
+ }
+ $rows[] = array($language_name, $title, $status, implode(" | ", $options));
+ }
+
+ drupal_set_title(t('Translations of %title', array('%title' => $node->title)), PASS_THROUGH);
+
+ $build['translation_node_overview'] = array();
+ if ($source_selector) {
+ $build['translation_node_overview']['source_language_form'] = drupal_get_form('translation_field_source_language_form', $source);
+ }
+ $build['translation_node_overview']['translation_table'] = array(
+ '#theme' => 'table',
+ '#header' => $header,
+ '#rows' => $rows,
+ );
+
+ return $build;
+}
+
+function translation_field_source_language_form($form_state, $source) {
+ $form = array(
+ '#method' => 'GET',
+ 'source' => array(
+ '#type' => 'select',
+ '#title' => t('Source translation language'),
+ '#default_value' => $source,
+ '#options' => array(),
+ ),
+ 'submit' => array(
+ '#type' => 'submit',
+ '#value' => t('Submit'),
+ ),
+ );
+
+ foreach (language_list('language', LANGUAGE_TYPE_CONTENT) as $language) {
+ $form['source']['#options'][$language->language] = t($language->name);
+ }
+
+ return $form;
+}
+
+function translation_field_content_language_alter(&$content_language) {
+ if (isset($_GET['content_language'])) {
+ $content_language = field_multilingual_valid_language($_GET['content_language']);
+ }
+}
+
+function translation_field_node_view($node, $build_mode) {
+ if (!empty($node->translations)) {
+ $translations = $node->translations->data;
+ $languages = language_list('language', LANGUAGE_TYPE_CONTENT);
+ global $language;
+ foreach ($languages as $langcode => $lang) {
+ // TODO: && langcode !=
+ if (isset($translations[$langcode]) ) {
+ $links["node_translation_$langcode"] = array(
+ 'title' => $lang->native,
+ 'href' => 'node/' . $node->nid,
+ 'language' => $language,
+ // TODO: 'title' => $translations[$langcode]->title
+ 'attributes' => array('class' => 'translation-link'),
+ 'query' => 'content_language=' . $langcode,
+ );
+ $node->content['links']['translation'] = array(
+ '#theme' => 'links',
+ '#links' => $links,
+ '#attributes' => array('class' => 'links inline'),
+ );
+ }
+ }
+ }
+}
+
+function _translation_field_tab_access($node) {
+ return !empty($node->language) && user_access('translate node fields');
+}
Index: modules/locale/locale.field.inc
===================================================================
RCS file: modules/locale/locale.field.inc
diff -N modules/locale/locale.field.inc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/locale/locale.field.inc 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,49 @@
+language) ? FIELD_LANGUAGE_NONE : $node->language;
+ list(, , $bundle) = field_attach_extract_ids('node', $node);
+ foreach (field_info_instances($bundle) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ $previous_language = $form[$field_name]['#language'];
+ // Handle a possible language change: previous language values are deleted, new ones are inserted.
+ if ($field['translatable'] && $previous_language != $selected_language) {
+ $form_state['values'][$field_name][$selected_language] = $node->{$field_name}[$previous_language];
+ if ($reset_previous) {
+ $form_state['values'][$field_name][$previous_language] = array();
+ }
+ }
+ }
+}
+
+// TODO: (hook disabled) implement language negotiation and fallback
+function __locale_field_attach_form($obj_type, $object, &$form, &$form_state) {
+ if (field_multilingual_check_translation_handler($obj_type, 'locale')) {
+ list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+ foreach (field_info_instances($bundle) as $instance) {
+ $field_name = $instance['field_name'];
+ $field = field_info_field($field_name);
+ $field_language = $form[$field_name]['#language'];
+ if (empty($form[$field_name][$field_language][0]['#default_value']) && isset($object->{$field_name}[FIELD_LANGUAGE_NONE])) {
+ unset($form[$field_name]);
+ $items = $object->{$field_name}[FIELD_LANGUAGE_NONE];
+ $form += field_default_form($obj_type, $object, $field, $instance, FIELD_LANGUAGE_NONE, $items, $form, $form_state);
+ }
+ }
+ }
+}
Index: modules/entity_translation/entity_translation.install
===================================================================
RCS file: modules/entity_translation/entity_translation.install
diff -N modules/entity_translation/entity_translation.install
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/entity_translation/entity_translation.install 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,87 @@
+ 'Table to track entity translations',
+ 'fields' => array(
+ 'etid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The entity type id this translation relates to',
+ ),
+ 'entity_id' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => 'The entity id this translation relates to',
+ ),
+ // TODO: Consider an integer field for 'language'.
+ 'language' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The language for this translation.',
+ ),
+ 'source' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The source language from which this translation was created.',
+ ),
+ 'uid' => array(
+ 'description' => 'The author of this translation.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'status' => array(
+ 'description' => 'Boolean indicating whether the translation is published (visible to non-administrators).',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 1,
+ ),
+ 'translate' => array(
+ 'description' => 'A boolean indicating whether this translation needs to be updated.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'created' => array(
+ 'description' => 'The Unix timestamp when the translation was created.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'changed' => array(
+ 'description' => 'The Unix timestamp when the translation was most recently saved.',
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('etid', 'entity_id', 'language'),
+ );
+
+ return $schema;
+}
Index: modules/translation_field/translation_field.info
===================================================================
RCS file: modules/translation_field/translation_field.info
diff -N modules/translation_field/translation_field.info
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/translation_field/translation_field.info 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,8 @@
+; $Id: translation.info,v 1.5 2009/06/08 09:23:54 dries Exp $
+name = Field-based content translation
+description = Allows node fields to be translated into different languages.
+dependencies[] = entity_translation
+package = Core
+version = VERSION
+core = 7.x
+files[] = translation_field.module