Problem/Motivation
In #2384863: Translation language base field handler should use views field handler, provide unified options we realized there is no way for code relying solely on field definitions and entity objects to determine the language of a translation entity object. [The motivation for that issue is that we need Views to be using entity-aware formatters for all entity information being displayed as views fields. Currently it is just not really possible to make an entity-aware formatter for Translation Language of an entity, because given a translated entity object, there is not any way to read off it what it's languge is.]
Additionally we have an inconsistency when using the Entity Translation API, as the following condition is true:
$entity->langcode->value = 'en';
$translation = $entity->getTranslation('de');
// Now you would expect the langcode field to have the value based on the currently active translation, like language(), but it does not, as langcode always contains the original/default/source language, the following condition would be FALSE:
$translation->langcode->value == $translation->language()->getId();
Proposed resolution
- Make the
langcodefield definition translatable and add adefault_langcodeboolean definition allowing to determine the default language. This will make entity querying more consistent as thedefault_langcodeconditions will apply to a regular field instead of a denormalized SQL column. - Remove special-casing around langcode in our SQL storage:
langcodewill be stored as a regular translatable field, as well asdefault_langcode, in the field data table. - Make the default
langcodeavailable as denormalized column in the base table along with the defaultrevision_id. - Add a constraint to ensure the
default_langcodefield is true only for one translation object in the set.
Remaining tasks
Validate the proposed solutionWrite a patchReview it
User interface changes
None
API changes
- All
langcodefield definitions for translatable entity types will automatically be marked as translatable. - A
default_langcodefield definition will be automatically defined for all translatable entity types.
Beta phase evaluation
| Issue category | Task because the current functionality is not broken, although the current implementation might severely reduce the ability of introducing further improvements. |
|---|---|
| Issue priority | Critical because the changes introduced here will have an impact on the very foundations of the conceptual model behind our Entity Translation API. |
| Prioritized changes | The main goals of this issue are reducing fragility, as it will remove some special-casing around the handling of the langcode field both on the API and on the storage levels, and streamlining how translation language is handled wrt the other fields. |
| Disruption | Disruptive for contributed and custom modules using the langcode field to retrieve the default language (which is not the recommended way). |
| Comment | File | Size | Author |
|---|---|---|---|
| #46 | et-langcode_translatable-2431329-46.patch | 59.12 KB | plach |
Comments
Comment #1
plach- Berdir wrote in #2384863-142: Translation language base field handler should use views field handler, provide unified options:
Yep, I updated the proposed solution to clarify my current approach.
In my initial code I simply added
langcodeto the list of translatable fields CT does not expose on the configuration UI. We could extract the related code into a method on the content translation manager so that's available to any other module. However being able to rely on an API would sound even better, although that can be a second step.- yched wrote in #2384863-143: Translation language base field handler should use views field handler, provide unified options:
We currently special-case the internal storage of the default langcode to make it easier to change its value, as otherwise we'd need to rekey all values in that case. However allowing to change the translation language would also require that (plus some messing with
langcodevalues in field table records), so this might be a valid goal. However I'd rather address these issues in a separate task, unless it becomes clear they belong here.A translatable BFD supports translation and by default is enabled for translation for every bundle. Translation can be disabled via a config override.
As I wrote above, in my current code
langcodefield translatability cannot be configured, same as with content translation metadata fields. We might want to enforce this at API level somehow.Comment #2
plachHere is an initial patch, many things to refine. Just wanted to see if I'm on the right track.
Comment #4
fagoCould we have a default_langcode field which just stores the langcode instead? Or would that make things more difficult?
$entity->default_langcode->value == 'en' would be nice to have imo - also it's a good replacement for the current behaviour of $entity->langcode->value then.
Comment #5
berdir@fago: We need the default flag in the data table, so that we can query on only rows which are the default language. Storing the default language doesn't give you that directly, you'd have to compare that with the actual language.
It would also be a storage change, this is AFAIK not.
Comment #6
plachWhat @Berdir said.
Moreover I'd avoid having two language codes on the entity object, it might make things more confusing than they need to be. This way the only way to get the default language is via the API (
$entity->getUntranslated()->language()).Comment #7
plachComment #8
fagoI see the reasoning, ok to me.
So, should be the boolean be then "translated" yes|no?
Introducing another terminology with "default langcode" here seems unfortunate, but keeps 1:1 with the storage. Unfortunate, but probably the best do now. Maybe we can clarify the relationhip to the "original default language" here.
Comment #9
plachThe attached patch should fix test failures. Still lots of things to clean-up, though. My main doubt atm is whether we should automatically define the
default_langcodefield in the entity manager: we are not doing that anywhere else, OTOH I think freeing developers from having to define fields that are required to implement the entity system business logic makes some sense too.@fago:
Personally I'd prefer to use
default_translation, that would match theisDefaultRevision()method. However that would require deeper schema changes. However I'd like to investigate the possibility of using an alternative name too.Comment #10
jhodgdonHuh what?
Um. I am really confused here. When you first create an entity, assuming you have the i18n modules all turned on and configured, you can set its language to anything, and that becomes the original/base/default language for that entity. It need not match the site's default language. So I don't think this logic is correct at all.
But maybe I'm misunderstanding what this method is about. Can we change the terminology here? There are so many things called "default language". I suggest adopting this terminology, to avoid confusion:
- Default site language - a property of the site (the setting for which language is the site default) This is set in locale.module at admin/config/regional/language
- Default language for an entity - a property of the entity bundle (the setting for the default language of this entity/bundle when you create new content). This is set in content_translation.module at admin/config/regional/content-language, and need not match the site default language.
- Original language (or base language?) - a property of an entity (the original language this particular entity was written in). This is set on the node/add form when you first create an entity, and need not match the site defeault or entity default language. Note: When you go to the node/N/trnanslations page, this language is already shown on the page there as "Original language".
- Translation language - a property of an entity translation, the language you translated it into. Cannot match the original language of the entity; may be equal either to the default site or default entity language, or not.
- Original? - Boolean property of an entity translation: TRUE if this is the original translation of the entity (the translation in the original language). I think there are some queries that need this.
So I just did a test (without any patches). My default languages are all set to English. I created an Article and set its language to Spanish (so the original language of this node is Spanish). In the database, node_field_data.default_langcode is 1, and when I translate this node into English, the value on that translation is 0.
On another node I created in English and translated into Spanish, node_field_data.default_langcode is 1 for the English translation and 0 for the Spanish one.
So that database field already has the meaning of "original?" not "Matches the default site language" or "matches the default entity language for this bundle".
Comment #11
plachThis should be cleaner. We have just two things left, I think:
default_langcodefield definition (and the related entity key) in the Entity Manager (see #9)?default_langcodeto something more meaningful likedefault_translation? This would be a huge change at this stage of the D8 life-cycle, so my answer is: I guess no, although I'd like it.If the answers are Yes and No, then this should be ready. Reviews welcome.
@Jen:
Too late for an elaborate reply, the short answer is:
default_langcodein the entity system is a flag indicating the original (untranslated) field values. We discussed that terminology a lot and what we have now, is what looked like the least bad option :)Edit: ignore interdiff #9 above, not sure how it got there. Wrong patch names, btw.
Comment #12
yched commentedI like default_translation - how big of a change would that be exactly ?
Comment #13
yched commentedAlso, re: "Should we hard-code the default_langcode field definition (and the related entity key) in the Entity Manager"
That seems to makes sense to me ? If the entity system relies on the presence of a default_langcode field, and on the actual content of its definition (i.e a specific entity type has no use case for defining it a bit differently, and can only break stuff by doing so), then it feels right to avoid entity type authors the burden of copy/pasting it over and over again ?
Comment #14
plach@12:
Well, aside from core changes (we still have quite a few hard-coded SQL queries around relying on the
default_langcodecolumn), it would be a storage change (not that concerning, since we do not support the upgrade path yet), but above all an API change, as entity queries would need to be updated. We need committer feedback, I think, tagging accordingly.Comment #15
plachThis adds a few more checks to ensure field data consistency and further test coverage.
Comment #16
plachIssues still to be created...
Comment #18
plachFixed failures and qualified @todos.
Comment #19
plachSpoke about this with @catch and @Berdir: neither of them finds
default_translationany better thandefault_langcode, so not worth doing the change.@catch said he'd be ok with renaming the field, if we found a really compelling alternative, but I'm not sure it's worth holding back this issue on that.
This should be ready, reviews welcome :)
Comment #20
dawehnerJust a review from someone without a lot of knowledge about the entity system.
Its odd that this has to be configurable, but well, i guess this is how we do it everywhere.
Nitpick: Afaik we use more === these days.
Should we update the documentation of FieldableEntityInterface::onChange to reflect the thrown exceptions? Not sure about our way of doing here.
... can't we just split up the exception to throw an exception for the last case which tells better what is going on?
Just curious, should that kind of logic on the longrun be moved to the table mapping?
Just curious, why can we stop checking isTranslatable()?
Small tip in case you want to write less in the future. You can use
->willReturnMap($map) / ->willReturnValue()Comment #21
jhodgdonI took a careful, long look at this patch, with plach in IRC helping me understand what is all doing. I have a few questions/comments -- but other than the questions below, I think the patch is nearly ready and the new tests look adequate to test the new functionality.
a) As one point, even after the previous conversation about the 'default_langcode' field, I *still* got confused about it being Boolean while reviewing. ;) But I understand that changing it now would be disruptive, so will not complain again. :) [This is not something to fix/answer, just a comment]
b) I love all the - lines in the patch without corresponding + lines -- all those if/else things for handling the langcode field that are now GONE. Super! The one exception being in dawehner's review in #20, item 5. I'm curious about that too? And there's another new one in content_translation.admin.inc but I think that cannot be avoided. [not something to fix/answer]
c) Regarding dawehner's review in #20, item 3 - yes we should add @throws. Probably on the interface, but if it can't be for some reason, then instead of using @inheritdoc you should copy the interface docs over to the class method and add @throws to the end.
d) I'll leave plach to answer the other points in dawehner's review.
e) Nitpick, in SqlContentEntityStorageSchema::onFieldStorageDefinitionDelete()
use "" instead of '' quotes to avoid \' here
f) I especially like that you tested the "use your own key" functionality for the default_langcode entity key. But I saw one possible hole in the testing: You removed a bunch of lines in SqlContentEntityStorageTest that verified that default_langcode was part of the "extra fields", and it shouldn't be any more, so that is good. But I didn't see that default_langcode was added to the test anywhere to verify it was part of the base schema? Maybe that doesn't need to be done, not sure?
g) We'll need a change record.
I think there's enough in my review and #20 to mark this needs work now?
Comment #22
plachThanks!
@dawehner:
1: Not sure why it's odd, it's just more flexibility :)
4: I don't get what you are asking, can you please rephrase?
5: Yep, we have #2274017: Make SqlContentEntityStorage table mapping agnostic for that
6: Because in our standard table layout field translatability does not affect the schema
@jhodgdon:
f: I think schema is sufficiently covered by our regular schema tests and by
EntityTranslationTest. However you are right that we are not ensuring that thedefault_langcodefield is always defined. I added also coverage for thelangcodefield exception.Comment #23
plachDraft CR at https://www.drupal.org/node/2446315
Comment #24
dawehnerWell, the exception message is this:
but it could just say that the langcode field should always be marked as translatable.
Comment #25
jhodgdonCR looks good -- I made a couple of small edits for better readability (I hope). And I added a section telling developers that they need to make their langcode fields translatable.
Can you verify it is still accurate? https://www.drupal.org/node/2446315
The rest of the changes look good to me. I think @dawehner's idea in #24 is good for the exception message, but other than that I think it's ready to go.
Comment #26
amateescu commentedThe patch looks good to me as well, just found one tiny thing:
$activeLangcode is declared with a default value of
LanguageInterface::LANGCODE_DEFAULT, is there any reason we're not using the interface here?Comment #27
plachI guess because my autocompletion engine is lazy :P
Comment #28
dawehnerThank you
Comment #29
jhodgdon+1
Comment #30
webchickSo.. I stared at this for at least a half hour and can't quite parse this. The issue summary looks accurate, but assumes a lot more understanding of D8MI than I possess and this time zone is not friendly to Europeans. :) (I did manage to find Berdir, but he also needed to take a closer look at this before he could really walk through it.)
Something that that would help from a reviewer POV is providing a series of steps one can take in the UI to reproduce whatever the situation is we're trying to fix with this patch. It's not clear from the summary. It's also not clear from the summary the big benefits we get from making the langcode field translatable, because at a glance it sounds like a "BOOOOOM brains all over the floor" kind of change. ;)
Also, regarding the change record:
This seems like a very easy thing for developers to forget to do. Is there a chance we could default translatable to TRUE for langcode fields somehow? That would also make this change less disruptive, and also reduce fragility in contrib.
(In about 16 hours, I will be traveling for basically 2 days, so please don't hold committing this up on me.)
Comment #31
jhodgdonOne of the main reasons we need this is the (critical) issue that is postponed on it:
#2384863: Translation language base field handler should use views field handler, provide unified options
Basically, we want everything on an Entity, when used in Views, to be using Field UI to format it, or at least it absolutely needs to be using an entity-aware formatter. This was not possible to do with the Language views-field, because it is not currently a real entity-field. I mean, it's terrible: when you load a particular translation of an entity, there is apparently no way in code to get the translation's language off the object, so unlike all the other information on the entity, you can't easily write an entity-aware formatter for it.
I'll add this to the issue summary... I don't know of other ways this affects the UI or even if there are any. I think it's mostly a developer-oriented change. It makes a LOT of the entity code a LOT cleaner, and cleans out a LOT of special cases for "When someone is trying to get language information out of the danged entity, special case it because we can't read it directly", by simply saying "language is a translatable field".
We do really need to do this...
Comment #32
plachThe attached patch addresses the following part of Angie's feedback:
I also updated the change record accordingly.
The updated issue summary looks good to me, and since Angie is not going to be available soon, I'd put this back to RTBC if the interdiff here looks good too.
Comment #33
plachComment #34
plachComment #35
plachComment #36
berdirUpdated the issue summary and tried to make the code snippet more clear, used an == instead of != condition, because == is what you'd expect.
Also can't see any mention of @chx/MongoDB here. This will affect him too, we should probably get an OK from him. I *think* it will make his life easier as well but it would be good to have a confirmation.
@webchick: There is nothing broken right now when looking at the UI, because we have no code that would incorrectly rely on wrong assumptions. But the referenced views issue is trying to do exactly that and failed to do so.
Didn't manage to read the patch yet, but the proposed changes look great to me.
Comment #37
berdirNot a full review, just noticed this:
See #2443663: PostgreSQL: Fix system\Tests\Entity\EntityDefinitionUpdateTest, I think you should check for the DatabaseException interface instead, as other backends might return slightly different exceptions.
Comment #38
plachAddressed #37.
About @chx: I spoke with him about this in IRC, he seemed to be ok with the proposed changes.
Comment #39
chx commentedThere are two sides of a storage engine, write and read. Read is being tested at #2436209: Test ContentEntityBase constructor called with multilanguage values . If that issue needs to change, that's fine, but that test is almost a 100% copy of what MongoDB could/should do (currently it's mimicking core, less optimal) so as long as it exists and passes I know what to do. As for storage, this is what I do:
I think that doesn't change, does it?
Comment #40
plach@chx:
Yep, that code is unaffected.
Comment #41
gábor hojtsyThe changes look good to address all concerns raised.
Comment #42
alexpottThis would be easier to read like so - also if the base field definition does not exist for an entity key - is that not a problem?
Missing the \InvalidArgument. Also we should be detailing the circumstances in which these exceptions are thrown.
Comment #43
plachAddressed #42.
Comment #44
plachGreen, back to RTBC
Comment #45
jhodgdonProbably you shouldn't RTBC your own patch. ;) Two comments/questions:
a) Nitpick on the @throws docs in FieldableEntityInterface::onChange():
We normally want each list item to start with a capital letter and end in .
b) Nitpick in the new EntityManager logic sections:
Last array element should end in , if you use vertical spacing. This occurs three times in the patch.
[EDIT: removed stray text after this]
Comment #46
plachWell, I thought changes were minor enough to go back directly to Alex, obviously they weren't...
Comment #47
gábor hojtsyLooks good. Thanks for the additional review @jhodgdon.
Comment #48
jhodgdonProblem is plach, you took Alex's code as written. ;)
Comment #49
alexpottCommitted a21abc7 and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to issue summary.
Comment #51
plachYay, thanks everybody!