Problem/Motivation
EntityType::getLowercaseLabel()
fetches the translated label and then runs a strtolower()
on that.
The usage of EntityType::getLowercaseLabel()
is for using entity type labels in a sentence, for example:
This is a block!
can be generated by
t('This is a @label!', ['@label' => $entity_type->getLowercaseLabel()]);
This breaks translation in some languages, however. In German, for example, nouns are always uppercase, so for the sentence to be correct, it must be
Das ist ein Block!
not
Das ist ein block!
It is not possible to generate the first string with the current API in German.
Proposed resolution
We can go with
(A) use new entity type labels and/or
(B) more keys in the $args
parameter of the t() function to allow the translator to decide.
(C) add context to the t() function to allow the translator to decide.
Remaining tasks
Decide which resolution to take.
User interface changes
No new functions or buttons, but more correct translations.
API changes
Deprecate getLowercaseLabel()
.
Comment | File | Size | Author |
---|---|---|---|
#43 | 2507235-43.drupal.EntityTypegetLowercaseLabel-breaks-translation.patch | 20.41 KB | Gogowitsch |
#43 | interdiff.2507235.40-43.txt | 1.68 KB | Gogowitsch |
#40 | interdiff.2507235.38-40.txt | 1.66 KB | Gogowitsch |
#38 | interdiff.2507235.36-38.txt | 556 bytes | Gogowitsch |
#36 | interdiff.2507235.34-36.txt | 2.54 KB | Gogowitsch |
Comments
Comment #1
ArlaIn order to have both cases ("Block" and "block") translatable, both strings have to be defined in code. The capitalization rules are not specific to particular entity types, so it would have to be defined by all entity types. Like in #2604902: Allow entity types to override EntityType::getLowercaseLabel(), an additional lowercase_label in the @ContentEntityType/@ConfigEntityType annotation would do it, but in contrast to that issue, the key would not be optional. A required annotation key would break APIs so I'm switching to 9.x.
Comment #2
tstoecklerI think that's a bit premature, to be honest. On German sites, this is a pretty big nuisance, because it makes Drupal look sloppy and unprofessional. The tabs added by the Configuration Translation module, for instance, are
in German, which looks like it is a typo (it should be Inhaltstyp übersetzen). Even more confusing, when a user tries to fix the apparently broken string on the user interface translation page, she will have no luck finding the string "inhaltstyp" there.
So the problem in German is comparable to if the string would erroneously be "Tranlsate content type" in English (except that that would be a much easier fix).
Also, I don't think we need any API break for this. As proposed in #2604902: Allow entity types to override EntityType::getLowercaseLabel(), I think a
lowercase_label
in the entity type annotation would allow fixing this, but we do not have to make it required. We can simply use it if it is there and fall back to the current behavior if not.Comment #3
ArlaOh, the "inhaltstyp übersetzen" example indicates a new aspect of the problem. Or actually two new aspects.
t('Translate @entity_type', ['@entity_type' => $entity_type->getLowercaseLabel()])
, the capitalization decision is dependent on the word order which of course is very language-dependent.I'm really not sure how to deal with that.
Comment #4
tstoecklerOh right! Nice find. I recently saw that string and blindly assumed it was this bug. Thanks! And yes, the order makes it even more complex. :-/
Comment #5
BerdirDuplicate of #2600806: Remove all invalid lowercase transformations from translatable strings.
I honestly have no idea how to solve this as already mentioned there, what @hass is doing there is obviously not correct.
IMHO, the easiest fix for config_translation would be to just stop using the type in there. It's usually also just "Edit" and "Delete" etc. local tasks, and not "Edit content".
Comment #6
BerdirIdea: What about introducing a new placeholder type that can upper/lowercase automatically based on the language? Something like *label. Then we can lowercase based on a flag on the language?
Comment #7
ArlaI though of a placeholder prefix as well. I'm really not sure about the consequences, but I suspect that there's a large and unpredictable number of similar parameters to placeholder substitution, like for instance noun cases and plural (in languages where these exist and are not simple affixes).
Also, this solution kind of conflicts with the discussion in #2604902: Allow entity types to override EntityType::getLowercaseLabel(). If TranslationInterface::translateString() takes over the responsibility to determine capitalization from the placeholder type, how do we make sure it doesn't just lowercase "RDF Mapping" and "JW Player" to "rdf mapping" and "jw player"?
Edit: These problems are not specific to Drupal, but i18n in general. Aren't there books written on the subject, or at least some pattern to adhere to? :)
Comment #8
bojanz CreditAttribution: bojanz at Centarro commentedWe have two options
a) Convert labels to lowercase, and then uppercase them when needed
b) Introduce an optional lowercase_label key. If missing, the current logic is used.
It's too late for a). b) can be done in a backwards compatible way.
Comment #9
ArlaTook the patch from #2604902: Allow entity types to override EntityType::getLowercaseLabel() and added the lowercase_label key to all non-test entity types. Also adding a context to all lowercase_label @Translation objects.
Comment #10
tstoecklerPatch looks good to me. I didn't really play around with it, so I can't really RTBC. Also the first item needs feedback from someone who knows POTX.
Some comments:
This makes a lot of sense, nice one!
I'm not very familiar (at all) with POTX, will it cause any problem that POTX will not be able to find the context? I.e. maybe we just have to add the context to all lowercase labels manually?
Can we add a comment (and a @see?) to the RdfMapping class docs pointing to
EntityType::__construct()
.In theory I think the generic "Entity type lowercase label" should be sufficient context here, but I can see that it is done here for consistency, which makes sense.
In the same light the uppercase label should really just have "Entity type label" as context, but it's out of scope to change this here (and in 8.x in general). For D9 we could do the same trick with the entity type labels itself (i.e. always add a "Entity type label" context by default) so we could remove this from the annotation here completely.
So nothing actionable, just thinking out loud.
I think the
$entity_type =
can be dropped from these lines.Comment #11
Gábor HojtsyIt would be nice to figure out how will this work if/once #1850080: Entity type labels lack plurality, cannot generate UI text based on label if plural is needed is added too. You don't need to resolve it here, just saying that is also in the works.
There is no way to find this context with static code analysis, so no way for localize.drupal.org to find it. Just include it in the annotation.
Comment #12
BerdirLooks like #1850080: Entity type labels lack plurality, cannot generate UI text based on label if plural is needed went with something that does not complicate this, as they only use lowercase labels.
But yes, Gabor is right. The automatic lowercase thing does *not* help. We need to manually, explicitly provide lowercase labels for all core entity types.
We do still need a runtime fallback in getLowerCaseLabel() I think. And I still think it might be better to use the approach above, so it *can* be translated, at least after it was looked at, instead of not being able to do so at all.
Comment #14
Gábor HojtsyRemoving from sprint so it reflects what is currently being worked on.
Unfortunately.
Comment #17
ckaotikThis is an issue that is spread all across core and also seeps into contrib modules that use core implementations as a guideline. The sooner we can get this fixed, the better.
The issue has been stagnant for over a year, what are the next steps to get this going?
Comment #18
BerdirCertainly needs a reroll and then trying to address the feedback in the last few comments.
Comment #19
tstoecklerIs this not now handled by the
label_singular
annotation?Comment #20
ckaotikNo, this is a different problem. A single node of type "Article" is referred to as "article" in English texts, but the same node in German (of type "Artikel") needs to be referred to as "Artikel" (because in German, nouns are always capitalized).
Having a separate lowercase label would allow the site builder/translator to capitalize the label as appropriate, though "lowercase label" might be confusing naming here. Think of it as an "in a full sentence, this is what we call this thing" label.
Arla also described another aspect in #3, where capitalization additionally depends on word order (e.g. sentences starting with a capital letter). But I suppose that is worth a separate issue.
Comment #21
tstoecklerWell that pretty much is the singular label, no?
Can you give an example where
label_singular !== lowercase_label
?Comment #22
ckaotikWell, if you put it this way ... It does make sense. (Unless I'm completely missing something here.) Seems this was introduced a year ago, if the change record is any indication.
That means we'd probably have to supply these new labels to all core entity types still (after a quick search, I could only find them specified for
node
,comment
,comment_type
,rdf_mapping
andcontent_moderation_state
) and try to weed out all places where$entity_type->getLowercaseLabel()
,strtolower()
orUnicode::strtolower()
are incorrectly used instead ofgetSingularLabel()
etc.Comment #23
Gábor HojtsyYay, thanks sounds like a good direction :)
Comment #27
ckaotikI've finally gotten around to looking into some of the cases mentioned in #22:
As for the
lowercase_label
annotation and its friends - they have already been added in the meantime.Most of the changes are straightforward, however there are some things that are not as easily solved and need some more insights.
\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator
lowercases@field_name
, but seems to not be used in Core.\Drupal\config_translation\Controller\ConfigTranslationFieldListBuilder::getFilterLabels
Is problematic, because a) we cannot get
$entity_type->bundle_label
(which is likely always capitalized and thus undesirable in English), and b) if it's not set,\Drupal\Core\Entity\EntityType::getBundleLabel()
returns getLabel() where we'd like getSingularLabel()getType()
\Drupal\config_translation\Plugin\Menu\ContextualLink\ConfigTranslationContextualLink::getTitle
and\Drupal\config_translation\Plugin\Menu\LocalTask\ConfigTranslationLocalTask::getTitle
,also within
ConfigTranslationUiTest
.Is problematic, because translation definitions only have one label (no singular/plural forms) - and it's used both for page/row titles (
/admin/config/regional/config-translation
) where capitalization is wanted, but also within phrases (e.g. "Translate x" tabs) where lowercasing is not wanted.I would suggest continuing this separately in #2275835: Capitalisation of translation tab forced to lowercase.
\Drupal\language\Form\NegotiationConfigureForm::configureFormTable
lowercases the negotiation message nameComment #29
Gogowitsch CreditAttribution: Gogowitsch as a volunteer and at QuoData GmbH Quality & Statistics for Merck KGaA commentedHere in this issue, there seems to be consensus that the entity type has to have even more keys. I propose to use a more light-way approach that doesn't break anything and does not add burden to all entity types.
Please have a look at the patch and approach outlined in #3025855. @tstoeckler correctly marked it as a duplicate, so let's continue the discussion here in #2507235.
I agree that this issue in general is quite important for the public image of Drupal in Germany.
Comment #31
ckaotik@Gogowitsch The thing is, we're not adding more entity annotation keys in this issue - that's already happened in core (see the change record). So while having tons and tons of things to set in an entity type's annotation isn't pretty, it's what core is doing. Changing that should probably get its own issue.
I've had two ideas regarding my notes in #27, that might be worth discussing for labels that don't currently use singular/plural forms.
1) We could (automatically when parsing the entity?) add a
context
to the string translation (e.g. "entity type label", "bundle label") to prevent accidental translation. Example: Labels that can be either verb or noun, like "exit" (in German: "Ausgang" = the exit/"verlassen" or "beenden" = to exit), where a generic translation will probably differ from the intended meaning.2) Currently, we use capitalized labels as defaults, and in certain cases defer to lowercased versions. What if, instead, we used the lowercase version as default and capitalize on a per-language per-case base? E.g. for config translation labels we use the label as-is on the local task (tab label "Translate @type_name") or in other cases where we can't be sure about capitalization, and capitalize it for standalone cases (i.e. when it's not a placeholder in a t-string), like the row title on the config translation overview page?
I'm not sure how this works for other languages, but iirc most european languages are fine with capitalized labels, as in standalone headings. The problem mostly comes from (not) using capitalization within phrases/sentences.
Comment #32
ckaotikRerolled patch from #27 against 8.7. Needs to be ported to 8.8, and remarks from before are still open.
Comment #33
ckaotikFixed recursion.
Comment #34
ckaotikRerolled.
Comment #35
tstoecklerThanks for the patch. It looks great. I think you missed one case in
UniqueFieldConstraintTest
.Also I think we should use this issue to deprecate
getLowercaseLabel()
. We should add a deprecation notice and simply inline the final remaining call ingetSingularLabel()
. Can you do the deprecation according to https://www.drupal.org/core/deprecation @ckaotik ? I am really glad to see you pushing this along and it would be awesome to see this land!Comment #36
Gogowitsch CreditAttribution: Gogowitsch as a volunteer and at QuoData GmbH Quality & Statistics for Merck KGaA commentedThanks to @ckaotik, @tstoeckler and all the others for the work on this issue.
I went ahead and created an updated patch for 8.8.x. In it, I:
getLowercaseLabel()
calls withgetSingularLabel()
getLowercaseLabel()
.EntityTypeInterface
to explicitly state that the return value fromgetSingularLabel()
will be used in (the middle of) a sentence.getSingularLabel
to callmb_strtolower
directly.I could not find any additional problematic call to
strtolower()
for entity types. There is code incore/modules/views/src/Plugin/views/style/Mapping.php:69
that might cause problems in German for field names, but I'll ignore that and focus on entity labels only.Comment #38
Gogowitsch CreditAttribution: Gogowitsch as a volunteer and at QuoData GmbH Quality & Statistics for Merck KGaA commentedOk, the new deprecation caused the tests to fail. That's actually good news, meaning that the tests pick it up.
This time I attached a patch that should get a green light from the Drupal Core tests.
Comment #39
tstoecklerAwesome work @Gogowitsch! 👍🏿 Thanks a lot. Yes, the previous test fail ist great verification of the patch.
One thing needs work, however, unfortunately. We need to add a change notice for the deprecation and then link to that from the deprecation instead of this issue. You can use the "add change notice" link at the top right of the page.
Sorry, I could have made that clearer by adding the respektive tag, doing that now.
Comment #40
Gogowitsch CreditAttribution: Gogowitsch as a volunteer and at QuoData GmbH Quality & Statistics for Merck KGaA commentedNo problem.
I wrote a draft change record - feedback is very welcome. In the attached patch, we now link to that new change record instead of this issue.
Send with love from the Extinction Rebellion 💚 bus :)
Comment #41
tstoecklerThanks a lot! Looks great to me.
Comment #42
catchI think we can remove 'to prevent breaking the German translation' here and rely on the change notice and commit history to convey why.
Comment #43
Gogowitsch CreditAttribution: Gogowitsch as a volunteer and at QuoData GmbH Quality & Statistics for Merck KGaA, ÖQUASTA commentedThanks, @catch!
Since the last patch I removed the explanation. Now, the deprecation message is clearer.
Comment #45
catchCommitted ed71ff5 and pushed to 8.8.x. Thanks!