Problem/Motivation

This is the same issue as described in the issue summary of #2267251: Do not rely on "content language" in hook_entity_presave(). Since it's Entity translation that needs to be fixed but the Title module also needs a test, let's leave the project of that issue as is.

To sum up the issue: I have a translatable node type with the title replaced with a field using the Title module.
When translating the node to German while having the interface in English (i.e. using the /en/node/123/edit/de url) the English title translation will be overridden with the German one.

Proposed resolution

Since the Title module has no clue about how other modules influence the "active language" (see title_active_language()), other modules (like Entity Translation) should let Title know about it. This is the case on a translation edit and add page.

Comments

czigor created an issue. See original summary.

czigor’s picture

Status: Active » Needs review
StatusFileSize
new1.17 KB

(The issue is critical because it causes data loss.)
The attached patch changes the active language used by the Title module and makes the new Title test in #2267251-48: Do not rely on "content language" in hook_entity_presave() pass.

pol’s picture

Nice

czigor’s picture

StatusFileSize
new1.05 KB
new904 bytes

It's better to use API functions if we can.

stefanos.petrakis’s picture

Thanks for the work on this!

I think the patch does the job; however I also feel that there are other modules besides the Title module that may find this useful.

I took the liberty of rewriting this into a hook that will notify all hook implementations that there is an incoming content form language to take into account.

Following from this hook, the Title module can have a much simpler way to - at least - get over the contradicting URLs problems. The code I am posting got through all the tests of Title, including the latest one posted as a patch here (https://www.drupal.org/node/2267251#comment-11903632).

czigor’s picture

Hi @stefanos.petrakis,

The root of the issue is that Title module can't properly determine the active language, since it has no clue about routes (e.g. add/edit translation urls) coming from other modules. So if we want to solve this with a hook, then it's the Title module that needs one and not Entity translation. The hook should enable other modules to alter the active language. But title_active_language() (being a getter and a setter as well) already makes this possible, so I don't think new hooks are needed at all.

stefanos.petrakis’s picture

Hi @czigor,

I find that the root of the issue is that the language negotiation logic for URL suffixes is buried deep inside Entity translation. I think this kind of negotiation logic should be exposed on a system-wide level.

In the meantime, I came up with an alternative, without needing to have this dependency between Entity translation and the Title module. https://www.drupal.org/node/2267251#comment-11917811

If that alternative solves the problem, we could close this one without any code modifications.

plach’s picture

Thank you all for opening this and getting the ball rolling!

I've been thinking about #2267251: Do not rely on "content language" in hook_entity_presave() for quite some time (intermittently ;) and I agree with @czigor's conclusion that it's Entity Translation's job to define the active language for field content, since it's exposing the actual translation functionality.

My original idea was to introduce a new EntityTranslationHandlerInterface::getActiveLanguage() method (with a corresponding setter) to replace the existing ::getFormLanguage(). This would be the language to be used by entity_translation_language() (the language callback). EntityTranslationHandlerInterface::getFormLanguage() (and the corresponding setter) would be deprecated and kept around for BC-only. They would just be thin wrappers around the new methods.

This approach would allow us to explicitly set the active language through a centralized API that can be reliably used when programmatically dealing with field translations. In Title we would agree on a sensible default behavior and modules implementing use cases for which the default does not work could set the active language explicitly.

Thoughts?

czigor’s picture

It sounds good to me. I'm not sure if we should really change the existing getFormLanguage() implementations because of BC. Shouldn't we just leave them as they are and replace it with getActiveLanguage() everywhere in entity_translation module? (Although the only other place I found it in use was media_field_widget_form()).

czigor’s picture

'active language' is a title module concept (see title_active_langauge()) and Title is not an Entity Translation dependency. What would getActiveLanguage() return if there's no Title module installed? Would it fall back to the current getFormLanguage() logic (which btw has changed just yesterday)?

plach’s picture

@czigor:

The reason to replace the actual implementation of ::get/setFormLanguage() is that active language is a broader concept that encompasses form language. Basically the entity active language is the form language when dealing with an entity translation form.

I'm talking about active language in these terms because it's a concept "backported" from D8. I introduced it in Title way after the form language concept was added to Entity Translation and that's the reason why in D7 it looks like a Title-only concept. However it's a general concept that architecturally probably belongs to core but, since there's no concept of whole entity translation in D7 core (just translatable fields), ET looks like the best candidate to me.

The Entity module also provides a similar concept with the entity metadata wrapper language, and there's probably quite some overlap it would be worth investigate. Unfortunately when ET was first drafted the Entity module only provided CRUD operations and so its field language handling and ET's diverged quite a bit. It would probably would be a sane idea to rebuild ET 2.0 on top of the Entity API module, but I'm not sure that will ever happen :)

I hope this helps to clarify my position.

czigor’s picture

@plach Thanks, this clarifies it.

I hope I can find some time to prepare a patch tomorrow.

czigor’s picture

Status: Needs review » Needs work
czigor’s picture

Status: Needs work » Needs review

I have created the patches for Entity translation and Title, all tests are green locally.

However, I don't feel good about the ET patch. It merely deprecates the formLanguage getter and setter and creates getActiveLanguage() and setActiveLanguage(). I think this will cause more confusion than good. I suggest just to add some comments to the getFormLanguage() and setFormLanguage() docs and continue using them in their current form.

czigor’s picture

Status: Needs review » Needs work
czigor’s picture

@plach What do you think?

plach’s picture

Assigned: Unassigned » plach
Status: Needs work » Needs review

I disagree: so far (hopefully) ::set/getFormLanguage() have (or should have) been used almost as an internal API, while ::set/getActiveLanguage() is meant to be a public API everyone dealing with Entity Translations in a programmatic context is expected to use. I agree we probably need to be very clear about this distinction in the PHP docs and perhaps immediately deprecating ::set/getFormLanguage() is not a good idea, as we may discover use cases where their behavior may diverge, although I cannot come up with any concrete example right now.

What if we just add ::set/getActiveLanguage() and change ::set/getFormLanguage() to proxy the call to those for now. If things appear to work smoothly we can then deprecate the form language methods later.

I will work on this ASAP if we agree on the ay forward.

plach’s picture

Status: Needs review » Needs work
czigor’s picture

Status: Needs work » Needs review
StatusFileSize
new13.97 KB

@plach The only module I know about using get/setFormLanguage() is Media.

Deprecating the getter is straightforward, but the setter has a $this->notifyChildren(__FUNCTION__, $args); call. I'm not sure how to handle that one. Maybe you have an idea so I provide my patch here.

plach’s picture

Thanks, I will start from there later today!

plach’s picture

And here it is.

plach’s picture

I decided to keep form language accessors deprecated as deciding when using those or the new ones would probably add confusion. Right now the instructions are easy: just use the new ones instead of the old ones.

pol’s picture

I don't know that part yet but I fully agree with the patch.

Status: Needs review » Needs work

The last submitted patch, 21: et-active_language-2849464-21.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
StatusFileSize
new944 bytes
new19.06 KB

Small PHP doc adjustment: the patch is ok, as the test failures were already there in HEAD.

plach’s picture

StatusFileSize
new2.29 KB
new19.01 KB
  1. +++ b/includes/translation.handler.inc
    @@ -99,6 +99,63 @@ interface EntityTranslationHandlerInterface {
    +   * This is the language that determines which translation for should be
    

    typo

  2. +++ b/includes/translation.handler.inc
    @@ -804,6 +851,35 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
    +    // For new entities the form should be built in the default language. The
    

    More form terminology.

  3. +++ b/includes/translation.handler.inc
    @@ -1109,32 +1185,19 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
         $args = func_get_args();
    

    mmh, not thin enough

stefanos.petrakis’s picture

Status: Needs review » Reviewed & tested by the community

Code looks good, tests are all green.
It also allows the latest patch from #2267251-56: Do not rely on "content language" in hook_entity_presave() to turn the Title tests green too.

Thanks a lot for this.

plach’s picture

@czigor:

Do we have your +1? :)

czigor’s picture

@plach My problem was exactly that setFormLanguage() is not 'thin enough'. Making it thin will break BC. We have 3 options:
1. We don't care, it's deprecated (current approach).
2. We don't make it thin.
3. We remove get/setFormLanguage() and thus force modules to update.

If we say explicitly that it's 1., I'm ok with the patch. We just need to be clear about this.

czigor’s picture

Also, let's not forget to create an issue for Media in case it's 1.

plach’s picture

@czigor:

I don't think making it thin will break BC: if you call ::setFormLanguage() it will proxy to ::setActiveLanguage(), which in turn will call ::setActiveLanguage() on the children, which should be the same of calling ::setFormLanguage() on each of them. The point of the new test was exactly to ensure this behavior is preserved. What am I missing?

czigor’s picture

@plach For nodes it's ok, but if a custom entity translation handler overrides setFormLanguage() then it will never be called through setActiveLanguage()'s notifyChildren().

That said, I don't know of any entity having a handler that overrides setFormLanguage(). Just nagging. Still fine with going with this patch. But let's not forget to mention this in the release notes.

plach’s picture

Mmh, perhaps you were thinking of modules overriding ::setFormLanguage()? That could indeed introduce some BC problems, what if we reversed the internal implementation and make ::get/setActiveLanguage() thin wrappers around ::get/setFormLanguage()? We can go back to the implementation provided by #26 when removing the deprecated functions.

plach’s picture

Sorry, crosspost. I checked all the modules I know of that provide translation handlers and none overrides ::setFormLanguage(), OTOH that's not nearly enough to claim we're performing a BC change, so I'd be inclined to go with #33. Thoughts?

plach’s picture

Category: Bug report » Task
Status: Reviewed & tested by the community » Needs review
StatusFileSize
new1.94 KB
new19.7 KB

Tests are still green. I guess it's either #26 or this.

czigor’s picture

#35 would break BC the same way as #26 just not now but when we remove the deprecated functions.

It still seems to me that the cleanest (and easiest) solution is not to introduce get/setActiveLanguage() but restate the purpose of get/setFormLanguage() from being an inner API function to a public one.

But I'm still fine with going with #26/#35 as well :).

plach’s picture

Status: Needs review » Reviewed & tested by the community

#35 would break BC the same way as #26 just not now but when we remove the deprecated functions.

Yep, but at that point people would be expected to have transitioned to ::get/setActiveLanguage(), so that will be an expected (and documented) BC break.

It still seems to me that the cleanest (and easiest) solution is not to introduce get/setActiveLanguage() but restate the purpose of get/setFormLanguage() from being an inner API function to a public one.

Easiest for sure. Cleanest, I don't agree, sorry :)

I would not want (or think it is right) to use an API that mentions form language when there is no form involved. ET is still in beta after all, so I think it's ok to perform this kind of changes.

But I'm still fine with going with #26/#35 as well :).

Ok, so back to RTBC. I will leave this around for a few more hours before committing it, to give people time to chime in.

plach’s picture

+++ b/includes/translation.handler.inc
@@ -99,6 +99,62 @@ interface EntityTranslationHandlerInterface {
+   * @deprecated in 7.x-1.0-beta6, will be removed before 7.x-1.0. Use set
...
+   * @deprecated in 7.x-1.0-beta6, will be removed before 7.x-1.0. Use set

Typos to be fixed on commit :)

plach’s picture

Title: Set active language for Title module » Add an API to set active language

  • plach committed 4c5841d on 7.x-1.x
    Issue #2849464 by plach, czigor, stefanos.petrakis: Added an API to set...
plach’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed, thank you all for your help and feedback!

Now it would be good open issues to update the affected modules providing ET integration. I found the following:

  • Media
  • ECK
  • Field Collection

It would also be good to add a change record. I will do it tomorrow if none beats me to it.

czigor’s picture

Awesome, thanks!

Created the issues with patches (see Child issues) and updated #2267251: Do not rely on "content language" in hook_entity_presave() as well. We should release a new beta asap so that modules can upgrade.

plach’s picture

Thanks!

And here is the change record:

https://www.drupal.org/node/2855208

I'd like to wait a couple of days before publishing a new release to make sure this did not introduce some unforeseen issue.

badrange’s picture

There is a patch in Inline Entity Form's issue queue that will probably be affected by this commit, I added a comment in https://www.drupal.org/node/1545896#comment-11953198

badrange’s picture

Same thing with Paragraphs, I see that Pol knows about this issue:
https://www.drupal.org/node/2452675#comment-11953202

badrange’s picture

I think Entity Reference also has some kind of Entity Translation integration; I'll leave it to you guys to choose whether to create an issue.

czigor’s picture

Modules are affected only if they call either getFormLanguage() or setFormLanguage(). Entity Reference is not affected.

@plach Shouldn't the change record also include getFormLanguage()?

plach’s picture

Updated the change record, thanks.

joseph.olstad’s picture

Any time-line for a tagged release? This new api function makes sense.

czigor’s picture

plach’s picture

Status: Fixed » Closed (fixed)

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

joseph.olstad’s picture

Thanks @plach and everyone else for this.

I've written a draft hook_requirements for the other modules that wish to go ahead safely with this change.

/**
 * If entity_translation is used, make sure it is beta6 or newer.
 */
function some_cool_module_requirements($phase) {
  $requirements = array();
  // Ensure translations don't break during installation.
  $t = get_t();
  if (module_exists('entity_translation')) {
    if ($phase == 'update' || $phase == 'install' || $phase == 'runtime') {
      $entity_translation_info = system_get_info('module', 'entity_translation');
      $et_installed_version = $entity_translation_info['version'];
      $et_installed_datestamp = $entity_translation_info['datestamp'];
      $march3rd_entity_translation_timestamp = 1488530885;
    
      if ($et_installed_datestamp < $march3rd_entity_translation_timestamp) {
        $requirements['entity_translation']['description'] = $t('Your entity_translation installation version: %version is too old. some_cool_module requires entity_translation at least beta6 from march 3 2017 or newer.', array('%version' => $et_installed_version));
        $requirements['entity_translation']['severity'] = REQUIREMENT_ERROR;
        $requirements['entity_translation']['value'] = $et_installed_version;
        $requirements['entity_translation']['title'] = $t('Entity translation (when installed) with some_cool_module');
      }
    }
  }
  return $requirements;
}

joseph.olstad’s picture

This change caused a regression which is fixed by patch 4 in the following issue:

#2863794: Translated content is lost when cloning

joseph.olstad’s picture

media 7.x-2.0 was released Thursday and it now requires beta6, needs related issue fixed, please action this related issue asap.

stefanos.petrakis’s picture

@joseph.olstad: Thanks for the last 2 comments, this is not the right issue/commit though. The code cited in #2863794: Translated content is lost when cloning originates from #2438615: New nodes are always created using LANGUAGE_NONE (only changed to the correct language during form submission).