The locale module is being cleaned up to be used only for interface translation, and language awareness is almost all moved out to the respective modules (user, path, node, etc.) One thing that is particularly left in locale module is the field language fallback handling code. That is inherent to fields, just as user preferences for language or path language support is inherent to paths. We take an approach with Drupal 8 to not solve language problems from the outside but bake language right in to the modules. Fields already do their own language based storage (language being even part of the primary key on all field data tables), so it is logical to have the language base rendering also part of the fields infrastructure.

Tasks

- move field language negotiation code from locale.module to field.module
- move related tests from locale to field tests too
- do not make the feature/tests depend on locale module anymore (this might have other tests in the system related which rely on field language)

Comments

vasi1186’s picture

Assigned: Unassigned » vasi1186
vasi1186’s picture

Status: Active » Needs review
StatusFileSize
new24.14 KB

A first patch to be reviewed.

The main changes I made:
1. I moved the locale_entity_info_alter() to the node module (basically put the translation handler in the hook_entity_info()), and also move the submit handler that was attached to the node form in the locale_form_node_form_alter() also in the node module because all that logic should reside in the node module and not the field module, IMO. Also, the field module is a Drupal required field, so it will be always enabled.

2. The locale_field_language_alter() and locale_field_language_fallback() were moved into the field module.

gábor hojtsy’s picture

Status: Needs review » Needs work
+++ b/core/modules/field/field.moduleundefined
@@ -369,6 +369,61 @@ function field_system_info_alter(&$info, $file, $type) {
+function field_field_language_alter(&$display_langcode, $context) {
+  // Do not apply core language fallback rules if they are disabled or if
+  // field_language is not registered as a translation handler.
+  if (variable_get('field_language_fallback', TRUE) && field_has_translation_handler($context['entity_type'], 'field_language')) {
+    field_language_fallback($display_langcode, $context['entity'], $context['langcode']);
+  }
+}

This is strange (and I see it is pre-existing before the patch), but there is $display_langcode here, but once it is passed on as-is, it becomes $field_langcodes in the invoked function (multiple langcodes).

+++ b/core/modules/field/field.moduleundefined
@@ -369,6 +369,61 @@ function field_system_info_alter(&$info, $file, $type) {
+        require_once DRUPAL_ROOT . '/core/includes/language.inc';
+        $fallback_candidates = language_fallback_get_candidates();

The code gets to this point I think even if there is no language module enabled. We should somehow stop this invocation chain at a point if the language module is not there (or prove it will not get here :)

+++ b/core/modules/node/node.moduleundefined
@@ -223,6 +223,9 @@ function node_entity_info() {
+      'translation' => array(
+        'field_language' => TRUE,

Can/should we say this independent of language module being enabled?

+++ b/core/modules/node/node.pages.incundefined
@@ -524,6 +527,30 @@ function node_form_submit($form, &$form_state) {
 /**
+ * Checks if field_language is registered as a translation handler and handle
+ * possible node language changes.
+ */

Function summeries should be on one line, extended comments should be on other lines followed by an empty line.

vasi1186’s picture

Status: Needs work » Needs review
StatusFileSize
new1.35 KB
new24.37 KB

For the 4th point, I implemented the change in the attached patch.
For the 3rd point, I wrapped now that translation handler into a module_exists() check, so, if the language module is not enabled, the field_language translation handler should not be assigned to the node.
By doing this, basically the 2nd point is also solved, because the field_language_fallback() function is called only if the entity type has the field_language translation handler attached (except the case when a custom module would alter the entity definition and always add the translation handler...)

For the 1st point, I think the root of the issue is in the field_language() function, in field_multilingual.inc file. There, the $display_langcode is an array and not just a "simple" value. So, either we change the name of that variable to something like $field_display_langcodes (because $display_langcodes is already used), or change the field_language_fallback() function and use $display_langcode instead of $field_langcodes. I would choose the first option, what do you think?

vasi1186’s picture

fubhy’s picture

StatusFileSize
new24.32 KB
+++ b/core/modules/field/field.moduleundefined
@@ -369,6 +369,61 @@ function field_system_info_alter(&$info, $file, $type) {
+ * Core language fallback rules simply check if fields have a field translation
+ * for the requested language code. If so the requested language is returned,

There should be a comma after "If so" => "If so, the requested language ..."

+++ b/core/modules/node/node.pages.incundefined
@@ -524,6 +527,31 @@ function node_form_submit($form, &$form_state) {
 /**
+ * Checks if field_language is registered as a translation handler.
+ *
+ * If so, handle possible node language changes.

I don't like this function description.

"Handles possible node language changes if 'field_language' is registered as a translation handler."

+++ b/core/modules/node/node.pages.incundefined
@@ -524,6 +527,31 @@ function node_form_submit($form, &$form_state) {
+
+      // Handle a possible language change: new language values are inserted,
+      // previous ones are deleted.

New (capital letter after the ":").

+++ b/core/modules/field/field.moduleundefined
@@ -369,6 +369,61 @@ function field_system_info_alter(&$info, $file, $type) {
+  $field_languages = array();

Unused variable.

fubhy’s picture

Status: Needs review » Reviewed & tested by the community

The last version of the patch is from me but it just changes a few comments and I already discussed it with the others at the sprint and it seems to be RTBC material now.

plach’s picture

Title: Move field language negotiation code from locale to field module » Move field language fallback code from Locale to Field and field language handling for nodes to Node
Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/field/field.api.php
@@ -1473,8 +1473,8 @@ function hook_field_attach_prepare_translation_alter(&$entity, $context) {
+  if (variable_get('field_language_fallback', TRUE) && field_has_translation_handler($context['entity_type'], 'field_language')) {

+++ b/core/modules/field/field.module
@@ -369,6 +369,60 @@ function field_system_info_alter(&$info, $file, $type) {
+  if (variable_get('field_language_fallback', TRUE) && field_has_translation_handler($context['entity_type'], 'field_language')) {

+++ b/core/modules/field/tests/modules/field_test/field_test.entity.inc
@@ -128,9 +128,9 @@ function field_test_entity_info_alter(&$entity_info) {
+    $entity_info[$entity_type]['translation']['field_language'] = FALSE;

+++ b/core/modules/node/node.module
@@ -226,6 +226,11 @@ function node_entity_info() {
+    $return['node']['translation']['field_language'] = TRUE;

+++ b/core/modules/node/node.pages.inc
@@ -524,6 +527,30 @@ function node_form_submit($form, &$form_state) {
+  if (field_has_translation_handler('node', 'field_language')) {

The field translation handler name is supposed to be a module name. Actually Node is providing the code dealing with node field languages, hence we should probably go with 'node' instead of 'field_language'. I'd suggest to just remove the translation handler name from the field_has_translation_handler() call in field_field_language_alter(), this will provide language fallback for every entity type if there's at least a translation handler enabled for it.

BTW, when we have an entity translation UI in core I would just drop the field translation handler concept altogether, since it has been introduced with the idea that we would have no UI in core for translatable fields.

+++ b/core/modules/field/tests/field.test
@@ -3414,3 +3414,130 @@ class EntityPropertiesTestCase extends FieldTestCase {
+
+/**
+ * Functional test for multilingual fields.
+ */
+class FieldMultilingualTestCase extends WebTestBase {

field.test already has a bunch of tests for generic field language support. This one should totally go in node.test.

vasi1186’s picture

Status: Needs work » Needs review
StatusFileSize
new13.88 KB
new19.61 KB

Attached a patch to implement the changes from #8.

plach’s picture

Status: Needs review » Needs work
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeFieldMultilingualTestCase.php
@@ -0,0 +1,137 @@
\ No newline at end of file

Just a minor thing: no new line at the end of the file. Otherwise looks good, great work!

vasi1186’s picture

Status: Needs work » Needs review
StatusFileSize
new24.72 KB

For some reason, after making the diff on the latest d8 version, the patch got much bigger, but it should work as before...

plach’s picture

#11 actually deletes LocaleMultilingualFieldsTest, which actually #9 didn't do. RTBC if the bot is happy.

gábor hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

In that case, tabididu!

catch’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/field/field.installundefined
@@ -6,6 +6,14 @@
 /**
+ * Implements hook_uninstall().
+ */
+function field_uninstall() {
+  // Clear some variables.
+  variable_del('field_language_fallback');

Field is a required module, can never be uninstralled, and I'm sure it has other variables that aren't cleaned up like this, so this should be removed unless we start properly putting in hook_uninstall() to all required modules.

+++ b/core/modules/field/field.moduleundefined
@@ -369,6 +369,60 @@ function field_system_info_alter(&$info, $file, $type) {
+function field_field_language_alter(&$display_langcode, $context) {
+  // Do not apply core language fallback rules if they are disabled or if
+  // the entity does not have a translation handler registered.
+  if (variable_get('field_language_fallback', TRUE) && field_has_translation_handler($context['entity_type'])) {
+    field_language_fallback($display_langcode, $context['entity'], $context['langcode']);
+  }

Is it OK to continue defaulting to TRUE here if this is baked into the field system. If I only have one language on my site won't that mean extra code to run?

Also any particular reason to have field module implementing it's own alter hook?

vasi1186’s picture

Status: Needs work » Needs review

For the first comment, I will remove the hook.

For the 2nd observation: I think it is good to default to TRUE, even if there is some extra code to run in case there is only one language, because then if the site has more languages, then people will have to know that they must set that variable to TRUE in order to have this functionality.
Regarding field implementing it's own alter, dont't really have a particular, strong, reason for that... I can move the code into the field_language() function itself if you think this would be better...

fubhy’s picture

Status: Needs review » Needs work

I think you forgot to upload your patch or accidently set it to 'needs review'

vasi1186’s picture

yes, accidently set it to "needs review", I think it was preselected...

gábor hojtsy’s picture

So the previous code never set 'field_language_fallback' to FALSE either, it was always TRUE, but merely by being in the locale module, it was only effective when the module was on. Now it would always be effective (unless specifically set to FALSE), so it will be down to field_has_translation_handler() to figure out you don't have a translation handler and return FALSE. That sounds like the logic change really. I think @plach did not want to use module_exists() or anything else because that makes it less pluggable.

I'm also fine with not implementing an alter hook and putting code in right away.

vasi1186’s picture

Status: Needs work » Needs review
StatusFileSize
new3.08 KB
new25.37 KB

Attached a patch with these changes:
- removed the hook_uninstall for the field module.
- removed hook_field_language_alter() from the field module and put the logic in the field_language() itself.
- by default, "field_language_fallback" is FALSE and the value is updated in hook_language_insert() and hook_language_delete().

vasi1186’s picture

gábor hojtsy’s picture

Looks like you have the same code basically in the insert and update hook. It was just one call before, but now its more code and comments. What about unifying those and calling one from the other?

gábor hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

We discussed in person that due to the order of the hooks invoked, it is required to have slightly different code/conditions in the two hooks. So as it was RTBC before with review from various people and the concerns are addressed, back there.

vasi1186’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new25.33 KB

Rerolled the patch for the latest version of d8

gábor hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

Still looks good, thanks for the reroll.

gábor hojtsy’s picture

webchick’s picture

Cool, this looks like sensible clean-up.

I had one question about this:

 function field_language_delete() {
   field_info_cache_clear();
+  // If the number of languages is less than 2, disable the core language
+  // fallback rules.
+  // Because the language_count is updated after the hook is invoked, we check
+  // if the language_count is less or equal with 2 at the current time.
+  if (variable_get('language_count', 1) <= 2) {
+    variable_set('field_language_fallback', FALSE);
+  }
 }

I was worried that this would result in a lot of unnecessary variable_set() calls, but it was pointed out that this hook only fires when languages are deleted/inserted manually, which will be relatively rare.

Committed and pushed to 8.x. Thanks! :)

webchick’s picture

Status: Reviewed & tested by the community » Fixed
gábor hojtsy’s picture

Issue tags: -sprint

Done, so removing sprint tag. Thanks!

Status: Fixed » Closed (fixed)

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

andypost’s picture

Filed related issue #1977790: Drop node_entity_info_alter() as unused
Seems translation key could be now removed in favor of controllers