This is a follow-up in response to #1188388-104: Entity translation UI in core

Problem

The core image field widget allows to enter alt and title along while uploading an image file. In many cases a desirable behavior, when translating content with image fields, is keeping the images and just translate their textual metadata. Currently when creating a translation the original image is preserved, but subsequent changes, such as uploading a new image for a certain language or change the image order in case of a gallery, are not reflected in the translations.

Proposed resolution

Introduce a field column synchronization capability which, once enabled, lets the field define which columns should be "shared" across translations, and which ones are allowed to change per-language. The synchronization takes care of propagating any change in the field items order and in the values themselves to all the available translations. This functionality is provided through a generic API which in core is exploited by the file and image fields.

Remaining tasks

  • Port the functionality.
  • add steps to test in #68
  • (novice) add screenshot of before the patch in #69
  • (novice) add screenshot of after the patch in #70
  • (novice) review patch. Review Task doc: http://drupal.org/node/1488992 done by @fago and @yched
  • (novice) manually test the patch. Manually Test Task doc: http://drupal.org/node/1489010 done in #72
  • (novice) Improve patch coding standards and documentation http://drupal.org/node/1487976 done in #39
  • decide on what follow-ups to do
  • final? review to rtbc

User interface changes

A checkbox to enable this functionality is added in the field instance settings. See #110 and #120 (#69 and #70).

API changes

A small Field API addition has been performed to allow declaring which field columns must be synchronized across translations.

CommentFileSizeAuthor
#120 sync-s02-confirm-label_and_invisible-2013-02-16_2157.png122.45 KBYesCT
#120 sync-s03-2013-02-16_2237.png119.84 KBYesCT
#117 et-sync-1807692-117.interdiff.do_not_test.patch7.17 KBplach
#117 et-sync-1807692-117.patch50.83 KBplach
#110 et-sync-1807692-110.interdiff.do_not_test.patch40.16 KBplach
#110 et-sync-1807692-110.patch50.87 KBplach
#110 et-sync-1807692-110-1.png21.79 KBplach
#110 et-sync-1807692-110-2.png21.03 KBplach
#110 et-sync-1807692-110-3.png26.82 KBplach
#110 et-sync-1807692-110-4.png29.51 KBplach
#103 et-sync-1807692-103.png4.65 KBplach
#103 et-sync-1807692-103.interdiff.do_not_test.patch1.43 KBplach
#103 et-sync-1807692-103.patch33.85 KBplach
#91 et-sync-1807692-91.patch33.85 KBplach
#88 et-sync-1807692-88.interdiff.do_not_test.patch1.96 KBplach
#88 et-sync-1807692-88.patch80.96 KBplach
#85 et-sync-1807692-85.interdiff.do_not_test.patch11.56 KBplach
#85 et-sync-1807692-85.patch33.84 KBplach
#79 et-sync-1807692-79.patch35.26 KBplach
#75 et-sync-1807692-75.patch35.26 KBplach
#75 et-sync-1807692-75.interdiff.do_not_test.patch10.88 KBplach
#74 et-sync-1807692-74.patch28.25 KBYesCT
#74 interdiff-63-74.txt1.1 KBYesCT
#70 sync-s01-create-article-2013-01-31_0302.png132.55 KBYesCT
#70 sync-s01b-af-2013-01-31_0313.png126.13 KBYesCT
#70 sync-s03-add-2013-01-31_0357.png102.98 KBYesCT
#70 sync-s04-translate_with_syncd-2013-01-31_0358.png112.45 KBYesCT
#72 sync-suggestion_add_settings_overview-2013-01-31_0458.png125.49 KBYesCT
#72 sync-add_sync_clue-2013-01-31_0503.png167.93 KBYesCT
#71 sync-s02-sync-setting-2013-01-31_0308.png154.22 KBYesCT
#71 sync-01a-en-2013-01-31_0312.png170.94 KBYesCT
#69 sync-00a-article-mangage-edit-image_field-2013-01-31_0422.png125.36 KBYesCT
#69 sync-s00b-adding_translation-2013-01-31_0426.png129.87 KBYesCT
#63 et-sync-1807692-63.patch28.35 KBplach
#58 et-sync-1807692-59.patch28.35 KBplach
#57 et-sync-1807692-58.patch60.11 KBplach
#49 et-sync-1807692-49.interdiff.do_not_test.patch1.15 KBplach
#49 et-sync-1807692-49.patch28.35 KBplach
#47 et-sync-1807692-47.interdiff.do_not_test.patch18.03 KBplach
#47 et-sync-1807692-47.patch28.27 KBplach
#41 et-sync-1807692-41.patch19.71 KBYesCT
#41 interdiff-39-41.txt858 bytesYesCT
#39 et-sync-1807692-39.patch19.71 KBYesCT
#39 interdiff-37-39.txt3.35 KBYesCT
#39 interdiff-just-style-37-39.txt1.45 KBYesCT
#37 et-sync-1807692-37.patch19.27 KBYesCT
#36 et-sync-1807692-73.patch20.44 KBplach
#34 et-sync-1807692-34.patch20.43 KBplach
#31 et-sync-1807692-31.interdiff.do_not_test.patch3.89 KBplach
#31 et-sync-1807692-31.patch20.43 KBplach
#13 et-sync-1807692-13.patch18.79 KBplach
#11 et-sync-1807692-11.interdiff.do_not_test.patch15.58 KBplach
#11 et-sync-1807692-11.patch19.14 KBplach
#8 et-sync-1807692-8.interdiff.do_not_test.patch3.99 KBplach
#8 et-sync-1807692-8.patch10 KBplach
#5 et-sync-1807692-5.patch9.22 KBplach
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

YesCT’s picture

Issue summary: View changes

Updated issue summary. link did not go to comment 104, added direct link to it.

plach’s picture

Title: Column synchronization for image field etc. that use shared fid column across trans [Follow-up to Entity Translation UI in core] » Introduce a column synchronization capability to translate alt and titles through the core image field widget
Component: language system » translation_entity.module
Issue tags: +Feature freeze
klonos’s picture

Yes please! Alt and title are used in (views) slideshows as overlaying text and it makes *a lot* of sense to have these translated in order to display the overlayed text in the proper language. So, providing an easy way to translate these texts is key and should be in core.

Of course there is this case where the images included in the slideshow might already include text. These cases call for a separate image per language, but that is OT here and IMO an edge case that has an easy solution.

YesCT’s picture

This one is tagged with feature freeze but has no patch yet. @plach, did you have something started, or should anyone jump in?

plach’s picture

I'm planning to post a patch for this during the upcoming weekend. @bforchhammer will help us with code reviews and coding, if needed.

plach’s picture

Status: Active » Needs review
Issue tags: +Needs tests
FileSize
9.22 KB

Here is a first draft. Tests missing.

plach’s picture

+++ b/core/modules/translation_entity/translation_entity.module
@@ -613,6 +624,139 @@ function translation_entity_form_field_ui_field_edit_form_alter(array &$form, ar
+  // current entity, or we haveno contextual information about the translation

typo

bforchhammer’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/translation_entity/translation_entity.module
    @@ -574,12 +574,23 @@ function translation_entity_field_extra_fields() {
    +      '#states' => array('visible' => array(':input[name="field[translatable]"]' => array('checked' => TRUE))),
    

    This doesn't seem to work when there's existing data, because then the checkbox is replaced with textual information, e.g. "This field has data in existing content. [Enable translation]".
    There should also be a validation function in case JavaScript is not available.

  2. +++ b/core/modules/translation_entity/translation_entity.module
    @@ -613,6 +624,139 @@ function translation_entity_form_field_ui_field_edit_form_alter(array &$form, ar
    +/**
    + * Performs field column synchronization.
    + */
    +function translation_entity_sync(EntityInterface $entity) {
    

    Missing @param.

  3. +++ b/core/modules/translation_entity/translation_entity.module
    @@ -613,6 +624,139 @@ function translation_entity_form_field_ui_field_edit_form_alter(array &$form, ar
    +  // current entity, or we haveno contextual information about the translation
    +  // being saved, there is nothing to synchronize.
    

    - missing space: "haveno"
    - add a "then" after the comma?

  4. +++ b/core/modules/translation_entity/translation_entity.module
    @@ -613,6 +624,139 @@ function translation_entity_form_field_ui_field_edit_form_alter(array &$form, ar
    +      $langcode = $original_langcode ?: $source_langcode;
    

    The "?:" looks rather strange ;-)

plach’s picture

Status: Needs work » Needs review
FileSize
10 KB
3.99 KB

Performed some code clean-up. Could not test it as head is broken atm.

I suppressed the state on the checbox for now. We can restore it once the migration code has been dumped.

bforchhammer’s picture

Note: the "Synchronize translations" checkbox would also be good to have in the configuration/operation column on the new settings page that is being added in #1810386: Create workflow to setup multilingual for entity types, bundles and fields.

plach’s picture

Yes, once we have the modal :)

plach’s picture

Ok, here is test coverage. This should be more or less ready to go, hopefully we will be able to remove those ugly request attributes once we figure out how to deal with entity translations properly in #1810370: Entity Translation API improvements.

plach’s picture

Any feedback here?

plach’s picture

FileSize
18.79 KB

rerolled

Status: Needs review » Needs work
Issue tags: -Usability, -Needs tests, -translatable fields, -D8MI, -language-content, -Feature freeze

The last submitted patch, et-sync-1807692-13.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
Issue tags: +Usability, +Needs tests, +translatable fields, +D8MI, +language-content, +Feature freeze

#13: et-sync-1807692-13.patch queued for re-testing.

YesCT’s picture

Needs tests (or a if tests are in place, a test only patch to show the tests work with the bot).

Needs manual testing.

Needs screenshots.

Updating tags and issue summary.

YesCT’s picture

Issue summary: View changes

Updated issue summary. corrected [#99999-99] filter use and added blockquotes on quotes

YesCT’s picture

Issue tags: +Novice

Updated issue summary.

plach’s picture

I don't think this needs an accessibility review or manual testing: it's only exposing a checkbox at UI level. And we have test coverage for the implented functionality.

plach’s picture

Issue summary: View changes

Updated issue summary remaining tasks.

plach’s picture

I updated the issue summary: I don't see a great value in copying/pasting generic portions of handbook pages in every issue summary. I'd say tt's better to keep it slim and focused.

plach’s picture

Issue summary: View changes

Updated issue summary.

YesCT’s picture

I agree that links to the contributor tasks do not need to go into the remaining tasks of every issue. Issues that have people participating, who are experienced already, know how to make a patch or do a review.

But for issues where we want to make it easy to bring people in, and those people may not know what to do, it will let new people come in. Since this issue seemed to need more feedback (it's about 9 days since #12), it's a way of helping people know what feedback is needed and how to give it.

plach’s picture

Sure, but some of the stuff that was in the issue summary previously was not relevant to this issue and IMO could mislead new contributors :)

YesCT’s picture

Yeah, I see (looking at the revisions). :) This is cleaner.

Tests are in the patch. So that is done.
And because this is adding a new functionality, we don't need to provide a tests only patch. Putting test only patches with the fix-and-tests patch in the issue queue for the test bot, is just for when there is a bug the patch is fixing. (right?)

plach’s picture

Putting test only patches with the fix-and-tests patch in the issue queue for the test bot, is just for when there is a bug the patch is fixing. (right?)

Correct. A failing test-only patch indicates some missing code in core, but new features are expected to be missing :)

YesCT’s picture

Issue tags: +Accessibility

Thanks!

Oh, and I *think* that the accessibility tag is just that it is related to accessibility. In this case translating alt and title tags is pretty great for accessibility.
+needs accessibility review
is what is used to get a accessibility review.
I agree we don't need an accessibility review. :)

plach’s picture

Ok, cool.

Gábor Hojtsy’s picture

Issue tags: +sprint

Bringing on D8MI sprint board.

plach’s picture

#13: et-sync-1807692-13.patch queued for re-testing.

yched’s picture

Status: Needs review » Needs work
+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -608,6 +608,162 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+function translation_entity_sync(EntityInterface $entity, $sync_langcode, $original_langcode = FALSE) {

Good inline code comments in the function itself, but I'm having a hard time figuring out the big picture of what the function actually does overall. Could we expand on "Performs field column synchronization" in the func's phpdoc, that's a bit cryptic.

Also - I might vey well be wrong, but it seems the behavior will only fire on form submission ? Not on programmatic saves ?

28 days to next Drupal core point release.

mgifford’s picture

I wanted to just jump in here to question why this issue is seeking to synchronize alt & title text in images.

Generally with a screen reader it is best to just stick with the alt text. Screen readers may actually read both the alt text & title to the user and if they are identical it really doesn't help.

As stated here:

The title attribute, by definition, can be used to provide advisory information. It should:

NOT provide vital information or information necessary for accessibility.
NOT provide the same information as is available in text or alternative text.

http://webaim.org/articles/gonewild/

plach’s picture

I think there's a misunderstanding here: this issue is about synchronizing the image accross translations, while leaving the possibility to enter different alt and title per language. They still are totally independent from one another.

plach’s picture

Issue summary: View changes

Updated issue summary.

plach’s picture

Issue summary: View changes

Updated issue summary

plach’s picture

Status: Needs work » Needs review
FileSize
20.43 KB
3.89 KB

Here is a reroll to match the BC decorator stuff and improving PHP docs as per @yched request:

4051f83 Issue #1807692: Fixed BC decorator.
7fecbcc Issue #1807692: Improved PHP docs.

I also updated the issue summary.

plach’s picture

The BC fix has now its own issue: #1880926: Fatal error when retrieving $entity->original on a BC entity. Go and RTBC it!

@yched:

Any other concern?

attiks’s picture

This looks great, only remark are 2 small typos

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+   * Tests entity field synchrnonization.

typo

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -634,6 +634,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+              // we language we fall back to the new source value.

typo, 'we'

plach’s picture

FileSize
20.43 KB

Thanks, typos fixed.

@yched:

Sorry, I forgot:

Also - I might vey well be wrong, but it seems the behavior will only fire on form submission ? Not on programmatic saves ?

Nope, the synchronization happens on field_attach_presave(). If you are referring to the request attributes stuff, it's a temporary solution. I am planning to find a way to pass along the "active" translation, however for now this works also on programmatic saves if you properly fill-in the request attributes.

plach’s picture

RTBC anyone?

plach’s picture

FileSize
20.44 KB

Rerolled

YesCT’s picture

FileSize
19.27 KB

I'm testing this. but first had to reroll.

git apply --index et-sync-1807692-73.patch
error: patch failed: core/lib/Drupal/Core/Entity/EntityBCDecorator.php:75
error: core/lib/Drupal/Core/Entity/EntityBCDecorator.php: patch does not apply

there were no conflicts.

plach’s picture

The reroll looks good, it just removed the hunk that was necessary to make tests pass and that had been moved to #1880926: Fatal error when retrieving $entity->original on a BC entity, which has been committed.

YesCT’s picture

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+ * @file
+ * Definition of Drupal\entity\Tests\EntityTranslationSyncTest.

Contains
http://drupal.org/coding-standards/docs#files

\Drupal
http://drupal.org/coding-standards/docs#namespaces

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+  /**
+   * Overrides \Drupal\simpletest\WebTestBase::setUp().
+   */
+  function setUp() {

http://drupal.org/node/325974 does need some updating but I think the bit about no php doc on getInfo() and setUp() is right.

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+    // Setup languages.
+    $this->langcodes = array('it', 'fr');
+    foreach ($this->langcodes as $langcode) {
+      language_save(new Language(array('langcode' => $langcode)));
+    }
+    array_unshift($this->langcodes, language_default()->langcode);

no problem here. I'm just wondering what the unshift is for.

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+    for ($delta = 0; $delta < $this->cardinality - 1; $delta++) {
+      // Simulate a field reordering: items are shifted of one position ahead.
+      // The module ensures we start from the beginning after reaching the
+      // maximum allowed delta.
+      $index = ($delta + 1) % $this->cardinality;
+
+      // Generate the item for the current image file entity and attach it to
+      // the entity.
+      $fid = $this->files[$index]->fid;
+      $item = array(
+        'fid' => $fid,
+        'alt' => $this->randomName(),
+        'title' => $this->randomName(),
+      );
+      $entity->{$this->fieldImageName}[$langcode][$delta] = $item;

no problem here, just a question. it's not reordering the fields in the entity, is it? is this changing the order the translations are done?
It gets the fid from the index (shifted delta) but stores it in the [$langcode][delta]

Or perhaps this is the key to the sync'ing. Without the reordering the syncing would not be needed. And this test is making sure, things are sync'd by the fid?

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+    // Perform synchronization: the translation language is used as source,
+    // while the default langauge is used as target.

That sounds strange. The default language was the source language. Source means something different here?

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+    // Check that one value has been dropped from the original values.
+    $assert = count($entity->{$this->fieldImageName}[$default_langcode]) == 2;
+    $this->assertTrue($assert, 'One item correctly removed from the synchronized field values.');

was one of the files removed from the original language? ... Or is this checking the translation to see if the original value for the translation that was not done is dropped? (only 2 of the three were translated)

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+    // Add back an item for the dropped value and perform synchronization again.
...
+    $values[$langcode][$removed_fid] = array(
+      'fid' => $removed_fid,
+      'alt' => $this->randomName(),
+      'title' => $this->randomName(),
+    );
+    $entity->{$this->fieldImageName}[$langcode] = array_values($values[$langcode]);
+    $entity = $this->saveEntity($entity);

This is just translating the remaining file? (only 2 were translated before)

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncTest.phpundefined
@@ -0,0 +1,259 @@
+    foreach ($entity->{$this->fieldImageName}[$default_langcode] as $delta => $item) {
+      // When adding an item its value is copied over all the target languages,
+      // thus in this case the source language needs to be used to check the
+      // values instead of the target one.

the langcode on the third image, that was added in the language (not the default language), has a langcode of the langcode? why didn't the other's that were translated get that langcode overridden too?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ * Implements hook_form_FORM_ID_alter().
+ */
+function translation_entity_form_field_ui_field_edit_form_alter(array &$form, array &$form_state, $form_id) {

http://drupal.org/coding-standards/docs#hookimpl

Implements hook_form_FORM_ID_alter() for field_ui_field_edit_form().

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ * translations. This functionality is provided by defining a 'translation_sync'
+ * key in the field instance settings, holding an array of column names to be
+ * synchronized. The synchronized column values are shared across translations,
...
+      $columns = isset($field['settings']['translation_sync']) ? $field['settings']['translation_sync'] : array();

does this php check each field setting to see if it should by sync'd? I think I'm confusing that a field can be checked to be sync'd and then there is also a setting to say what part of the field (columns?) gets sync'd. Oh, if it's set, it's set to the columns that should be sync'd! "This functionality is provided by defining a 'translation_sync'
+ * key in the field instance settings, holding an array of column names to be
+ * synchronized."

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+function translation_entity_sync(EntityInterface $entity, $sync_langcode, $original_langcode = FALSE) {
+  $translations = $entity->getTranslationLanguages();
+
+  // If we are creating a new entity, or if we have no translations for the
+  // current entity, or we have no information about the translation being
+  // saved, then there is nothing to synchronize.
+  if (empty($sync_langcode) || $entity->isNew() || (count($translations) < 2 && !$original_langcode)) {
+    return;
+  }

Maybe re-write the comment to match the order in the condition.

$entity->isNew() is clearly if it's a "new entity".

"no information about the translation being saved" is that... ? empty($sync_langcode) and also !$original_langcode

why is count translations being tested for < 2? I would think that "if we have no translations" would be count($translations) < 1

Oh yeah, it counts the language of the entity too. http://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21TypedData%...

but why the && ?

I guess I would suggest:

  $include_default = FALSE;
  $translations = $entity->getTranslationLanguages($include_default);
  $count_translations = count($translations);

  // If we have no information about what to sync to, if we are creating a new
  // entity, if we have no translations for the current entity, or we have no
  // information about the translation being saved, then there is nothing to
  // synchronize.
  if (empty($sync_langcode) || $entity->isNew() || ($count_translations) < 1) || ($count_translations >= 1) && !$original_langcode)) {

But how do you know if there is more than one translation, if that is the translation being changed and needs the original_langcode?

Maybe the test for having enough information about the translation needs to come later, comparing something like the next block: the original/unchanged values with the current values?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+  // If the entity language is being changed there is nothing to synchronize.

Really? seems like there might be.

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+  // Enable compatibility mode for NG entities.

I admit, I dont know what NG stands for and google was less than helpful. I thought maybe if I searched for EntityNG... http://api.drupal.org/api/drupal/core!lib!Drupal!Core!Entity!EntityNG.php/8

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+    // If the field is empty there is nothing to synchronize. Synchronization
+    // makes sense only for translatable fields.
+    if (!empty($entity->{$field_name}) && !empty($instance['settings']['translation_sync']) && field_is_translatable($entity_type, $field)) {

comment seemed to imply that empty fields were not translatable... which was just a little odd wording.

I started with Sync when possible. There is nothing to synchronize when the field is empty, if synchronization is not turned on for the field, or it's not translatable. but that's double negatives. So I suggest:

    // Sync when the field is not empty, when the synchronization translations
    // setting is set, and the field is translatable.
+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+      // If a translation is being created, the original values should be used
+      // as the unchanged items. In fact there are no unchanged items to check
+      // against.
+      $langcode = $original_langcode ?: $sync_langcode;

Use the source of the translation as the sync source?

You are checking if a translation is being created by checking if the $original_langcode arg was set? I think that means that the first condition in the function checking if we can even do sync'ing cannot know if we "have enough information about a translation".

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -680,6 +680,169 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+      // Make sure we can detect any change in the source items.
+      for ($delta = 0; $delta < $total; $delta++) {

What are we looping through here? overall, we are doing each field (that it makes sense to sync). are the items each language's value of the field? or is it taking into account the cardinality of the field, so it's the sync source language field's values?

Sorry, that's as far as I got... I didn't actually test it yet.

There is an interdiff with just the code style changes if that is all that is really needed.

Oh, I think in the tests and the code, maybe with some better variable names, things might be easier to understand. source is tricky as this is kind of dealing with translations, which have sources (or original values) and also with the sync, which has a source. Also, values and items were confusing. What to do if anything probably depends on the target audience of the comments.

Status: Needs review » Needs work

The last submitted patch, et-sync-1807692-39.patch, failed testing.

YesCT’s picture

Status: Needs work » Needs review
FileSize
858 bytes
19.71 KB

I know the condition is wrong still logically... just want to see.

YesCT’s picture

Found this while trying to manually test:
#1884148: field widget disappears when increase cardinality for image to allow multiple values
Reproduced in clean 8.x install

yched’s picture

OK, I understand the goal better now, thanks ! Wow, that's a nasty/complex feature to implement :-/

So, yeah, using drupal_container()->get('request')->attributes to set info and get it back in other parts of the code looks like an unholy hack - globals without globals :-). What are the possible ways of getting rid of this ?

There is some hairy logic in translation_entity_sync() to "recognize" and track items between old an new values and figure out which item got reordered where - the part from "// Make sure we can detect any change in the source items." to the end of the function.
It could really use an "algorithmic overview" comment to explain the logic IMO - even though the "step by step" comments that are present are good, I still have a hard time understanding the overall logic and figuring out if it's valid.
- For example, the case where there are several synced columns sounds like it might be complex ? And that case is not covered by the tests. And ouch, testing that case is not going to be trivial. Maybe splitting that logic to a helper func that works with raw arrays and not $field, $instance and $entities, would help make it more unit testable.
- Also, what if several items have the same value for a 'synced' column (e.g same image) ? how do we tell which is which ? I mean, the image might be the same but the alt text and their translations might differ, isn't that a problem ?

+            // If a synchronized column has changed we need to override the full
+            // items array for all languages.
+            elseif ($created) {

Looks like the comment doesn't match the logic : "has changed" vs if ($created))

+            $value = $source_items[$delta][$column];
+            $change_map[$column][$value]['new'] = $delta;

Are we 10%% sure that $value is safe to use as an array key ? I don't remember offhand what can and cannot be a valid array key...

+ // The module ensures we start from the beginning after reaching the
+ // maximum allowed delta.
+ $index = ($delta + 1) % $this->cardinality;

Minor : s/The module/The modulo operator/ :)

plach’s picture

Good suggestions, I'll try to implement them as soon as I can.

fago’s picture

hm, that's a pretty complex feature and problem space. I must say I'm not in favour of the syncing solution, but it seems to be the most feasible one.

Thinking about how it could be done with EntityNG, we could support adding 'translatable' flags on the field value level there while by default they are all translatable, and then implement the synching logic inside of the entity field objects - i.e. if translatable is set and FALSE for a property being changed, do something like

$entity = $this->getRoot();
$entity->$field[$delta]->$name = $value;

$field, $delta and $name could be determined via $this->getPropertyPath(). Of course we'd have to apply it to all translation languages.

That way, we'd have full support for 'translatable' in the API while the storage layer would only have to take care of field translatability.

Maybe, it would make sense to do it inside EntityNG?
Update: I'm not sure it really makes sense in EntityNG as the field is not really translated anymore. It's kind of being shared while some values are translated - how would this feature be exposed in the UI?

plach’s picture

@fago:

Honestly I thought converting this to NG would be pretty straightforward, once all the entities are converted.

I just spent other 3 hours to improve the documentation and write a brand new unit test. Would you consider letting this feature in as-is and talking about the NG conversion later? This could happen also after feature completion, while I'm pretty sure I won't have time to rewrite the code here from scratch since I have more urgent stuff to work on.

plach’s picture

This should address #43. Hopefully this can go in now, otherwise I'm afraid we won't have this functionality in D8 core.

Status: Needs review » Needs work

The last submitted patch, et-sync-1807692-47.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
FileSize
28.35 KB
1.15 KB

Tests were passing here because I had ET enabled. The attached patch uses DrupalUnitTestBase instead of UnitTestBase.

fago’s picture

Would you consider letting this feature in as-is and talking about the NG conversion later?

Sure, it's not my intent to block this in any way - I was just trying to figure out the best way to implement this quite complex feature.

So am I right, that this can be enabled in addition to the general translation option, i.e. you have the choice to do both?

Then, while thinking more about it, I don't think we should have the sync-ing feature in the storage layer as we need to be able to switch between full translation and sync-ing anyway - so it can't just be magically the translation of the field. So the approach taken in the patch makes totally sense for me :-)

plach’s picture

So am I right, that this can be enabled in addition to the general translation option, i.e. you have the choice to do both?

Yes, you can choose to have you fields vary independently by language or have part of their values synchronized across languages. There's a new checkbox in the field settings.

So the approach taken in the patch makes totally sense for me :-)

Glad to hear that :)

plach’s picture

So, I guess we are just missing @yched's ok here, right?

plach’s picture

Any other concern/suggestion?

YesCT’s picture

#49: et-sync-1807692-49.patch queued for re-testing.

yched’s picture

Sorry, very little drupal time lately :-/
Will try to make some time for this, but if it's not tomorrow it won't be before another week.

plach’s picture

#49: et-sync-1807692-49.patch queued for re-testing.

plach’s picture

FileSize
60.11 KB

Rerolled

plach’s picture

FileSize
28.35 KB

Sorry, wrong one.

Status: Needs review » Needs work
Issue tags: -Usability, -Novice, -Needs screenshots, -Accessibility, -translatable fields, -D8MI, -sprint, -language-content, -Feature freeze

The last submitted patch, et-sync-1807692-59.patch, failed testing.

plach’s picture

Status: Needs work » Needs review

#58: et-sync-1807692-59.patch queued for re-testing.

plach’s picture

plach’s picture

@yched:

Even a "yes" or "no" would be welcome :)

plach’s picture

Status: Needs review » Needs work
Issue tags: -Usability, -Novice, -Needs screenshots, -Accessibility, -translatable fields, -D8MI, -sprint, -language-content, -Feature freeze

The last submitted patch, et-sync-1807692-63.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
Issue tags: +Usability, +Novice, +Needs screenshots, +Accessibility, +translatable fields, +D8MI, +sprint, +language-content, +Feature freeze

#63: et-sync-1807692-63.patch queued for re-testing.

yched’s picture

Sorry for the delay, the last fortnight has been pretty dense in my "non-drupal" mode :-/. Slowly getting back at the core queue.

Minor / nitpicky stuff first :

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.phpundefined
@@ -0,0 +1,176 @@
+  protected $unchangedFieldValues;
+
+
+  public static $modules = array('language', 'translation_entity');

Double empty line (& after getInfo() too)

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.phpundefined
@@ -0,0 +1,176 @@
+    $this->cardinality = mt_rand(2, 5);

Can we avoid random values, use, like, 4 instead ?

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.phpundefined
@@ -0,0 +1,176 @@
+    foreach ($this->langcodes as $langcode) {
+      for ($delta = 0; $delta < $this->cardinality; $delta++) {
+        foreach ($this->columns as $column) {
+          $sync = in_array($column, $this->synchronized) && $langcode != $this->langcodes[0];
+          $value = $sync ? $this->unchangedFieldValues[$this->langcodes[0]][$delta][$column] : $langcode . '-' . $delta . '-' . $column; //$this->randomName();
+          $this->unchangedFieldValues[$langcode][$delta][$column] = $value;
+        }
+      }
+    }

A bit cryptic.
"Set up an intitial set of values in the correct state, i.e. with "synchrnozed" values being equal." ?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -766,6 +766,209 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ *   The unchanged items ti be used to detect changes.

s/ti/to

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.phpundefined
@@ -0,0 +1,176 @@
+    $sync_langcode = $this->langcodes[mt_rand(0, count($this->langcodes) - 1)];

Likewise, pick one language instead of random ?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -766,6 +766,209 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+function translation_entity_sync_field(&$field_values, $unchanged_items, $sync_langcode, $translations, $columns) {

Yay @ the new unit testable helper func, & extensive comments. I'd just suggest prefixing the func name with an underscore so that it's not mistaken for an API entry point.

-3 days to next Drupal core point release.

yched’s picture

More substantial stuff :

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -766,6 +766,209 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ * available languages. For this to properly work the column values must be
+ * integer or strings. The synchronized column values are assumed to be unique

"The column values must be integer or strings" - because they're used as array keys, IIRC ?
What about NULL ?
What about decimal / floats ?
What about LONGTEXT ?
Also, where / how do we document that the 'synchronize' feature only works on integer / text values ?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -766,6 +766,209 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ * integer or strings. The synchronized column values are assumed to be unique
+ * among the various items, this is necessary to detect and replay changes in
+ * the order. They are also assumed to change simultanously, so that a change in

"The synchronized column values are assumed to be unique among the various items".
OK, but in lots of actual cases they won't be, what happens then ?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -766,6 +766,209 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ * the order. They are also assumed to change simultanously, so that a change in
+ * any column indicates that all of them have changed.

Yeah, so that's the "how does this work when there are several synced columns ?" part.
Not sure what are the practical implications of this though ?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -766,6 +766,209 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+        // (...) A changed value will be interpreted as a new
+        // value, in fact it did not exist before.

This means that any change in the value of any of the synced columns is seen as "some delta disappeared, that gets removed in all other languages, and a new one appeared, that gets copied over to all languages" ?

Meaning, if I change the image (value for fid column is changed) but not the alt / title texts, then the alt / title texts in other languages (that were possibly translated) will be overwritten with the ones of the language I made the change in ?
Is that really what we want ?

All in all, major kudos for all the work and dedication that were put in this.
I have to admit I'm still scared at the complexity of the feature, and not fully convinced that a) it can be achieved 100% bullet-proof rather than "best effort", and b) that this "best effort" will be "good enough" rather than "unpredictable in practice".
It's not directly in my queue and I won't be in charge of handling followups or bugs ;-), so I won't be blocking this if everyone wants it, though.

YesCT’s picture

steps to test (a simple test)

  1. fresh git pull
  2. apply patch
  3. fresh install
  4. extend by enabling content translation
  5. configure language and add a language
  6. configure translation (admin/config/regional/content-language)
  7. check Content to customize content types
  8. check translate for Article (defaults to enabling translation for image field)
  9. add an article (english) with an image
  10. translate it (pick a different image and alt text for the language)
  11. notice no sync setting, also translations have different images and alt text.
  12. turn on the sync setting by editing the Article content type, manage fields, and edit the image field.
  13. create new article, with image and alt text
  14. translate that article and notice that the translation has the same image and alt text inherited from the source
  15. change (translate) the alt text
  16. notice the translation of the alt text is on the new language, and the source retains it's original alt text
  17. both source and translation have the same image
  18. change the image on the source
  19. notice the image has changed on the translation
  20. change the image on the translation
  21. notice the image has changed on the source
YesCT’s picture

Issue summary: View changes

Updated issue summary.

YesCT’s picture

YesCT’s picture

with patch

1. create article, with translation enabled, but not sync'd

sync-s01-create-article-2013-01-31_0302.png

1a. source with image and alt text
sync-01a-en-2013-01-31_0312.png

1b. translation (using different image and alt text)
sync-s01b-af-2013-01-31_0313.png

2. sync setting in article manage fields edit image field tab

sync-s02-sync-setting-2013-01-31_0308.png

3. create another new article, with sync setting checked on article image

sync-s03-add-2013-01-31_0357.png

4. create translation, with sync setting on image
(very similar to what form looks like without the patch) if change the image here, though, it changes also for the source. [edit again: corrected.]

sync-s04-translate_with_syncd-2013-01-31_0358.png

[edit: added the missing files here]

YesCT’s picture

opps. missed some files.

updated previous comment to include them.

YesCT’s picture

Issue summary: View changes

updated remaining tasks to reflect steps to test were added.

YesCT’s picture

Issue summary: View changes

updated remaining tasks to reflect screenshots done

YesCT’s picture

manually tested. works!

I have some suggestions.
@plach let me know if you want to do them in follow-up (I can open the issues), here, or you think not really a problem or whatever.

  1. it's strange that the sync translation setting is in the field edit. and the translation enable/disable is in the field settings (global field settings). There is an issue to make tranlatable a field instance setting (under the tab that is called edit), so this problem will go away. #1893568: Make each field instance have its own translatable property, instead of sharing translatable property with others.
  2. Maybe add a follow-up to put this sync setting on the custom language setting page (in the regional config page) admin/config/regional/content-language
    sync-suggestion_add_settings_overview-2013-01-31_0458.png
  3. if create a source and a translation without the sync setting. after turn on sync, they are still different (not sync'd). Give a warning? Like: Previously translated content retains possible different values. Values will sync when the values are edited. (but with much better wording)
  4. Might be good to put a note that the field is sync'd in the edit
    sync-add_sync_clue-2013-01-31_0503.png
  5. change help text. (I had to read it three times to figure out if the help text was describing what would happen when it was checked or if it was not checked.) Other checkboxes do not start with "check this option to..." they just say what it is used for if checked.
    from:
    Synchronize translations
    Check this option if you just wish to translate the textual elements of this field. All the other aspects, such as the order of items or non-textual values, will be the same for all languages.

    to:

    Synchronize translations
    Synchronize non-textual values, such as the order of items, across all languages. Textual elements of this field will always be translatable. 

    Would be too specific.. but more clear to say:

    Synchronize translations
    Synchronize non-textual values, such as the order of items, image, or file, across all languages. Textual elements, such as alt text, of this field will always be translatable. 

(novice tasks done for now, will update issue summary, removing tag)

YesCT’s picture

Issue summary: View changes

updated remaining tasks to reflect reviews have been done.

plach’s picture

Issue tags: -Novice

@yched:

Thanks for the review(s)! I'll try to address them ASAP. A couple of answers meanwhile:

"The column values must be integer or strings" - because they're used as array keys, IIRC ?

Yes, this is a limitation we need to document (and probably better enforce). Our main use case are fids so this perfectly works for now.

"The synchronized column values are assumed to be unique among the various items".
OK, but in lots of actual cases they won't be, what happens then ?

One possible solution is that we dected this situation and we bail out without syncing anything. I'd like to explore the possibility to support multiple values, but I'm not sure it's possible without complicating the algorithm too much.

Yeah, so that's the "how does this work when there are several synced columns ?" part.
Not sure what are the practical implications of this though ?

Well, we could refactor the algorithm to combine the column values so that we have a unique key (a primary key). This would allow us to support any combination of the synced columns, provided that at least one value differs from the others.

This means that any change in the value of any of the synced columns is seen as "some delta disappeared, that gets removed in all other languages, and a new one appeared, that gets copied over to all languages" ? [...] Is that really what we want ?

Yes, I am afraid we cannot get around this: a change in a synced column cannot be intepreted in any other way but "a new value is here". We might not propagate it to all the translations or leave the unsynced columns empty, but in the case of alt/titles a language fallback looks like the most reasonable behavior.

I have to admit I'm still scared at the complexity of the feature, and not fully convinced that a) it can be achieved 100% bullet-proof rather than "best effort", and b) that this "best effort" will be "good enough" rather than "unpredictable in practice".

I agree we probably cannot achieve a 100% bullet-proof behavior. What I think we can get is something that works in the 80% of use cases and does not break things in the remaining 20%. The code here has been almost straightforwardly ported from contrib ET (D7), which has around 6000 installations, and no bug about it has been reported so far. Hence I'm pretty confident we can have something that can actually be useful.

YesCT’s picture

I'm not sure, @plach might be in the middle of making a new patch to address some stuff (or maybe just writing explanation).
This help text change could wait and be a follow-up, but here it is, in case it's helpful.
I looked and didn't see any tests that would need be changed as a result of the title change of the checkbox (guess testbot will tell me for sure).

plach’s picture

Here is a new patch, I think it's considerably more solid than the previous one: it should now support multiple elements having the same values for the synchronized columns and the ability to detect changes when just one synced column changes.

This should also take care of #66.

As a bonus all the synchronization logic has been moved to an autoloadable class, to reduce memory footprint. Not sure we need to register it into the DIC: would it make sense for the sync logic to be swappable?

Anyway, the attached interdiff shows only the changes in the sync logic and the related tests. The actual one wouldn't be very useful.

@YesCT:

The attached patch incorporates the proposed textual changes, but it collapses them into the checkbox description: the label felt too long in the original proposal.

About #72:

@1: Yes, this should be solved by making translatability an instance setting.
@2: Also @bforchammer propoed something similar above: I think it would be nice if we could have all these settings in a modal dialog, as originally proposed by Bojhan.
@3: I think a warning message after enabling the setting should be enough. Do you wish to provide a text or a patch?
@4: Looks a bit verbose to me: maybe just "Synchronized"? I'd prefer to address this in a follow-up to be sure to have a UX feedback and not hold this patch on it.

YesCT’s picture

sounds good. I think the @2-4 can be follow-ups for sure... and that will make it easier for people (uh hem, me) to try and address some of them.

plach’s picture

It would really important to get this RTBC before feature completion.

plach’s picture

Issue summary: View changes

update remaining tasks, added need to decide on follow-ups

plach’s picture

FileSize
35.26 KB

Here is a reroll.

Status: Needs review » Needs work
Issue tags: -Usability, -Accessibility, -translatable fields, -D8MI, -sprint, -language-content, -Feature freeze

The last submitted patch, et-sync-1807692-79.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
Issue tags: +Usability, +Accessibility, +translatable fields, +D8MI, +sprint, +language-content, +Feature freeze

#79: et-sync-1807692-79.patch queued for re-testing.

yched’s picture

My remarks on the latest code. Mostly nitpicks / code organisation remarks.

+   * must be integer or strings.

Nitpick : both singular or both plural ?

core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.php
+      // Continuous field values: all of values are equal.

Minor: missing word or extra 'of'.

class FieldTranslationSynchronizer:
+   * @param $sync_langcode
+   *   The language of the translation whose values should be used as source for
+   *   synchronization.
+   * @param $original_langcode
+   *   (optional) If a new translation is being created, this should be the
+   *   language code of the original values. Defaults to FALSE.
+   */
+  public function synchronizeEntity(EntityInterface $entity, $sync_langcode, $original_langcode = FALSE) {

Missing type hints for the langcode args.

Also, "$original_langcode is either a string or FALSE" looks sloppy, that's precisely why NULL exists.
But more generally, the method is always called with a value for $original_langcode, so why is it optional ?
[Edit : oh ok, FALSE is because the value comes from EntityTranslation::getSourceLangcode(), which currently returns "string or FALSE".
I'd be inclined to change that to NULL upstream, but then I guess that's not for this patch.]

FieldTranslationSynchronizer:synchronizeField()
+          $old_delta = -1;
+          $new_delta = -1;
...
+            $created = $created && $old_delta < 0;
+            $removed = $removed && $new_delta < 0;

Looks a bit hackish, relying on the fact that "real" deltas are positive, and then interpret "< 0" for "not found".
Initializing $old_delta & $new_delta to NULL and checking isset($old_delta) / isset($new_delta) should work.

class FieldTranslationSynchronizer:
+  protected function itemIdentifier(array $items, $delta, array $columns) {

Could it be named itemHash() instead ?
What this does is compute a hash for (the values of the synced cols of) an item, not an id. What makes the patch complex is precisely that field values don't have ids that we could use to track them, but this does not add ids, just works around the absence thereof :-).

FieldTranslationSynchronizer::itemIdentifier() :
+          $values[] = $this->validateValue($items[$delta][$column]);
...
+    return !empty($values) ? implode('.', $values) : FALSE;

Minor : implode($empty_array) == '', so the last line could be just return implode('.', $values); ?
+ I'd tend to think validateValue() as a separate method adds an unnecessary mental indirection, could be inlined within itemIdentifier() now.
+ I guess we could use a real hash, like md5(serialize(array_intersect($item, $synced_cols)) and therefore support more than number & strings, but maybe that's not worth the runtime overhead ?

FieldTranslationSynchronizer::validateValue()
+    if (!is_numeric($value) && !is_string($value)) {
+      throw new \InvalidArgumentException('A synchronized value can only be a number or a string.');

is_numeric(NULL) == FALSE, is_string(NULL) == FALSE
That's annoying because a numeric db column could have a value of NULL. (for image fields that would mean the field is empty and is not stored, but for multi-column fields that could happen). It seems allowing NULL would be doable ?
implode('.', array(1, NULL, 2)) == '1..2', works.

+    // Set contextual information that can be reused during the storage phase.
+    $attributes = drupal_container()->get('request')->attributes;
+    $attributes->set('working_langcode', $form_langcode);
+    $attributes->set('source_langcode', $source_langcode);

This is still an ugly hack :-(. Not sure what are the plans to get rid of that ?
@todo + followup issue ?

FieldTranslationSynchronizer :
+  protected function loadUnchanged($entity_type, $id) {
+    $controller = $this->entityManager->getStorageController($entity_type);
+    $controller->resetCache(array($id));
+    $result = $controller->load(array($id));
+    return reset($result);
+  }

Interesting.
In the previous patch, this was translation_entity_sync() function calling entity_load_uncached().
So now that the code moved to a class, we avoid calling functional stuff like entity_load_uncached() within a method and go for the OO equivalent ?

Does that mean that entity_load() too is considered bad practice in OO code, and that any class willing to load an entity needs to get an injected EntityManager and do $this->entityManager->getStorageController($entity_type)->load(array($id)); instead ? Ew :-/
Definitely not a debate for this thread, but IMO that would deserve a spin off discussion to establish the practice.
At the very worst, this loadUnchanged() method should be provided by EntityStorageController, not recoded in FieldTranslationSynchronizer and in each other class that might have a use for entity_load_uncached().

For this patch, I'd be inclined to keep FieldTranslationSynchronizer::synchronizeEntity() calling entity_load_uncached() for now...

Regarding the behavior itself :

FieldTranslationSynchronizer::synchronizeField()
+            $field_values[$langcode][$delta] = $source_items[$delta];
...
+            $field_values[$langcode][$new_delta] = $item;

As I mentioned earlier, I still don't get why we overwrite the whole item in the target languages, instead of only the values of the synced columns, which would be easily doable ?
The current code does : if I edit the node in german and change an image, the english and french alt & title texts that I previously entered will be overwritten with the german values, and I need to edit the other languages to switch them back.
I would think we want : if there are corresponding values in english and french, only synchronize the columns that are supposed to be synchronized and keep the unsynchronized ones untouched ?
In other words, the patch introduces the notion of "synchronized columns", but those are in fact only triggers for the sync behavior, and all columns are in fact synchronized.

But @plach says the current behavior comes straight from D7 ET contrib, where it received no major complaints, so I'm fine with committing the current and possibly discuss / revisit in a followup.

YesCT’s picture

Status: Needs review » Needs work

yched, thanks for the thorough review.

Once we hear back from plach, and follow-ups are opened, then this sound ok to proceed with.

plach’s picture

Thanks for the review, I think I agree with almost everything. I'll be a bit busy this weekend. Hope to be able to squeeze some time out and get this done...

plach’s picture

This should address more or less everything in #83. Could not launch tests so I hope this is still good.

Some answers:

+ I'd tend to think validateValue() as a separate method adds an unnecessary mental indirection, could be inlined within itemIdentifier() now.
+ I guess we could use a real hash, like md5(serialize(array_intersect($item, $synced_cols)) and therefore support more than number & strings, but maybe that's not worth the runtime overhead ?

I originally thought about real hashing too, but I dismissed this solution because of performance concerns. After rethinking about it I went for a hybrid approach: a hash is computed only for non-string and non-int values.

This is still an ugly hack :-(. Not sure what are the plans to get rid of that ?
@todo + followup issue ?

In #1810370: Entity Translation API improvements we agreed to try and introduce an EntityLanguageDecorator that should allow us to remove this hack. Since this would be the only example in core right now, I think it would be fine to fix it there. Added a @todo about this.

Does that mean that entity_load() too is considered bad practice in OO code, and that any class willing to load an entity needs to get an injected EntityManager and do $this->entityManager->getStorageController($entity_type)->load(array($id)); instead ? Ew :-/
Definitely not a debate for this thread, but IMO that would deserve a spin off discussion to establish the practice.

I think we already implicitly dediced to go this way when we introduced the DIC: no point in using procedural functions which are in the global scope and make everything harder to unit-test. That said nothing would prevent us from somehow improving the entity manager DX. Agreed on having this dicussion elsewhere. Reverted the change for now, since loadUnchanged() totally belongs to the storage controller.

[...]
I would think we want : if there are corresponding values in english and french, only synchronize the columns that are supposed to be synchronized and keep the unsynchronized ones untouched ?

Well, the point is that if you change a synchronized value, supposedly you are invalidating also the other ones: think of a node with an image of an apple (english alt = "An apple", french alt = "Une pomme"), then you replace it with the photo of a kitten (I cannot image why you would do this, but let's suppose for a moment :) and enter an english alt the says "A cute kitten". For me it makes far more sense to have a french translation where you have an untranslated english alt rather than having the photo of kitten with a alt saying "Une pomme" :)

plach’s picture

Status: Needs work » Needs review
Issue tags: -language-content +language content

No idea of wtf just happened :(

yched’s picture

Status: Needs work » Needs review
Issue tags: +Usability, +translatable fields, +sprint, +Feature freeze, +language content

After rethinking about it I went for a hybrid approach: a hash is computed only for non-string and non-int values

Yup, great idea !
Although, my bad, md5() is actually forbidden in core now even for non security-related uses, hash('sha256') is the standard. See http://drupal.org/node/845876 and the recent #1884826: Regression - replace md5 in Block module calls with sha2 hashes.

elseif ($old_delta >= 0 && $new_delta >= 0) {

Shouldn't that be changed to isset($old_delta) && isset($new_delta) too ?

plach’s picture

md5() is actually forbidden in core now even for non security-related uses, hash('sha256') is the standard

Indeed, fixed. Thanks!

yched’s picture

Status: Needs review » Reviewed & tested by the community

Thanks !
RTBC if green.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, et-sync-1807692-88.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
FileSize
33.85 KB

This should be more like it :)

yched’s picture

Status: Needs review » Reviewed & tested by the community

:-)

catch’s picture

Status: Reviewed & tested by the community » Needs work

OK I don't have time for a full review but I don't see any discussion here about converting these to fields on the file entity instead of serialized data in the field storage. The problem here is really with the image field for me, and introducing this to work around that doesn't feel good.

plach’s picture

@catch:

This has a far broader scope than just image fields: it will work with any field having item values needing to be synchronized across entity translations. Image fields are just a prominent example. This API would make sense even if we had no use case in core. Please, reconsider the issue status.

I don't think we can realistically make file entities fieldable in core one week before feature freeze, since this would require a dedicated UI, unless there already has been significant work on this that I totally missed. Which could be very well possible, btw :)

plach’s picture

Title: Introduce a column synchronization capability to translate alt and titles through the core image field widget » Introduce a column synchronization capability and use it to translate alt and titles through image field widget

Retitling to be more clear about the goal of this issue.

plach’s picture

Title: Introduce a column synchronization capability and use it to translate alt and titles through image field widget » Introduce a column synchronization capability and use it to translate alt and titles through the image field widget
plach’s picture

Tagging for D8MI.

@catch:

This is the only issue I was able to find where the discussion you are missing might have happened: #376287-16: Make files fieldable.

I have no idea how we could get this done in the scope of the multilingual initiative. My approach with this was: if the Media initiative succeeds and we have fieldable files in core, wonderful, otherwise we need an alternative solution. Hence I came up with the current proposal which meanwhile matured in something that can totally live without image fields exploiting it. AAMOF if you review the patch you'll discover that only 2 lines of it, leaving alone a dedicated test, is dealing specifically with images/files.

To sum up I think this is a cool feature to exploit in contrib and leaving out from core will mostly kill it since I probably won't maintain a D8 version of contrib ET just for it, although people upgrading from D7 might expect to find it still there.

catch’s picture

Status: Needs work » Needs review

Talked to plach about this in irc. I asked for use-case other than image fields (which should really just be a reference). Plach mentioned a map field with a label - pointing out you probably wouldn't want that to be a reference to an entity with a text field.

Moving back to CNR - I still haven't reviewed the code here, only worried about the use cases at this point.

plach’s picture

Actually we have at least another use case in core: the link field, which has an optional Title textual column.

fago’s picture

Also, the displayed label for a referenced image field might vary by usage? Having it in the file entity would not account for that.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Looks like plenty of other use cases to back this up, back to RTBC then.

Dries’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs usability review

The UI elements introduced in this patch don't cut it for me. 'Synchronize translations' is just too cryptic of a setting for the end user. It's misleading actually as its suggests that the strings will be synchronized. In the case of the Image field, as far of the site builder is concerned, it would be better to write something like [ ] Use different image for all translations. If we do this right, we may be able to get rid of the help text too. Right now, it seems like the current patch exposes implementation details in the UI.

plach’s picture

Status: Needs work » Needs review
FileSize
33.85 KB
1.43 KB
4.65 KB

@Dries:

I must say I completely agree :)

I spent some time thinking about an alternative when writing the first patches but nothing really user-friendly came to my mind and then forgot about it. Now I spent some more time thinking about this: would something like the screenshot below work better?

 Enter a different value for each language / Translate only textual elements

plach’s picture

Issue summary: View changes

yched's comments were addressed. also follow-ups opened.

effulgentsia’s picture

I think that's an improvement, but how about this idea?

Instead of making the field type module decide which columns are 'sync' worthy, and implicitly assuming that everything else is "textual", and then needing to add an extra form element to the field settings form, we instead:

- Let field modules specify "column groups". For example, image_field_info() could add the following:

'column_groups' => array(
  array('title' => 'File', 'columns' => array('fid', 'width', 'height')),
  array('title' => 'Alternative text', 'columns' => array('alt')),
  array('title' => 'Title', 'columns' => array('title')),
),

- Have translation_entity.module enhance the admin/config/regional/content-language form (where the administrator decides which fields to make translatable), such that when a field that has 'column_groups' is set to translatable, that a subrow appears for each column group, initially starting off checked, but the admin could uncheck as desired. Basically, the same pattern as how a bundle is expanded into its fields, just applied again to a field expanding to its column groups.

@plach: what do you think?

effulgentsia’s picture

I don't see any discussion here about converting these to fields on the file entity instead of serialized data in the field storage.

Yeah, this would have been great, but it didn't happen. The challenge with making file entities fieldable is that it's only useful if you define bundles. You don't necessarily want an "alt" field on all files, just on the image bundle. But implementing administrator-customizable bundles that properly relate to mime types turns out to be non trivial. #1260050: Provide administrative UI for adding/removing file types is an issue that's been open over a year in the File entity queue. Looks like it's gonna need more time in contrib, but would be awesome to get into D9.

Also, the displayed label for a referenced image field might vary by usage? Having it in the file entity would not account for that.

Also a good point, and there's been discussion in various places (sorry, don't have links handy) about needing a "File usage" entity in addition to file entities to support that cleanly.

That's just background for folks interested in helping out with fieldable files. I also agree with #99 that the Link field provides another use case and several contrib field types do as well.

Bojhan’s picture

Had a short call with plach. This looks like a good idea, I agree with Dries that the word sync is misleading. I really like effulgentsia his proposal, I think thats exactly what people will expect!

The ability to toggle translatability on properties, this should come with some sensible defaults and should be displayed on both the i18n setup page as one the field settings of an individual field. Otherwise we might end up to require way too much setup work for fields with properties.

plach’s picture

Ok, I will work on this ASAP. I'd just want to point out that the items order when we have a multiple field is not covered by the current proposal. Bojhan suggested to make it explicit by having an "Order" checkbox, but since this would force us to totally revisit the sync algorithm and that it would be more stuff for the user to configure, I'd suggest that that we just make it implicit and sync also the order when there is at least one synchronized column.

effulgentsia’s picture

I'd suggest that that we just make it implicit and sync also the order when there is at least one synchronized column.

That makes complete sense to me. I can't think of a use case where it makes sense to vary item order by language, but require syncing of some part of each item. (says a person who's never operated a multilingual site)

Bojhan’s picture

FYI, just to avoid confusion I just figured that would be a way to handle such a use case - if the use case does not exist we should definitely not support it.

@effulgentsia your last remark, is quite funny. I challenge you to just try it sometime :)

plach’s picture

Here is a patch implementing #104 + #107.

As suggested by @Bojhan in the content language settings we have a slightly different behavior wrt fields: instead of making all columns translatable, when making a field translatable smart defaults are applied. In the screenshot only the textual columns are made translatable by default (thus triggering column synchronization). When making a field untranslatable column settings are reverted to their defaults. The same settings with their defaults are available on the instance settings page when the field is translatable.

Since the behavior implemented now might not satisfy every possible use case, above all because of #107, I decided to make the field sync logic swappable and registered the implementing class into the DIC.

Moreover while fixing tests I noticed that the image field sync test was not extending the ET base test, hence I updated it accordingly.

The end result is that the interdiff is pretty useless :)

et-sync-1807692-110-1.png

et-sync-1807692-110-2.png

et-sync-1807692-110-3.png

et-sync-1807692-110-4.png

plach’s picture

Priority: Normal » Major

I didn't foresee to work on this so much, I'd really like to see this going in now :)

YesCT’s picture

Priority: Major » Normal

this looks great! very clear!

YesCT’s picture

postponed #1909218: add (all languages) hint to synchronized fields on translation add/edit form on this. it will needs to be looked at again after this lands.

plach’s picture

Priority: Normal » Major
YesCT’s picture

in general this code is very well documented, and is even beautiful in some places.

some of the following are nits, and I'm happy to do them now, or after commit. documenting them here so I dont forget them.
I dont think any of these are blocking.
I did not (yet) test it manually. When I do, I'll check the hidden labels on the fields, but the code looked to add the necessary accessibility things. (or maybe someone will beat me to it)

-------------

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.phpundefined
@@ -0,0 +1,193 @@
+    // By picking the maximum size between updated and unchanged items, we make
+    // sure to process also removed items.
+    $total = max(array(count($source_items), count($unchanged_items)));
...
+    for ($delta = 0; $delta < $total; $delta++) {
+      foreach (array('old' => $unchanged_items, 'new' => $source_items) as $key => $items) {
+        if ($item_id = $this->itemHash($items, $delta, $columns)) {
+          $change_map[$item_id][$key][] = $delta;

should this be max, or source + unchanged?

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.phpundefined
@@ -0,0 +1,193 @@
+    // Reset field values so that no spurious one is stored. Source values must
+    // be preserved in any case.
+    $field_values = array($sync_langcode => $source_items);

spurious? as in bogus?

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.phpundefined
@@ -0,0 +1,193 @@
+          // By inspecting the map we built before we can tell whether a value
+          // has been created or removed. A changed value will be interpreted as
+          // a new value, in fact it did not exist before.
+          $created = TRUE;
+          $removed = TRUE;
+          $old_delta = NULL;
+          $new_delta = NULL;
+
+          if ($item_id = $this->itemHash($source_items, $delta, $columns)) {
+            if (!empty($change_map[$item_id]['old'])) {
+              $old_delta = array_shift($change_map[$item_id]['old']);
+            }
+            if (!empty($change_map[$item_id]['new'])) {
+              $new_delta = array_shift($change_map[$item_id]['new']);
+            }
+            $created = $created && !isset($old_delta);
+            $removed = $removed && !isset($new_delta);
...
+          // Otherwise the current item might have been reordered.
+          elseif (isset($old_delta) && isset($new_delta)) {

why && with created or removed, they are hard coded to be TRUE.

should there be an else in here? can something be both created (not old) and removed (not new)?

Oh... it can be neither...

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.phpundefined
@@ -0,0 +1,193 @@
+   * @param integer $delta
+   *   The delta identifying the item to be processed.
...
+  protected function itemHash(array $items, $delta, array $columns) {

http://drupal.org/node/1354#types says to use "int"

add int to function declaration for $delta.

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncImageTest.phpundefined
@@ -0,0 +1,229 @@
+  function setUp() {

setUp should be public
http://drupal.org/node/325974
(in the past this was not generally done though)

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncImageTest.phpundefined
@@ -0,0 +1,229 @@
+      // Simulate a field reordering: items are shifted of one position ahead.

shifted one position ahead (remove of).

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.phpundefined
@@ -0,0 +1,262 @@
+  protected function setUp() {

http://drupal.org/node/325974 says setUp should be public.

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.phpundefined
@@ -0,0 +1,262 @@
+    // Add a new item to the source items and check that its added to all the
+    // translations.
...
+    // Remove an item from the source items and check that its removed from all
+    // the translations.

its -> it's (or: it is)

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncUnitTest.phpundefined
@@ -0,0 +1,262 @@
+  /**
+   * Tests that items holding the same values are correctly synchronized.
+   */
+  public function testMultipleSyncedValues() {

what does this test, that the other tests do not?

+++ b/core/modules/translation_entity/translation_entity.admin.incundefined
@@ -6,11 +6,44 @@
+ * (proxied) Implements hook_form_FORM_ID_alter().

@@ -57,6 +94,112 @@ function _translation_entity_form_language_content_settings_form_alter(array &$f
+ * (proxied) Implements hook_preprocess_HOOK();

what does proxied indicate?

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -802,7 +802,7 @@ function translation_entity_field_extra_fields() {
- * Implements hook_form_FORM_ID_alter().
+ * Implements hook_form_FORM_ID_alter() for field_ui_field_settings_form().
  */
 function translation_entity_form_field_ui_field_settings_form_alter(array &$form, array &$form_state, $form_id) {

@@ -837,6 +837,41 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ * Implements hook_form_FORM_ID_alter().
+ */
+function translation_entity_form_field_ui_field_edit_form_alter(array &$form, array &$form_state, $form_id) {

add a similar for ... field_ui_field_edit_form ?

effulgentsia’s picture

I haven't reviewed the code in-depth, or manually tested the syncing functionality, but from the little I've seen, I think this is really good. I hope @YesCT or someone else more familiar with this issue than I am can RTBC it in the next 24 hours or so. Otherwise, I'll try to detail review it then.

plach’s picture

Great review, thanks!

should this be max, or source + unchanged?

Max is enough, since we just need to ensure we reach the end of both source and unchanged items arrays. Anyway this is not needed when populating the change map, so moved it down and cleaned-up the related code.

spurious? as in bogus?

Yep :)

why && with created or removed, they are hard coded to be TRUE.
should there be an else in here? can something be both created (not old) and removed (not new)?

There was a lot of cruft here left out from previous iterations. Cleaned-up.

add int to function declaration for $delta.

Nope, type-hinting works only with classes.

what does this test, that the other tests do not?

It tests the sync behavior when multiple items have the same values to ensure they are retained and moved around if needed.

what does proxied indicate?

That it is the actual hook implementation. The proxy one is in the .module file. This helps reducing the memory footprint.

add a similar for ... field_ui_field_edit_form ?

That one is not touched by this patch, so it would be out of scope.

plach’s picture

YesCT’s picture

wow! quick turn around. glad some of that was helpful. I'm going to do manual testing now.

@plach

That one is not touched by this patch, so it would be out of scope.

I thought that doc block was changed lines in the patch...

+++ b/core/modules/translation_entity/translation_entity.moduleundefined
@@ -837,6 +837,41 @@ function translation_entity_form_field_ui_field_settings_form_alter(array &$form
+ * Implements hook_form_FORM_ID_alter().
+ */
+function translation_entity_form_field_ui_field_edit_form_alter(array &$form, array &$form_state, $form_id) {
+  if ($form['#field']['translatable']) {
+    module_load_include('inc', 'translation_entity', 'translation_entity.admin');
+    $element = translation_entity_field_sync_widget($form['#field'], $form['#instance']);
+    if ($element) {
+      $form['instance']['settings']['translation_sync'] = $element;
+    }
+  }
YesCT’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
119.84 KB
122.45 KB

1. labels make sense, match the checkbox id, and have invisible text to help identify it's content to compensate for the equivalent visual indent.

sync-s02-confirm-label_and_invisible-2013-02-16_2157.png

2. unchecking all parts of image, on save the image is unchecked too. that makes sense.

3. I was confused because even though I tried unchecking and check translatable for title on image, it did not show in edit or add translation form. Then I remembered I might have to configure image itself to make title field show. yep.

sync-s03-2013-02-16_2237.png

suggestion, i) expect people will figure this out. ii) dont show fields are translatable if they are not enabled.
is this image specific? if so, go with i)

4. things make sense when file, alt, title are all translatable.
they make sense when file is not but alt and title are translatable.
stuff gets weird when the file is translatable but the alt and title are not. In practice this does not make sense anyway. During testing, the result is that changing an image means deleting it, and the alt and title are deleted when that happens also, so their values are not sync'd. since that behavior is image specific. it's a works as designed or wont fix too. since the code is generic, I think its behavior is ok.

that's as far as I got with manual testing and it works. rtbc from me.

plach’s picture

suggestion, i) expect people will figure this out. ii) dont show fields are translatable if they are not enabled.
is this image specific? if so, go with i)

Yes, this is image-specific: we could have a novice follow-up to add a tiny bit of state magic in the image.module and hide the Title and Alt groups when the related form items are disabled.

stuff gets weird when the file is translatable but the alt and title are not.

Yep, this is why I originally went for a less flexible approach: less stuff to figure out for the user. Also here we might have a follow-up and add some JS to make the dependent groups always checked (and readonly) when the master group is checked. E.g. when I check "File", "Alt" and "Title" are automatically checked and cannot be unchecked.

YesCT’s picture

I agree those follow ups make sense. I can help open follow up issues.
Should we open follow ups for some of the @todos in the code?

plach’s picture

Yep, probably. Already created #1919322: entity_load_unchanged() should be part of the storage controller for:

+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php
@@ -0,0 +1,191 @@
+    // @todo Use the entity storage controller directly to avoid accessing the
plach’s picture

Issue summary: View changes

Updated issue summary.

YesCT’s picture

Issue summary: View changes

added recent screenshots.

webchick’s picture

Category: feature » task
Status: Reviewed & tested by the community » Fixed

The old issue summary linked to #89/90 and I was very scared because that UI needed a ton of work. But very stoked to see the screenshots at #110! That's exactly what I expected.

I did not test this patch myself, but it's clear that YesCT did a bang-up job of this already, and both she and yched have given it a thorough code review. I also think that being able to translate fields, but not being able to translate attributes of fields, is a WTF so am going to re-classify this as a major task.

Committed and pushed to 8.x. Thanks!

plach’s picture

Wonderful, thanks!

plach’s picture

Issue tags: -sprint

Removing from sprint, thanks everyone for the help!

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

plach’s picture

@yched:

Opened #1935762: Remove entity_load_unchanged() call from FieldTranslationSynchronizer::synchronizeFields() so we can dicuss the DX implications of injecting the entity manager.

plach’s picture

Issue summary: View changes

Updated issue summary, added follow-up section and some follow-ups

yched’s picture

In #1992138-52: Helper issue for "field types as TypedData Plugins" (the "unify Field API with Entity/FieldNG" issue), we could use some help with failures in EntityTranslationSyncImageTest :-)

YesCT’s picture

I did a manual test today as part of looking at #2013837: Rewrite field sync on top of NG entities
without that patch, I've found that image files are always sync'd, even when the image file field is checked to be translatable.
I'm not sure when that broke, and someone should double check they have found the same thing as I have existing in 8.x right now.

YesCT’s picture

Issue summary: View changes

added some followups that were already opened earlier to the issue summary

YesCT’s picture

klonos’s picture

...removed the "Related issues" section from the issue summary and moved all issues listed there as child issues of this one. That was a pain because we currently do not have this: #2130889: Allow adding child/follow-up issues directly from the parent issue and converting related issues to children.

klonos’s picture

yched’s picture

@YesCT, @klonos (and @plach) : opened #2136197: Move field/instance/widget/formatter settings out of annotation / plugin definition for the issue about config schema for 'translation_sync'.