Beta phase evaluation
Issue priority | Major because the API changes required to support the integration are not possible later. |
---|---|
Disruption | Changes are within config_translation, language and locale modules. Only modules integrating with the typed configuration / configuration translation API are affected and those modules are very limited in number. |
Updated: Comment #100
Problem/Motivation
The underlying problem that spawned this issue is:
It is currently not possible to translate values configuration values that consist of text with a text format.
The discussion of this problem and its resolution revealed a number of corollary problems:
- Because Views allows to place formatted text into Empty/Header/Footer areas and Configuration Translation allows to translate those views you can currently translate the formatted text, but without the context of the actual text format. This is both confusing and - as the source value is simply printed into the HTML - allows XSS injection in the Configuration Translation UI. (This is exploitable for anyone with the
administer views
permission.) Note that this specific issue will be fixed in #2144505: Views does not use the text format type for formatted text, but this issue will lay the groundwork for that one. - Configuration Translation can only handle primitive data types (in other words: strings) and is not capable of translating complex data structures. In this case we want a mapping containing a
value
and aformat
needs to be translated. - Configuration Translation explicitly invokes the API of Interface Translation to integrate shipped configuration with localize.drupal.org. When updating language overrides of shipped configuration from code, however, Interface Translation does not react properly leaving the system in an inconsistent state where the string translations do not match the configuration translations.
LanguageConfigOverride
does not dispatch an event when saving or deleting the configuration override.- There is no way to retrieve the language code of a
LanguageConfigOverride
. - Instead of traversing the configuration schema Configuration Translation traverses the form values. This is not a bug, but traversing the schema is superior.
- The form structure of the Configuration Translation UI is repetitive.
- Form element classes cannot override the display of the source configuration in the Configuration Translation UI.
- Interface Translation invokes unnecessary deletes on non-existent language overrides.
Proposed resolution
- Add a configuration schema type of
text_format
that is translatable (on the mapping level) so that Configuration Translation saves the entire object into the language override. The mapping consists of avalue
sub-element that is a translatable string and aformat
sub-element that is a non-translatable string. This way Interface Translation only synchronizes thevalue
with localize.drupal.org, but not theformat
. - Adapt
ConfigTranslationFormBase
to support complex data structures that are translatable and save them wholesale. To facilitate this the form structure is adapted to be less repetitive and to be consistent with the configuration structure. As part of this the traversing of the configuration schema is moved into aListElement
which is used as the form element class for theMapping
andSequence
schema types. - Remove the
Interface Translation
integration fromConfigTranslationFormBase
and into aLocaleConfigSubscriber
that reacts to theConfigEvents::SAVE_OVERRIDE
andConfigEvents::DELETE_OVERRIDE
events. - Add a
FormElementBase
class which provides base logic for simple form elements. - Add a
TextFormat
form element to the configuration translation system. - Change
locale_config_update_multiple()
to not (needlessly) delete non-existent language overrides. This is fixed as part of this issue because it is necessary to make tests pass.
Remaining tasks
Steps to test
- Apply patch
- Install the Configuration Translation Test (
config_translation_test
) module. (Note that this requires the$settings['extension_discovery_scan_tests'] = TRUE;
trick which is included in the shippedsettings.local.php
- Add a language on
/admin/config/regional/language/add
- Go to
/admin/config/media/file-system/translate
- Ignore the Edit link, and instead Add a translation.
- You should see
- Uninstall the Configuration Translation Test module
- Change
plain_text
tobasic_html
incore/modules/config_translation/tests/modules/config_translation_test/config/install/config_translation_test.content.yml
- Install the Configuration Translation Test module again
- Go to
/admin/config/media/file-system/translate
- Ignore the Edit link, and instead Add a translation.
- You should see
- Click on the respective source button on both sides.
- You should see
For extra credit:
User interface changes
None.
API changes
\Drupal\config_translation\FormElement\ElementInterface::getFormElement()
is renamed togetTranslationBuild()
and is changed in behavior to include the entire subform for this element, i.e. both souce and translation.\Drupal\config_translation\FormElement\ElementInterface::setConfig()
is introduced. Because form elements can now control the entire form structure, they are also responsible for setting the configuration values correctly.
API additions
- The
text_format
configuration schema type. ConfigEvents::SAVE_OVERRIDE
andConfigEvents::DELETE_OVERRIDE
events and a correspondingConfigOverrideCrudEvent
class are introduced.- A
LanguageConfigOverride::getLangcode()
is introduced. - The
ConfigEvents::SAVE_OVERRIDE
andConfigEvents::DELETE_OVERRIDE
events are invoked fromLanguageConfigOverride
. \Drupal\config_translation\FormElement\FormElementBase
and\Drupal\config_translation\FormElement\ListElement
are introduced.
Comments
Comment #1
webflo CreditAttribution: webflo commentedComment #2
Gábor HojtsyComment #3
Gábor HojtsyDiscussed this with @webflo. This would be much cleaner if the type altering would be putting in the form class all the time, so we can remove this stacked logic. The alter could put in the form element class if it has a type and translatable. Then we'd have that one place to look for what is what.
debug :D
Comment #5
webflo CreditAttribution: webflo commentedNext try. No interdiff because i forgot a few files in the first patch ...
Comment #6
webflo CreditAttribution: webflo commentedDoh.
Comment #7
webflo CreditAttribution: webflo commentedComment #9
webflo CreditAttribution: webflo commentedOk, the logic in config_translation_config_translation_type_info_alter was wrong ...
Comment #10
YesCT CreditAttribution: YesCT commentedstarting to write how to test steps in the summary.
remaining task: Convert the existing elements in core
needs specifics. are they just the views, and so will be done in that views issue, or are there others? How do we know where they are?
Comment #11
YesCT CreditAttribution: YesCT commentedI'm not sure why the test uses the media file system config form for the test. Makes the test rely on that. Can we make our own test config thing and use that? Maybe we already have one.
Screenshot of that translate page though.
But on save I get:
spotted a few little things in the patch to fix. new patch coming.
Comment #12
YesCT CreditAttribution: YesCT commentedadded install to steps to test
Comment #13
YesCT CreditAttribution: YesCT commentedmaking issue for this.
(and no : in @todo)
https://drupal.org/node/1354#todo
updated comment.
maybe this should be an else if, and why only if not a form element? Should we set the default to be textfield anyway? what else might definitions be if not type form_element_class?
I didn't fix anything here. waiting for feedback.
oh, maybe this logic is being moved into the hook.
nit, missing newline. added.
adding type, string
not sure why we are using the media filter config for this test. Can we use a test config so we dont rely on that? Maybe we have a test config already we can re-use or copy from another test.
I dont think we put uuid's in default config. Maybe that was just copied based on an active config file.
also, it's been a while but I remember langcode: en coming last in config files. Not sure, and doesn't really matter as this is test config.
removed uuid, didnt move langcode.
Comment #15
vijaycs8513: config-translation-text_format-2144413-13.patch queued for re-testing.
Comment #16
Gábor Hojtsy@YesCT: indeed, we only found text with format in config in views by grepping the code. So no other core is affected as far as we found.
Comment #17
Gábor Hojtsy@YesCT: re #11/#13 on why we use the file settings screen, that is also what the other existing test does. We can define our own route and attach our config to that route. This is just a shortcut to not needing to define a route with a valid callback (that would be lots of overhead just so we can attach the config translation stuff to *something*). We just picked an existing route. Again, that is what the existing test already does for the alter hooks.
#13/1: added reasoning on the todo at #2145633: Use standard discovery facility in TypedConfigManager instead of one-off implementation
#13/2: we only set the form element class if not already set. this allows contrib to extend this with other type form classes provided; if we overwrite it, then contrib does not get a chance to extend; in fact !isset($definitions[$type]['form_element_class']) should probably be wrapping the whole contents of the foreach; and yes, the second if() should be an elseif then
#13/3: yes, but we can still improve it, see 2 above.
#13/4-5: thanks
#13/6: we use our own config; we just don't define a base route for our test, because that has no role in the test, so we picked an existing route; the test needs filter module because the text element with filters are only meaningful if you have filter module :)
#13/7: the default config in core is chock full of default uuids, but indeed, they only make sense for config entities, thanks for removing this
So looks like 2 is directly actionable (if logic in the hook) and we can add a route to make it a bit more cleaner, but its just doing what the tests already do. However if the behavior itself is as buggy as you experienced in #11, that sounds concerning. Why does the test pass (which also has locale module enabled and foreign language configured?).
Comment #18
Gábor HojtsyUpdated with code tidying for the foreach and comments on the route - config mappings that these are just for testing and does not really matter. I think its overkill to define a test route just so we can mostly ignore it and attach a config translation tab on it when we can do it on any existing route just as easily. I also added the comment to the existing core test (on theme translatability, sorry, not alters :).
This should address all code review points. Now is this buggy when translating? :)
Comment #19
Gábor HojtsyThis is said to still be buggy with locale, its getting an array instead of a string.
Comment #20
webflo CreditAttribution: webflo commentedComment #21
webflo CreditAttribution: webflo commentedDoh.
Comment #22
Gábor HojtsyFix weekend tag.
Comment #23
webflo CreditAttribution: webflo commentedComment #24
webflo CreditAttribution: webflo commentedRe-roll.
@Gabor: Can you summarize the problems with your last patch? I applied it and translated a element (with text format). But could not find any errors. Thanks!
Comment #25
webflo CreditAttribution: webflo commentedComment #26
prodosh CreditAttribution: prodosh commentedComment #28
tstoecklerRe @prodosh: I think that is this issue: https://drupal.org/node/2167039 not this one
Comment #29
Gábor Hojtsy@webflo: the problem is explained in #11, when translating default config, the locale integration fails due to now getting an array instead of a string when locale saves :/
Comment #30
tstoecklerDiscussed this with @webflo on the weekend. The problem can probably be solved by moving the translatable property one level down into 'value'. Having the format translatable is pointless anyway.
We also discussed that we want to hide the text format selector on the translation form and we need #1423244: #allowed_formats property for #type 'text_format' to filter available formats for that. I'm not sure if we want to actually postpone this issue on that, though. We could just as well introduce '#allowed_formats' here, and then it would start working as soon as that issue goes in.
Comment #31
Gábor HojtsyYeah if we don't allow modification of the format, then we need to work that out ourselves (and still keep the possible WYSIWYG working)
Comment #32
tstoecklerOohh WYSIWYG is another interesting dimension to this. So does that mean you are in favor of (hard) postponing this on #1423244: #allowed_formats property for #type 'text_format' to filter available formats? I would personally be pretty strongly against doing our own form processing until that lands.
Comment #33
Gábor HojtsyYeah WYSIWYG integration should work out of the box by the nature of using the text format element. I don't think there is a problem with letting translators pick other formats they have access to. The whole formats vs. translators problem is a mess anyway. Eg. i18n has a full settings setup for permissions, since you may have issues translating to target formats that you otherwise don't have access to. I don't think we should solve or think of that problem here, just saying :D
Comment #34
YesCT CreditAttribution: YesCT commentedI'm going to reroll this now. (https://drupal.org/patch/reroll)
Comment #35
YesCT CreditAttribution: YesCT commentedThat was an easy reroll, no conflicts.
Needs review to see if this gets the same fails as #24.
Comment #36
YesCT CreditAttribution: YesCT commentedI took out the instructions to reproduce the error when translating the body label on a content type.
I was able to do that without an error, and also, that translation form did not use a input filter.
Comment #37
YesCT CreditAttribution: YesCT commentedgr. img tag
Comment #39
YesCT CreditAttribution: YesCT commentedas I summarized irc conversation into pros and cons of different possible solutions, I could see they were almost mirrors. So it's possible to maybe just take out the cons, of each and just list the pros, and assume one pro of one is the con of the other. But I left it spelled out as I think it might be more expressive to be able to word them different and they might change as more people voice their opinion.
Comment #40
YesCT CreditAttribution: YesCT commentedforgot the screenshots
Comment #41
robertdbailey CreditAttribution: robertdbailey commentedI was able to reproduce the error per the test steps (attached).
Comment #42
Schnitzel CreditAttribution: Schnitzel commentedfirst a reroll
Comment #44
Schnitzel CreditAttribution: Schnitzel commentedComment #45
Schnitzel CreditAttribution: Schnitzel commentedso, fixed all the failing tests, and also implemented a new function in ElementInterface which allows to return the translation string.
I have some strange things with running the
hook_config_translation_type_info_alter
in the submit handler of the Form.Because when building the form the patch iterates through all the Config Schema, but while saving the translation I don't see a possibility to iterate through them, because we already iterate through the provided values. So I make some ugly things (in my opinion) to find if there is a schema for some data types.
Comment #46
Schnitzel CreditAttribution: Schnitzel commenteddiscussed with tstoekler and where not sure if we needed the check for all the keys.
let's see what the Testbot says.
Comment #47
robertdbailey CreditAttribution: robertdbailey commentedupdated patch to reflect the new location of the core.data_types.schema.yml file.
Comment #48
robertdbailey CreditAttribution: robertdbailey commentedPatch #46 did not apply because of the changed file location. So, patch #47 is a manual re-roll. As such, there is no proper interdiff possible, so the attached "interdiff" is just a diff between patch files 46 and 47.
Comment #49
robertdbailey CreditAttribution: robertdbailey commentedupdated summary (steps to test) to include instructions for temporarily moving module config_translation_test into the modules directory because of test modules no longer being discovered (issue https://drupal.org/node/2188661), with a likely long-term solution being a settings.php variable to discover test extensions at runtime (issue https://drupal.org/node/2198713).
Comment #50
robertdbailey CreditAttribution: robertdbailey commentedFollowed the test steps in the issue summary, and all seems to work properly: Step 11's error now does not appear. Before submission:
After submission:
Comment #51
robertdbailey CreditAttribution: robertdbailey commentedReviewing this with @tstoeckler, changed the 'translatable' flag for config_names from the mapping level to the underlying field level. This currently appears to work from the interface side; however, the created yml file still contains the "format" attribute of underlying sub-field "content". This still needs work to remove this from the yml file.
Update 2014.04.21: I have tried to duplicate the testing failures locally, and I have not been able to do so. On my local machine, all tests under Configuration Translation pass. Can anyone think of why this might be? In any case, since I am not able to regenerate the failures using individual testing. The previous failure for the current patch was the following string:
FAILED: [[SimpleTest]]: [PHP 5.4 MySQL] 67,506 pass(es), 76 fail(s), and 0 exception(s).
All failures above were in ConfigTranslationUiTest.php. I will rerun the SimplyTest.Me in case perhaps the failures go through on another pass.
Comment #53
robertdbailey CreditAttribution: robertdbailey commentedSome of the form-field names changed as part of this latest patch, so tests that were referencing the old field names are now failing. I'll update those and then resubmit the patch.
Comment #54
robertdbailey CreditAttribution: robertdbailey commented51: config-translation-text_format-2144413-51.patch queued for re-testing.
Comment #56
Gábor Hojtsy@robertdbailey: are you working on that patch update?
Comment #57
robertdbailey CreditAttribution: robertdbailey commented@Gábor Hojtsy: I got stuck on this as of 4/21, but I will reattempt this as my project for this week!
Comment #58
tstoecklerHere we go.
This does the following:
- Adds a bunch of inline comments
- Removes references to ElementInterface::getTranslationString() as it is no longer needed.
- Fixes tests. Most of the changes were due to the element name changes, as 'translation' is no longer a parent, and the form key is no just the sub-key name, and not the entire tree. In other words: Before the name was
config.file.name[top-level][second-level][translation][top-level.second-level.third-level]
and now it is justconfig.file.name[top-level][second-level][third-level]
. So that's certainly for the better IMO, but required a bunch of updates.There are not in the interdiff, but were part of the merge with 8.x:
- Moves the configuration into config/install to keep up with 8.x
- Latest 8.x also removed the Element base class in favor of the string translation trait. Instead of re-introducing that here, I moved the getRenderedSource() base method to a SimpleSourceTrait.
Comment #60
tstoecklerThis should be green.
Forgot a use statement during the merge.
Comment #61
YesCT CreditAttribution: YesCT commentedComment #62
sunAFAICS, this is an inappropriate use of traits — the code requires the
getRenderedSource()
method to exist.A trait is overkill here. A new
FormElementBase
class is much more appropriate - provide a default implementation; if you don't like it, override it (or don't use the base class in the first place).Comment #63
tstoecklerEven though I wouldn't lose sleep over re-introducing the FormElementBase class, I'd like to get some more opinions on this first.
1.
I don't see how a trait can be "overkill". A base class is nothing but a trait with the additional constraint of (single-)inheritance. So in terms of "kill": base class > trait.
2.
The code requires $this->t() just as well, so if that is a trait now, I don't see why getRenderedSource() shouldn't be on a trait either.
3. In general I (and not only I) think that traits are superior to base classes because they do not enforce any inheritance chain (see 1.). One could argue about the value of converting all existing base classes, but for
new
code, I don't really see the point of introducing new base classes.Comment #64
Wim LeersI haven't read through the entire issue, but if the screenshot in the issue summary reflects what this patch does:
Then I have one concern: the original is shown in plain HTML, the translation is shown in the WYSIWYG editor. That's less than great.
Why don't we show both in the same way? You can load CKEditor in read-only state for the original. A quick search tells me that setting the
disabled
attribute on the<textarea>
should do the trick, so setting#disabled = TRUE
should do it for us.Comment #65
Gábor Hojtsy@WimLeers: that is amazing! Woot!
Comment #66
tstoecklerWell, the screenshot is inaccurate (sorry!), we're actually showing the filtered markup right now. But showing a disabled CKEditor would be *way* cooler. Thanks @WimLeers!
Comment #67
Jose Reyero CreditAttribution: Jose Reyero commentedTaking a look at the code, not sure I get everything -since I am not up to date with all the config translation thing - but just some thoughts:
- (Strongly) Agree with sun #62: adding a trait for this is overkill. Nope, traits are no replacement for base classes, just some device to use when you cannot effectively use simple class inheritance. Call me old fashion...
- I think the 'text_with_format' type should have the 'translatable' property set to TRUE and we should treat it as an object that is translatable by itself (and rearchitect what it takes so we can translate any data type with a simple extension, otherwise contrib modules will need to ditch this and provide their own translation form to translate other types)
- About the widget, the disabled CKEditor sounds good, as long as it allows to see the source. Some translators may prefer the formatted text, some other may prefer the markup and in any case translating the markup will allow more accurate translations. So I think either we show both or we show only the markup. Note also that there may be non-visible language dependent stuff in the markup (lang="xx", links, etc..)
- Btw, are we handling the 'you cannot access this format case' when there is a translation already that the translator cannot edit? (Some message, no submit button, etc...)
- About the 'form_element_class', maybe too late but, can't we add it into the core data types with some other hook that is not 'config translation' specific? There should be some generic way to alter the data types, right?
Overall, this is maybe too complex and I wonder whether needing all this to make one more 'data type' translatable doesn't mean we should improve how the 'configuration translation' feature in general is architected. I mean it should need a plain form element class to make one more different data type translatable and this one is a proof of concept for how extendable is the translation form in that sense. It seems not too much :-/
Comment #68
Jose Reyero CreditAttribution: Jose Reyero commentedI need to add some note: Thought my latest comment sounds overly negative, I think this is looking pretty good and the patch is ok -but for the trait :-). Note my concerns are more about the underlying system -which requires all this patch for this feature- than about the patch itself, so half of them may be a bit out of scope here.
Comment #69
Gábor Hojtsy@Jose: the proposal to add a generic config type alter in #2100223: Add an alter hook for typed config definitions was won't fixed based on feedback from @alexpott last September, which was directly followed by our need for our own alter which is why we added that.
Comment #70
Wim LeersAs long as you have the "Source" button enabled for a text format, that is possible.
Here's a reroll that adds what I described in #64. I had to modify
getRenderedSource()
to also allow arrays to be returned. Be ware, it's a hacky reroll; you'll want to clean it up, I just wanted to show in the smallest diff possible how this would work. Updates are still necessary to the base classes/interfaces/…Notice how all of the source version's CKEditor's buttons are disabled, except the "Source" button (which answers #67).
Finally, I had trouble reproducing this because I didn't have
config_translation
enabled. Apparentlyconfig_translation_test
didn't depend on it yet… :) So fixed that too.(Leaving at NW because I didn't address anything else.)
Comment #71
tstoecklerWill take a stab at updating this.
@Jose Reyero: Thanks very much for your review, much appreciated!
Some remarks in response to #67:
Can you elaborate on why you think we should do this, and how Contrib would be affected by this? We're bound fairly strongly to do it that way by the current implementation, but before arguing that I would like to discuss this on a semantical level first.
Currently we do not declare the entire text_with_format element as translatable, but only the contained value property (and not the contained format property). Essentially, we are saying that the text can be translated but the format cannot. And this is exactly how it should be, in my opinion. Translating the format part of a text_with_format is semantically wrong and should not in any way be supported. If a translator does not have access to a particular text format that translator cannot translate text written in that format. It's as simple as that.
I'd love to hear your thoughts on that.
This will be handled automatically once #1423244: #allowed_formats property for #type 'text_format' to filter available formats is in. We will then just limit the translation form to the one format that is the source format. If that has not landed when this gets to RTBC, though, we should make sure we are not introducing any security regressions so thanks a lot for mentioning that!!! Will keep that in mind.
In a perfect world, yes, yes, and yes :-). There is no typed data alter hook, though. And while form elements for typed data are in no way specific to config translation, and will have to be re-implemented for Rules / Config Inspector / ... in contrib, that's how things currently stand. I do still plan to improve this for D8 but it's certainly a separate issue.
Hmm... I'll try to justify some of the changes and also the size of the patch but it'd be great if you could be more specific about what you dislike.
In current 8.x everything that ends up in
$form_state['values']
under atranslatable
key will be saved into the language override configuration. We are currently not using thetranslatable
flag in the schema to determine what and what not to save. This is problematic for thetext_format
form element as itsformat
property has a value, but we do not want to save that in the language override configuration. That is why the patch changes the behavior to only save those values that have thetranslatable
flag set toTRUE
. This change however, is worthwhile in its own right and makes the whole form a lot less fragile. I.e. if you have a contrib module altering the config translation form and happens to make some form value appear somewhere in the form state you do not all of a sudden save that value into the configuration file.In order to traverse the schema and then - when we have a translatable element - grab the correct values out of
$form_state['values']
without any arcane magic we adapt the form values so that they resemble the structure of the configuration (schema). Because of$form['#parents']
this is easily possible. Again, this change makes the structure of$form_state['values']
much more sane as it A) removes the pointlesssource
andtranslation
separation (source
has no right being in$form_state['values']
in the first place) and B) it allows to remove an unnecessary complexity where the parent of thetranslation
key in$form_state['values']
needed to contain the full dotted "path" to the config value, as this was necessary to set the correct config value.Comment #72
tstoecklerHere we go.
Thanks a lot @Wim Leers for your input regarding CKEditor and for you initial patch. The fact that the interdiff is relative to #60 and not #70 (and that there are 2) is due to me failing at life, not because I didn't use yours!
This follows through with @Wim Leers' idea above and makes it so that both
getTranslationElement()
(previouslygetFormElement()
) andgetSourceElement()
(previouslygetRenderedSource()
) both return a render array. I also moved the appropriate parts into the newFormElementBase
(previouslySimpleSourceTrait
). This allowed to remove a couple of lines fromConfigTranslationFormBaser
. I also cleaned up a couple things in the lines that I had to move anyway (i.e. usage of$language->getId()
vs.$language->id
).Tested this both with CKEditor and without and it works nicely now (even though it took me a while). @Wim Leers I would like to ping you regarding the
@todo
about CKEditor.While working so much with the
text_format
Form API type, I realized that it is quite weird that we call the schema typetext_with_format
(even though the latter is more accurate). Do people agree that we should rename this totext_format
?Would love to see some reviews of this, this feels quite ready now. That's barring @Jose Reyero's architectural feedback of course.
Also updated the issue summary.
Comment #74
tstoecklerConflicted due to #2224887: Language configuration overrides should have their own storage. (Yes, I had a old 8.x...)
Comment #76
Gábor Hojtsy@tstoeckler: I think text_with_format is more accurate, but if we want to keep in line with the confusing name used in form API then yeah it may be better to use the same confusing name :D Also is the format selector a dropdown even if there is only one item? That seems to be not in scope for this patch, but that looks confusing. Its inviting to do something with it just to figure out you only have one item...
Comment #77
tstoecklerWow, Editor module has quite thorough test coverage. This should be green again. Also renamed the schema type to
text_format
.Just a note that the editor.module changes are @Wim Leers-approved per IRC. It would be great though if @Wim Leers could take a look at the test fixes, and whether those are OK.
Comment #78
tstoecklerOops, don't know what happened here. Will fix in next re-roll.
Comment #79
Jose Reyero CreditAttribution: Jose Reyero commentedRe @tstoeckler #71:
When thinking of contrib extending schema elements and providing their own translation widget, we need the ability for the module to define the full widget for the element. Like in this case, we need not only a text field, but a full widget with text format.
If only the text string is translatable, our generic form code would just produce a translatable text field, unless the form code itself is reworked to handle such elements like in this case.
So the fact that we need to patch the form generating code means that it is not really reusable to add arbitrary translatable elements and widgets.
It's just how you call it, but the fact is we are "translating" the text format too. Or call it what you want "to allow a different value for each language", that won't make a difference.
Comment #80
tstoecklerSorry, but I do not really understand this. In current 8.x you are correct, that this is not possible. But the current patch adapts it to work for arbitrary elements which have one or more translatable elements within them. I don't see why the fact that it wasn't like that before - i.e. that we need to patch the form generating code - can say anything about how it is reusable after the patching. I'm probably missing something...
No, that is not true! There are no different text formats per language. And it would be both very confusing and semantically incorrect if it were the case, at least that's my (strong) opinion. The fact that there is currently still a format selector is due to #1423244: #allowed_formats property for #type 'text_format' to filter available formats not being committed yet - i.e. it's impossible to render a
'#type' => 'text_format'
element without a text format selector. But we do not actually save the format into the language overrides. (Sorry if I was unclear about this detail above.)Comment #81
Jose Reyero CreditAttribution: Jose Reyero commentedDon't we? Then I may have missed that part sorry, I thought we were allowing the translator to use other of the formats they have access too.
However, if we are using the same format for all the translations, how do we handle when the source text's format changes? Are we just applying the same text format to existing translations? (This would be a security issue I'll explain if it is the case).
Comment #82
tstoecklerYes, that is how it currently is. And, yes, that needs to be fixed.
That's why it's so great to have you back! :-) I had never thought of that complexity.
You're right that just saving the text while the format can be changed is a security issue which we need to prevent. We need to somewhere store the format with which the translated text was saved in at the time of translating. And the only sane place to store that is next to the text in the language override. So in effect we do need to make the format translatable. Because we still do not want to allow people to change the text format, that would make #1423244: #allowed_formats property for #type 'text_format' to filter available formats a hard blocker of this now.
So the question then remains whether we want to allow to mark the entire
text_format
translatable or whether we want to mark thevalue
andformat
properties translatable separately. The former might (arguably) be considered more semantically correct, but the latter would be easier to implement, for the following reason:To be able to translate shipped configuration on http://localize.drupal.org we need to integrate with the
Interface translation
(locale) module. That, however, is only capable of storing strings. It cannot cope witharray('value' => 'foo', 'format' => 'plain_text')
. So we would need to refactor the locale integration in order for that to work.Also we need tests to ensure that the format makes it into the locale override as this is security-related.
Comment #83
Gábor HojtsyI agree we should store the format on the translation as well. And then there is still the question that translations may not have access to the same format the source uses. What happens then? Is that already covered?
Comment #84
tstoecklerI assume you mean translators, not translations. In that case (at least with #1423244: #allowed_formats property for #type 'text_format' to filter available formats) the translator will not be able to translate that particular piece of text, i.e. it will be a disabled textarea. That's the only sensible thing we can do in this case.
That's why this is now blocked on that issue. I'd like to get this done - in terms of reviews and architectural reviews - first though, before actually marking the issue postponed. I.e. I'd like to go straight from postponed to RTBC, so that we don't lose more time.
Comment #85
Gábor Hojtsy@tstoeckler (#84): that default behaviour makes sense to me for formats without access.
Comment #86
Jose Reyero CreditAttribution: Jose Reyero commented@tsoeckler,
Now I think I get it, thanks for the explanation, sorry for not doing my homework and being up to date with all that background info.
Here are my (now up to date :-) thoughts:
- First of all, I think we need the whole thing (text + format) to be marked as 'translatable'. Though you can argue about this specific case, I think we want to implement some generic mechanism for configuration 'things' to be translatable. I mean now it's text + format, but a contrib module may want to handle more complex stuff like 'price + currency' or a map ('image', 'cooordinates', 'zoom level'...). Let't not get lost into the detail (because arguably you can always translate piece by piece) but let's think of objects that need to be translated as a whole ,that is, they need it to be editable in a single widget, and this would be the case: 'need a specific widget for editing/translating it'.
- Moreover, that 'objects' cannot rely on custom forms because they always can be a single config object or they may be part of a bigger one, a mapping, a list, etc... And here comes the need of mapping 'configuration name' and 'translation widget' for the full object.
- About how locale, I don't think we need to change too much because default strings can be translated without the format, that should be the same. However we may need to extend LocaleTypedConfig to get 'translatable objects' not only translatable strings. This is how I see it working:
This would be the schema definition:
That 'schema', when 'compiled' (text element inherits translatable = TRUE) will look like this (only relevant parts):
Now when the LocaleTypedConfig parser gets to the object, it should check:
a) The object is 'translatable':
a1) If the (full) object doesn't match the default, pull out (format may have changed, it is not safe anymore to blindly translate strings)
a2)) If it does (object is default config, thus format too), get into the object and translate the translatable strings in it
b) The object is not 'translatable': Recurse into the object's elements and find translatable strings in it.
On the other side, our form/element code, since the object is translatable as a whole will just handle it as a whole widget/subform. Our form element class, as it is specific to the object type, will 'know' how to handle it.
Comment #87
Gábor Hojtsy@JoseReyero: I think in your setup of two translatable elements nested code in Locale and localize.drupal.org can just skip if the translatable element is a container (not scalar) and move on. Only deal with translatable scalars. That would be the only change needed, no? Sounds like you explain some more complex logic, which I am not sure why is needed?
Comment #88
Wim LeersLooks good!
What exactly is missing here? When the
disabled
attribute is set, CKEditor enables its read-only state automatically. See http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-readOnly.:)
Glad to see that independently verified :D
I strongly agree. Imagine that you have a media filter with tokens for e.g. a video. If you switch to another text format, that video may no longer work.
Indeed.
Considering what I replied to two quotes above this one: the only possible solution is to tell the current user that (s)he does not have sufficient permissions to translate this piece of content.
Comment #89
Jose Reyero CreditAttribution: Jose Reyero commented@ Gábor Hojtsy,
Yes for localize.drupal.org, I don't think so for locale module.
I mean we'd need to check whether the container as a whole has changed before happily translating contained strings and a very good case is this 'text_with_format' container. Once the 'format' has been changed, I wouldn't risk translating the 'text' in it even if it's the default text.
Of course we could check also whether the translation's 'format' has been changed or not but that's a type-specific check (it assumes we are dealing with text+format), so not generic enough to implement in locale module.
This is my -yes, I know, twisted and stupid and that- but I think valid use case:
0. Original text_with_format, not translation yet:
1. Now I am the site admin playing around with texts and formats, and change:
(Oh, cool, the filtering works... I prefer the nice popup though....)
2. Site gets updated translations (automated, po import form somewhere, etc..), and the translation is:
* Since the source text is not changed, this is suppossed to be a safe automated translation
** The format is inherited from the source text. (full_html)
*** No one really looked at the translation because it *was* being filtered as plain text so why should they care?
Well, you can tell me translations will be somehow validated but, how do you validate translations for text+format?. How do you automate it for any contrib module with any hypothetical text format?
So I think translating texts with format is somehow risky, but at least you can rely on a known format (or skip translation if unknown). But what about translating texts with known format that may land on a system with a different format?
Comment #90
tstoecklerBefore I get into the heavy discussion:
Literally laughed out loud at that. That will be my response from now on when people tell me about XSS. It's a feature, not a bug! :-) (Sorry, now back on topic...)
So there's the
disabled
attribute which "greys out" the textarea and then there's thereadonly
attribute which still allows focus, and to highlight the text in the textarea (but not change it). I think in this case the latter is preferable, and it is in fact the behavior that CKEditor performs in itsreadOnly
state: You can still highlight the text. However, CKEditor does not react to setting thereadonly
attribute, but only thedisabled
attribute. That's why we have to set both attributes, whenreadonly
should in fact suffice (and it does - when not using CKEditor). So there are two options for this as far as I see it, from the CKEditor side:readonly
attribute have the same effect as thedisabled
attribute currently has. This shouldn't be too hard, looking at line 170 of core_editor.js.disabled
state which is distinct fromreadOnly
and completely greys out the textarea and does not allow focus. As this would be an API/behavorial change I assume the CKEditor folks will not be too excited about this.Let's discuss this in a separate issue or preferably in IRC, (because I'm not yet sure that it will end up as a d.o issue), but I wanted to justify why I put the
@todo
there.That said if you have suggestions both for the code and for the
@todo
comment, please enlighten me! :-)Regarding where to put translatable and how config translation and locale interact:
So what you propose is the following (please correct me if I'm wrong:)
translatable: true
and saves the entire "container" object into the language overridetranslatable: true
or not. If so (and if the source string matches a shipped string of course), it saves the string into the string translation system. (To be precise config_translation does this on behalf of locale, but that's an implementation detail.)This allows for the simple case of the first translatable element already being the final one in the tree and thus both config_translation and locale act on the same element. But it also allows for the more complex case where a container element is translatable as a whole but only some components of it are eligible for the string translation process.
If this is in fact the proposed solution I agree 100%. I think it rather elegantly solves this complex problem. BUT this will need extensive documentation as it will be impossible to get right otherwise for contrib maintainers. Perhaps we should add a whole config_translation
@defgroup
. Of course, that should be a follow-up.I think this would make sense.
The scenario given in #89 is both valid an problematic, however I don't see a way around it with the current system. I think the shortfall is that we are bending localize.drupal.org - which is at its heart a string management service - to work with aggregated data structures such as configuration. If we were building l.d.o from scratch with the D8 architecture in mind, we would allow to translate config entities in their entirety (which is totally possible, as the form depends only on the schema) and would export the language override YAML files directly. Then you could never enter
because you wouldn't be able to change the text format as a translator.
Also note that this problem exists already for views empty/footer/header texts, etc. which can be translated, but their format is changed independently currently. So I think we can safely make this a follow-up issue, even though this is security-related.
Comment #91
Wim Leers#90: RE CKEditor supporting
readonly
— I'm talking to them about this. But don't expect this to be supported overnight. In the mean time, we could support this ourselves by modifyingcore/modules/ckeditor/js/ckeditor.js
— which contains the CKEditor-specific attaching/detaching logic that the Text Editor module API requires.Comment #92
Gábor Hojtsy@tstoeckler:
Well the way Jose proposed, locale needs to be aware if the text format was changed from the install source and not load in the translation if it did. So locale needs to handle the whole object as well, but look up translation only for the text translatable value. For the original ==/!= comparison, it would look at the container, for the string translation lookup it would only look at the string. Does that make sense?
Comment #93
tstoecklerJust FTR, the CKEditor fun is now happening in #2275491: CKEditor does not support the "readonly" attribute.
Re #92, yes I think that makes sense. If we consider the container objects to be the ones that are semantically translated as whole objects then it is fine for locale to be aware of that. That doesn't seem like it is an implementation detail of the text_format type. However, I think the comparison logic needs to be a little bit more involved. a simple == on the container object is not sufficient, unless I am missing something. I think the following example code should do it:
?!
Comment #94
tstoecklerJust realized this was still assigned to me, sorry. I do plan to work on this next week, but that shouldn't stop anyone from beating me to it.
Comment #95
Gábor Hojtsy#1423244: #allowed_formats property for #type 'text_format' to filter available formats landed, this needs some updates.
Comment #96
tstoecklerHere's a first start. This is not 100% ready set, but I want to see what breaks. I will update the issue summary once this is green.
Comment #97
tstoecklerComment #99
tstoecklerHere we go. Will update the issue summary and provide some details on the patch later.
Comment #100
tstoecklerUpdated the issue summary.
One note on introducing
LocaleConfigSubscriber
: Even though I believe it is an improvement regardless of this issue I wouldn't have gone ahead and introduced it here if it wasn't necessary. However - as a result of the above discussion - the config saving and the string translation saving have a different traversing logic. And embedding both of those intoConfigTranslationFormBase
would have been terrible for readability.I re-read #93 and I am in fact incorrect (who knew! :-). We cannot ignore values if non-translatable properties change because then it would be impossible to re-translate formatted text when the source text format changes. We just need to document that it is the responsibility of the form element class to make sure that invalid/unsafe combinations of values cannot be submitted. In our case the
text_format
form element already ensures that for us. I will add documentation and tests for this, though.I'm very much reluctant to do this, but raising to major because of the security impact here. The scenario depicted by @José Reyero in #86 is already exploitable with the empty/header/footer areas from views. In fact the text that is entered in to the textarea is simply printed into the Config Translation UI so you can create an XSS injection there fairly easily, which makes #2144505: Views does not use the text format type for formatted text major as well.
If you're wondering about the change in LocaleConfigTranslationTest, it is - strictly speaking - unnecessary, but it failed at some point and the current assertion there is just impossible to debug, so I split it up into multiple ones.
Some notes from self-review:
I realized it's also https://localize.drupal.org now.
Stray "u" (ConfiguCrudEvent).
Oops, faulty merge.
Missing trailing comma and weird formatting.
Oops, sorry.
Unused.
A comma before the "so" would make the sentence less ambiguous.
Remove.
Comment #101
tstoecklerHere we go. This feels ready from my point of view, but I'd love to hear some thoughts on the architecture. The added test coverage showed that the #allowed_formats integration did not work in the above patch. Fixed that now (tests++).
Also noticed that I didn't provide an interdiff for the previous patch, sorry for that! Here they are (two, because I merged 8.x in between).
Comment #102
tstoecklerComment #103
tstoecklerRe-roll after #2277945: Typed configuration implements TypedDataInterface improperly
Comment #105
Jose Reyero CreditAttribution: Jose Reyero commentedHi, I was in the middle of a review but this is not applying anymore, since this one got committed #2277945: Typed configuration implements TypedDataInterface improperly
So if you don't mind I'll re-roll the patch for you, then add some comments after I post the update.
(It looks great to me so far)
Comment #106
tstoecklerHere we go. The
getSourceElement()
didn't conflict in the merge, because it is introduced here, but we need to useDataDefinitionInterface
there as well, of course.Edit: Crosspost. I beat you with the re-roll. :-)
Comment #107
Jose Reyero CreditAttribution: Jose Reyero commentedThe patch looks great to me, but since it's big and complex, I have some questions, suggestions, etc... None of this should be considered a blocking issue though.
1. Mostly curiosity, but:
I think ok, it is a more specific type that happens to be true, but just wondering, why do we need this?
Could we use ComplexDataInterface instead? (may open up some doors to future schema definitions by contrib modules)
2.
The long explanation here is great, but I think it should be somewhere else, where we document all options, especially the 'translatable' one, for config schemas (I don't remember where..)
3.
We may be making a way too broad assumption here, that is adding our TextField element for any other unknown translatable element. This may override better suited form elements set by other contrib modules for their data types.
I think we should add form elements only for the types we "know".
4.
Wondering whether we need so many parameters here. From $chema we could get
$config_data = $schema->getValue()
$base_config_data = $schema->getRoot()/getParent()->getValue()
Or maybe they are config objects, then forget about this but better remove the '_data' suffix which makes them confusing.
5.
Since it seems likely this hook goes through some change, could we encapsulate the whole thing with something like getFormElementFromDefinition(..) ? (that also may use some static caching in the future if we don't eventually fix the hook)
6.
Since this is a custom made class, could we move the logic into ElementBase so it could at some point produce really more complex elements?
I mean $build[element_key] = $form_element->getElementSubform(....);
The issue is both 'Source' and 'Translatable' methods being overridable but not the subform itself (source + translation) and since we have a base class now, it would be equally cheap, and maybe better encapsulation.
6b. Also I think we could either make them static methods, so we don't need to instantiate the class or optionally, pass the data when creating the form element object. Needing to instantiate it without data doesn't make too much sense I think.
• I don't know if there is any pattern for element building classes somewhere else...?
7.
The explanation is right but I'd say if such a long explanation is required, there's something wrong somewhere else...
Could we make it shorter, maybe move the text-format specifics to that class... ?
My main concern is people jumping over this too much text and missing the security implications.
8. Also wondering, what happens when there's a translation but it cannot be edited by current user (because of text format) and the the form is saved. Does existing translation get deleted?
(Sorry but I don't find where to manual test the feature with durrent d8)
Do we have any place in Drupal core btw where this is actually used?
9.
Have some issues with that but not for this patch so I'll follow up on that other case. I understand this patch only builds on what is implemented atm for the hook...
10.
About the new events and changes in LanguageConfigSubscriber, code looks good, but I'm afraid I'm not the one to review them atm, not up to date on config language / locale overrides... Will try in the next days though.
Still, I'd appreciate some better comments on
Yes, but how does it update them? What happens when?
(updated just to add some numbering)
Comment #108
tstoecklerHere we go. Replies with the same numbering as in #107.
1.
- In
SchemaCheckTrait
we check that each config object is anArrayElement
so the more specific check is safe and is in fact what is considered "valid" schema.- The suggestion to use
ComplexDataInterface
is awesome, but unfortunatelyArrayElement
does not implement that (#fail). Also we should updateSchemaCheckTrait
then as mentioned above. I will open a follow-up for that.- Thus left this as is.
2.
- I agree. :-)
- I don't really have an idea where to put the information if not there, however.
- In the long run I think it makes sense to have a dedicated @topic/landing-page for config translation where such things can be documented and which we could reference from
core.data_types.yml
- Barring any concrete ideas for improvement, I left this as is for now.
3.
- This was how it was previously. But I did change it as I tend to agree.
4.
- Yeah, those are not even config objects (despite the docs), they are config values, i.e. the result of
$config_object->get();
- I thus changed the names to
$source_config
and$translation_config
.- I also changed the order to be more consistent with
ElementInterface::getTranslationElement
and as I think it makes more sense that way. :-)5.
- Since that will change soon with #2145633: Use standard discovery facility in TypedConfigManager instead of one-off implementation anyway I left this as is for now. Is that OK?
6.
a. I'm not sure how that would work because we rely on the exact structure (including the
#parents
trick) in the submit handler. Thus, if would off-load that entirely into the form class, we would also need to let the form class handle the value submitting or something like that.b. Awesome idea! I changed
FormElementBase
to have the definition injected. We could also inject even more, but wasn't sure. Happy for more suggestions.7.
Again, I'm happy to incorporate concrete suggestions. I personally don't think leaving out the text format parts (i.e. the "for example" parts) would be a good idea, as that makes it hard to understand for people that don't already what we're talking about.
Example:
Is that understandable on its own, i.e. without an example? I would say no, but if you think it is, then - you're right - we can vastly shorten that entire section.
Didn't change anything as of yet.
8.
- No, the existing configuration is not overwritten. This working correctly depends on the form element returning the correct value in that case (by setting #value). I added a little bit of text to that effect to the (already overlong...) explanation in
ElementInterface::getTranslationElement()
.- We have tests for that behavior.
- Manually testing is also possible, but you will have to enable the
config_translation_test
module (with the patch applied) and then visit the Media > File system page, which should have a translation tab then.9.
Left that for #2145633: Use standard discovery facility in TypedConfigManager instead of one-off implementation to fix.
10.
Added a comment to
LocaleConfigSubscriber
.Comment #109
tstoecklerOops, missed one part of the interdiff (it's in the patch, though).
Comment #111
Jose Reyero CreditAttribution: Jose Reyero commentedTaking a look at patch #108
I think it has some minor issue -maybe also why tests are failing, I don't know, in config_translation.module
That $definition['type'] may not be set, better use the $type foreach key or fallback to it.
About #107, ok with almost everything, I think important issues have been addressed and about the rest, I don't have better ideas either.
Only about the access control part (previous 7th point):
- It should be really clear this may need access control
- Usability related, but user doesn't get any clue why elements are grayed out.
I'm thinking that FormElement maybe should implement the AccessibleInterface and that way:
a) It is stated clearly that it needs access control and it is done in a single point.
b) The access check can be done from FormElementBase, user warned, if it is an access issue
c) We don't need to save forms/elements that may not have any editable element at all (Because right now if I got it right, the parent form doesn't know whether the element is actually editable, does it?)
$element_class->access('translate') ?
Comment #112
Jose Reyero CreditAttribution: Jose Reyero commentedI guess this may need an update after we've got a brand new hook_config_schema_info_alter(). That should simplify this one a little bit.
See #2145633: Use standard discovery facility in TypedConfigManager instead of one-off implementation
@tstoeckler, are you still working on this one? I may give it a try if not.
Comment #113
tstoeckler@Jose Reyero: Feel free to tackle this one. I do intend to come back to this eventually but I can't promise anything right now.
Comment #114
robertdbailey CreditAttribution: robertdbailey commentedRerolled patch from #108
Comment #115
robertdbailey CreditAttribution: robertdbailey commentedSome comments on this current patch:
This comment block was removed, but the beginning of the comment block (/**) remains. This needs to be fixed in an updated patch.
Text should read: "The base key to which the schema and configuration values belong." (not important)
Lines should be removed, as the equivalent moduleHandler code was removed in HEAD.
Comment #117
tstoecklerWhile @robertdbailey is happily ploughing away at this I opened #2346129: Introduce a TraversableTypedDataInterface and use that for typehinting instead of ArrayElement to address #107.1
I will try to move as much as possible into separate issues so that we can maybe finally get this sucker finished this week.
Comment #118
tstoecklerOpened #2346195: Document configuration schema with a dedicated @config_schema_api @defgroup for #107.2
No patch there yet, though.
Comment #119
robertdbailey CreditAttribution: robertdbailey commentedMade the fixes from the comments in #115, and fixed an issue with #114 causing the fields to disappear.
The points of conversation in #107 and #108 have not been addressed here.
Please review.
Comment #120
tstoecklerComment #121
kfritscheManually tested this with the steps provided above.
Fixed an issue where Rob and me noticed that the source and translation is flipped, when editing an existing translation. Also a minor spelling mistake.
Works great now also with CKEditor.
Seems fine for me.
Comment #124
kfritscheFixed the two failing tests.
Comment #125
kfritscheComment #126
Jose Reyero CreditAttribution: Jose Reyero commentedI think this is looking good, but still, I am missing really reusable/replaceable form elements.
This patch builds on the previous code, but aiming to provide reusable form elements:
- All element building logic moved from ConfigTranslationFormBase to ElementInterface classes.
- New form element type, ListElement that will handle mappings and sequences like any other element.
The difference is the whole subform, included nested elements is handled by element classes now, which means we could code any form element structure into the Element classes. No ArrayElement nor other element-specific logic into the ConfigTranslationFormBase class.
Posting mostly for feedback, it may need some more cleanup yet (like dropping most of ElementInterface)
Comment #127
broonFollowed the instructions closely and got the exact output as expected for steps 7, 13, and 15.
Patch applied without issues or error messages.
Comment #128
webflo CreditAttribution: webflo commentedWe (jose, tobias, and me) did another in depth review during DrupalCon code sprint. We decided to move the setConfig method to the ListElement. This decouples the submit action for form elements from ConfigTranslationFormBase.
Comment #129
tstoecklerChecked that 'n/a' is in fact widely used in core, in contrast to 'N/A'. So ++
one dimensional -> one-dimensional
recursive -> nested
I'm pretty sure this is not correct. The whole idea with the #parents trick is that 'translation' does not show up at that place. Also the nesting with the '.' in the keys should be gone now.
It would be awesome if the traversing logic could be moved into ListElement. That way we would have true parity between the form generation and the value submission.
Comment #130
tstoecklerHere we go.
This implements what I meant in #129. I think it neatly puts the traversing into ListElement while the other elements no longer care about that and receive the same $config_values in setConfig() as in get(Translation|Source)Element().
Looked through the entire patch again and also tried it out manually. I feel pretty confident that this is ready.
Comment #131
webflo CreditAttribution: webflo commentedJust two nitpicks.
DataDefinitionInteface is not used.
These methods are not part of the Interface and should be protected.
Comment #132
tstoecklerHere you go.
Comment #135
tstoecklerThis should be green.
I also tested this with CKEditor module again and it works fine.
Comment #136
Jose Reyero CreditAttribution: Jose Reyero commentedThis is looking great, I love specially how the Element classes can get to process their own submission, though I think there are still a few things we could improve, and still save some more code.
1. Could we get rid completely of ConfigTranslationFormBase::buildConfigForm() ? These few lines of code need a lot of overhead for passing and documenting parameters. Also there are some parameters not used there, like $open and $base_key.
2. We are using a constructor for ElementInterface classes that is not part of the interface. We should replace it by a create() method and make it part of the interface. That method could take some more parameter, like base_key, that is used both for form building and submission, maybe 'parents' too.
3. There is some duplication in ListElement and FormElementBase. I'd rather have one more base class that could be extended by both.
4. I have some doubts about the form submission. What happens when a plugin that was previously there is disabled now? (I think the config translation may stay?). Still I don't know what is supposed to happen with configuration itself, will 'disabled configuration' stay there in case we enable it again?
5. For this last part (instead of setConfig) I'd rather have a callback that returns a plain config values array, and let ConfigTranslationFormBase handle the full LanguageConfigOverride set/clear methods. That way we'd save passing the config objects to the form element, a lot of intermediate set/clear, and still it would be able to do some processing on submitted form values.
Anyway, I think the only important points in this list are 1. (not used parameters) and 2., and maybe assess 4., the others are just some ideas for improvement/simplification.
Comment #137
tstoecklerWill fix 1. and 2.
3. That's really just the constructor that is being duplicated. I don't see what a sensible base class for that would be and I also don't think we gain much with adding another level of inheritance just to save ~20 LOC (incl. docs). The new class would certainly be an increase in pure LOC.
4. So are you talking about form element classes that were provided by a module and then the module gets uninstalled? Since we now moved the form submission logic into the form element classes the config will just stay around until a form element class for that element exists again. So there's nothing to do here?! This also validated my concern above, as this use-case is still a problem in #128.
5. I will look into this. I assume you mean that we can save passing the
$base_key
intoFormElementBase::setConfig()
? That would indeed be nice, although I'm not sure this will actually work as - per 4. - we now actually have a reason why the form submission *must* be handled by the form element classes. So we would still need to track the difference between a form element class explitly deleting the value and no value being there due to a missing form element class. I'm not sure how to do that off-hand.Comment #138
Jose Reyero CreditAttribution: Jose Reyero commented@tstoeckler,
3. Ok
4. Yes, that was my concern, now I think that should be handled by a different layer, maybe LocaleConfigSubscriber -if it ever needs to be done-, so fine with that too.
5. Well, I was thinking more about proper encapsulation, more consistent parameters. While in the form building we are passing config arrays, then we use config objects for form submission, and I think they're almost the same (config objects contain the config arrays), so it is mostly about reducing the different types of objects form elements need to handle (let ConfigTranslationFormBase handle config objects, and form elements handle array values). Not a major concern though.
But if you are re-rolling the patch, just one more request. Could we use some better (translation related) name instead of getElements(), like getTranslationSubform() or similar?.
Just in case you are wondering, I'm thinking of some contrib module that would build on this to actually autogenerate the configuration forms, it looks to me that it could very well build on these form elements, adding some more interface while still implementing the current ElementInterface, i...
Comment #139
tstoecklerHere we go. Did not fix 5. due to
I wouldn't be offended if someone were to RTBC this, just sayin' ;-)
Comment #141
tstoecklerRe-roll after #2340667: Protect Drupal\Core\Language\Language::id, and use getId().
Because I already updated the deprecated usages where we were changing code nothing else should fail. So much for things being out of scope...
Comment #143
tstoecklerI hereby request to be slapped in the face unexpectedly at the next DrupalCon.
Comment #149
Jose Reyero CreditAttribution: Jose Reyero commentedThis looks great to me, all important issues have been addressed. Also, tested manually, everything seems to work.
Just minor code/documentation issues before this can be RTBC:
1. I guess we should be using the brand new TraversableTypedDataInterface instead of ArrayElement in a few places #2346129: Introduce a TraversableTypedDataInterface and use that for typehinting instead of ArrayElement
2. Method Documentation in ListElement, should be ListElement instead of FormElementBase:
3. I don't think this comment applies here for ListElement
I mean this 'source and translation appear next to each other etc...' is ok for FormElementBase, not for ListElements...
Comment #150
Gábor HojtsyJust noting that #2351991: Add a config entity for a configurable, topic-based help system introduces a format supporting text field for help topics, which will need this missing integration as well.
Also retitling and reclassifying as bug, which it is.
Comment #151
tstoecklerConflicted with #2028109: Convert hook_stream_wrappers() to tagged services. but only because both issues add stuff to
locale.services.yml
so very easy merge.Fixes #149, thanks for catching that!
Could this be the last one?
Comment #152
tstoecklerUpdated the issue summary. Was mostly correct already but not quite up to date with the latest patch.
Comment #153
Jose Reyero CreditAttribution: Jose Reyero commentedI think this is ready, nice work!
Comment #155
YesCT CreditAttribution: YesCT commentedrerolled for #2341341: Change public 'name' property access on languages to getName() and add back setName()
Comment #157
YesCT CreditAttribution: YesCT commentedsorry, missed a conflict.
Comment #158
tstoecklerThanks for the reroll, looks perfect.
In verifying that everything looks as it should I found some minor docs problems, which the attached patch fixes:
"which used for for Mapping" -> "which is used for Mapping"
Also the thing is called "Sequence" not "List" (that was my bad)
This was moved to \Drupal\config_translation\Element\FormElement::getTranslationElement()
This should be reverted.
This comment could use some love and I think should be placed a bit lower where the string translation is actually updated.
Marking RTBC. (The patch was RTBC before + I've verified the reroll + This patch is only doc fixes)
Comment #159
YesCT CreditAttribution: YesCT commentedthat interdiff looks good.
Comment #160
tstoecklerComment #161
tstoecklerAdded some draft change notices.
Comment #162
alexpottI reviewed this in irc with @tstoeckler - general points are:
These events should be specific to language overrides - since they are an implementation detail of the language override. Different overrides may choose to fire events or not.
This should get the langcode from the storage collection name. In order to make this simple a couple of protected methods on the override factory should be turned into a trait a used here.
I'm concerned that we don't have test coverage of all the code pathways through the logic contained in this event. Do we have coverage of a delete where the config is shipped and where it is not. And similar coverage for an update. And there are a number of conditions in saveStrings which I can't determine if we have coverage (without doing a lot of work).
Comment #164
tstoecklerComment #165
tstoecklerPlease take a look at this patch, it should address the remaining concerns. Here's the commit log from my local branch:
Apart from the above I found straight up bug in
LocaleConfigSubscriber
where we were not disabling the override state before fetching the source config. That did not surface yet, becauseConfigTranslationFormBase
explicitly disables that itself.In writing the tests I furthermore fixed a few more things which were lacking and which forced me to refactor
LocaleConfigSubscriber
a fair bit:LocaleConfigManager::saveTranslationData()
. In that case the strings are the community translations and thus should not be marked as customized.LocaleConfigManager::deleteTranslationData()
deletes language overrides. Again in this case, the logic we do otherwise - we save a customized string with the value of the source, so that it doesn't get overridden - does not apply. In this case the string should actually be deleted.Because the traversing logic is the same in each case but there are 4 different cases what to do at the end of the traversion the code became a little bit more complex, but I split each logical unit into a separate method and added documentation to each detail, so I hope it makes the code fairly readable.
The same goes for the test, which should serve as a good reference for the intended behavior. There were different combinations of setting up the system and performing tasks that were identical but put together in different ways so that I split them in a bunch of methods to make the actual test methods really lean.
As always, I am open to suggestions on pretty much everything.
This is not a re-write in terms of the actual code that is being run but it is a little bit of an architectural change so I would like to have a review both from the Config system side as well as the Interface Translation side. I.e. I would love if @alexpott and @Gábor Hojtsy or @Sutharsan could take a look at this, specifically
LocaleConfigSubscriber
andLocaleConfigSubscriberTest
.Comment #166
tstoecklerAlso had to merge 8.0.x.
Comment #168
tstoecklerI incorrectly merged #2349651: Default contact form does not send email as email recipient is not set during the installation..
Comment #169
YesCT CreditAttribution: YesCT commentedI'm not the architectural review being looked for, that is still needed.
Read up to ListElement. Just a first read to get oriented to it. Fixed a few little things as I read. (Hope they are only fixes and didn't break anything.)
Note to myself, check when the permissions are checked. At the beginning when building the form, *and* when submitting it?
if they are traversable, they are translatable?
Comment #170
tstoecklerRe 1. The flow is that during form building access is checked so that only the correct text formats are given as options. Then during submit Form API checks whether the received value is one of the values specified in #options.
Re 2. Yes, that comment is no longer accurate, I meant to remove/update that. Thanks!
Comment #171
Jose Reyero CreditAttribution: Jose Reyero commentedPartly review, just some mixed notes:
- LanguageConfigCollectionNameTrait. I don't think we need a trait to introduce two api methods. Couldn't they be static methods in any of the classes or api methods anywhere else? Though maybe I'm wrong because it looks like current Drupal pattern may be 'add traits everywhere for everything'... :-/ But really you don't need a trait to provide reusable functions that don't access any of the class properties or internals.
- LocaleConfigManager::isUpdating ? I don't like too much this kind of 'internal state' of LocaleConfigManager and certainly it doesn't help to much for code readability or to understand what is going on.
Also we end up with this kind of methods, btw, missing documentation, but my question: is this an event callback or what?
If this variable is anywhere I think it should be in the event itself.
- LanguageConfigOverride::getLangcode()... When I see the depths we need to get into to get the language for a 'language configuration override' I'm wondering whether 'language' shouldn't be a straight parameter/property of this class.
Overall, this is already a really complex patch and when I read the issue summary it looks like we are trying to fix everything configuration & language related in this one. Could we consider letting some of the events/subscribers/etc.. -mainly the ones that according to @alexpott #162 are not clear enough yet- out of this patch and for another one?
Comment #172
tstoecklerThis was specifically requested by @alexpott, so I hope we can defer that discussion for now. If you feel strongly maybe we can discuss that in a follow-up.
I am personally totally in the add traits everywhere for everything camp, just for the record. :-)
Yes, I don't very much like that either. But I couldn't think of a different way to solve this problem. Basically we have to differentiate between locale saving language overrides and everyone else saving language overrides. But I have added documentation to why that is needed, so please be specific on the parts that are unclear.
Again, can you specify which methods are missing documentation? I thought I went through all of them again before posting the patch but maybe I missed some. And I don't understand your question, sorry.
Again, I don't really know what you mean, sorry.
That's how it was in the previous iteration. But @alexpott (IMO correctly) pointed out a problem with that: The langcode needs to be persisted to the storage, so we have it in two places then (even though - granted - one of them is bit "hidden") and it is not at all clear what happens when one of them changes. They certainly aren't synched anywhere explicitly so the system would be in an inconsistent state. I personally think that's more of an edge case so I could live with re-introducing the langcode property, but I don't see any reason to: Now that we have the
getLangcode()
method the internal storage logic is hidden away from consumers and whether it's a property or not should not matter.As I tried to explain above and in the issue summary there is a hard reason why all of this is included in the patch. It is unfortunate that this patch has grown over time the way it did but at this point I don't think we gain much by splitting the locale stuff out as we would have to postpone this issue on that. And at this point I would just like to get this one in, no matter what. Also, 100KB patches are not that rare these days. I think that's more a criticism of our issue queue process in general than a defense of this patch but it's a fact nevertheless.
The reason why the locale stuff cannot be split out and handled separately is the following: Currently
ConfigTranslationFormBase
marks the strings as customized when you save the form. As part of this issue we are changing the traversing logic ofConfigTranslationFormBase
to support complex elements containing multiple sub-elements. Because locale - by nature - needs to retain the old traversing logic, however, keeping it inside ofConfigTranslationFormBase
would require traversing the config schema in two distinct ways, one of which would have nothing at all to do with config translation. That's why it was split off in the first place.As I said, I would do pretty much everything at this point to get this patch in, so if people feel
LocaleConfigSubscriber
is too controversial to introduce here, I can also put all that code back intoConfigTranslationFormBase
and open a follow-up for the event subcriber. But - to be blunt - I would rather someone just mark this one RTBC.Comment #173
Jose Reyero CreditAttribution: Jose Reyero commented@tstoecker,
This is where I mean there's missing documentation, for the $callbable parameter.
And the function name itself is confusing, because it resembles an event callback.
About LocaleConfigManager::isUpdating what I mean is this property and the associated method 'isUpdatingConfigTranslations()' could make more sense in the event itself (LanguageConfigOverrideCrudEvent), or maybe if it is a really different kind of event, we can add another different event class. Otherwise we are breaking encapsulation because we need some other variable to determine what should be the event's behavior.
For the rest, ok, you've provided valid reasons for all my objections. Nothing wrong with the patch.
Thanks for the explanation, I think I knew... but I had forgotten... :D
Also I'd like to get this in asap, so I was just suggesting to split some parts in case it was helping getting the big part in faster... Now I see it all makes sense together.
Comment #174
tstoecklerOk, so this fixed the missing docs and renames the method.
I agree that it is sub-optimal that we require
LocaleConfigSubscriber
to check into internal state ofLocaleConfigManager
. I don't see any other way to solve that problem, however. Because we need to trigger the state change inLocaleConfigManager::saveTranslations()
and::deleteTranslations()
maintaining the state inLocaleConfigSubscriber
would meanLocaleConfigManager
would have to call intoLocaleConfigSubscriber
at the appropriate times. This, however, is not possible becauseLocaleConfigSubscriber
depends onLocaleConfigManager
so that would mean a circular dependency.We are already adding a new event class (
LanguageConfigOverrideCrudEvent
), so I don't know what you mean which this proposal.Totally agreed, but I really don't see any way around it, to be honest.
Comment #175
Jose Reyero CreditAttribution: Jose Reyero commentedAbout the onUpdate() method rename it looks ok to me.
About the isUpdating, I've been looking into where to put it so it can be checked from the event, attached diff is just an idea, not really tested. But the only place I can think of without reworking half of the interfaces is the LanguageConfigOverride object.
You tell me whether it makes sense, it may need still some polishing if so...
Comment #176
jhodgdonThe previous comment was about documentation so I started looking at this patch and got hung up on the first bit:
That first line is confusing to me. It's not just "associated" with a format, it "has" a format, and the mapping here has both the string and the format in it?
And the rest of the documentation seems overly verbose for a schema file. Starting any documentation with "Even though..." seems wrong, and saying "we conceive"... Can't we just skip the philosophy and document what it is and how it's stored?
And:
Can't these be combined in some way? Again, this seems like too much information.
I also glanced through the rest of the patch... it seems like there are several changes to base form elements and element interfaces that may not be captured in the change records? You've added, removed, and changed methods on several interfaces. Interfaces are implied contracts... I don't think the new create() method on FormElementBase(), for instance, has been covered in a change notice? Even changing the type of a parameter or return value on an interface, I think, needs a change notice at this stage?
Comment #177
tstoecklerRe #175: The problem I have with that approach is that the distinction of "update mode" is really specific to locale module. language module should not know about it. It should just dispatch the event and be done with it.
Re #176:
1. I had a go at making the documentation in
core.data_types.schema.yml
more concise. I kept the security-relevant note as that is important information and I also did not combine the latter two blocks of documentation. The point is that we havetranslatable: true
has slightly different semantics depending on where it is declared and, thus, I feel it's important to elaborate not only where we're puttingtranslatable: true
but also where we're omitting it as this is sort of intricate.2. Updated the corresponding change notice to mention
ElementInterface::create()
, that was in fact missing. As far as I know, everything else is covered by change notices, please correct me if you find anything.Comment #178
jhodgdonThanks - I like the docs in the schema file much better.
Regarding change notices, here are some changes that I don't see in the 3 change notices for this issue:
- TypedConfigManagerInterface - @return type changed for some method (can't tell what it is in the patch)
- ConfigTranslationFormBase - several changes... does that need a change notice or is this considered an internal-use base class for within Config Translation module only? Probably the latter, so OK.
I also took the time to read through all of the API docs in the patch, except for in test classes, and they really look great! I had a few nitpicks:
a) ConfigTranslationFormBase::buildConfigForm() docs:
Should be "Creates "... actually ... that's just wrong. I don't think it creates a form element builder? Doesn't it just build the form?
b) ElementInterface::getTranslationBuild()
Should be type string|null I think?
c) On that same method, the first-line doc says:
But ... what it returns is an array. I am not familiar with the term "translation build" but didn't understand it... Maybe it should say something like "Builds render arrays for the source and translation form elements"?
d) Same interface:
The implementation of this method on the base class doesn't seem to have a return value. Is this right?
e) Same setConfig() method... This comment in the base implementation really concerns me because this behavior is not documented in the interface that I can see:
f) LanguageConfigCollectionNameTrait - Several places in the doc the word "langcode" is used. This is not a word. Please use "language code" when writing documentation.
Comment #179
tstoecklerThanks for your attention to detail @jhodgdon, this should fix everything mentioned. Some comments:
That's not actually an API change, that's just making the docs conform with what's already the case to benefit from type-hinting. Of course if there were other implementations of that interface which were strictly abiding by the interface that would be an API change for them, but IMHO that's going a bit far.
Yes, I agree.
No it actually creates an
ElementInterface
instance which then gets called to actually build the forum. Changed to "Creates a ...".Comment #180
jhodgdonDocs look much better, thanks! I haven't reviewed or tested the code in the patch.
Comment #181
Jose Reyero CreditAttribution: Jose Reyero commented@tstoeckler #177
Yes, you are right, I don't have a better idea either.
Comment #182
dawehnerDisclaimer: by knowledge of config translation/config overrides/config is limited, but we should move this issue forward, so here is a review.
Good idea in general.
in an ideal world I would swap that if statement, because the second if, would imply the, from my point of view, more important condition.
Well, I am glad that not many people or noone woudl have to write a custom translation form element, but yeah having to deal with #parent for yourself is worse in terms of DX
I don't see a point why this method was renamed, but well, no big deal here.
Well, we already passed in a different variable, so renaming is less problematic.
Is there a reason $description could not be part of the config schema directly? Maybe worth to investigate in a follow up.
Is there a simple reason why this is not just passed to the constructor?
Are you sure we should not remove those elements from $config_translation ? Maybe document why it should not be done.
It is super confusing that we support both "title" and "label", but well it seems to be like that.
It is great that there is a dedicated test coverage for that!
Just a note: #2264049: Create an Events topic
Nitpick: You could just put translated => TRUe in the $conditions directly.
There is no point in storing the variable first.
I tried to find a testcase for the following scenario, but could not:
* You start with a translated string
* You override that translated string locally
* You update translations from remote, ensure that the local override wins.
It is a bit odd, that both have the same text.
Comment #183
tstoeckler@dawehner: Thanks for the review!!!
1. -
2. Agreed. Fixed.
3. Yes, see also the discussions above. Until #126 the
#parents
magic was handled byConfigTranslationFormBase
so that form elements did not have to deal with it. That also made things less flexible, though, so by making the form elements fully self-contained (i.e. they output the form and submit the data now) we had to move that logic to the form elements. Because it is implemented in the base class I don't think it matters much practically.4. Since it now also returns the render array for the source element,
getFormElement()
was no longer correct. If that were to hold this up, though, in any way, we could surely rename it back or introduce a BC-helper.5. Yes.
6. I like that idea a lot, so opened #2376693: Consider adding descriptions to config schema.
7. This was requested explicitly in #136 to make the instantiation transparent (as in part of the interface). I think this could be seen as consistent with e.g.
DataDefinition::create()
.8. Wow, you're right, that that could happen in theory. It does not actually happen now because we do not have any translation elements that use checkboxes or radios (not that that would make much sense, either...). The downside is that since we now can generate a form element class for any mapping (or sequence) we do quite a lot of extra processing because of removing that check. I.e. currently we only traverse into the elements in
setConfig()
which have an actual config value. Removing the check means traversing theentire
config schema, including all elements which do not have a translatable "tip"/"leaf" element. This is consistent with what we do during the form building anyway, so removed that check. Thanks for your attention to detail!9. Yes, that is brought over from the current code (at least now it's in its own method!). I don't like it much either, but Views translation UI is horrible enough already, so we can't get away with removing the more meaningful labels and I also don't have an idea how to make that more generic off-hand.
10. -
11. Thanks, subscribed.
12. No, because
$conditions
is used below. (See your 13.)13. Fixed, thanks!
14. This is covered in
testUpdateTranslation()
. Note that we don't actually test what happens when you update translations (that would be fairly complex), we just assert that the string is marked customized. This is also a little bit more unit-test-y because LocaleConfigSubscriber simply marks the string as customized, it does not deal with the import directly, either. Needless to say, feel free to suggest improvements to the test.15. Fixed, thanks!
Comment #184
Gábor HojtsyAs per https://groups.drupal.org/node/448068 the impact needs to be bigger than the disruption (API changes incurred) based on this being a major bug. While the patch is huge, the changes are limited to mostly config_translation and then some in language and locale modules.
Addresses @alexpott's concern since it was last RTBC. It has also been reviewed to death by a wide array of people since then and co-worked on by several area experts.
Assigning to @alexpott because of the topic of the issue and his earlier concerns.
Comment #186
tstoecklerDon't what failed to apply here, but Git was able to resolve this automatically.
Back to RTBC per #184.
Comment #187
alexpottThis issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Thanks for all the change records and addressing all my feedback from #162. Committed 1a8858a and pushed to 8.0.x. Thanks!
Out-of-scope changes are bad :) ... fixed this on commit.
Comment #189
Gábor HojtsyFolks, I cannot thank you enough for bringing this issue to a reality. I think this is a huge milestone to the usability of configuration translation and a great testament to the team spirit in the multilingual team. Thanks all for the reviews, testing and feedback all! Let's all pour a $favoriteBeverage and celebrate :) Well deserved.
Comment #190
Gábor HojtsyOnwards to #2144505: Views does not use the text format type for formatted text :)