Problem/Motivation
D7 introduced great feature: language fallback that was used for entity translation. Unfortunately this feature was used only for entity translation. A contributed module was created to add language fallback features to the string translations too.
Unfortunately it seems that this inconsistency in translation mechanisms is now also present in D8. The core string translation ignores language fallback features that are also provided by the core.
Proposed resolution
It all boils down to the LocaleLookup::resolveCacheMiss function that in case of missing translation should check fallback languages offered by LanguageManager::getFallbackCandidates() function.
Pass the languageManager down further.
Improve test coverage.
Remaining tasks
- (done) Create a patch.
- (done) Review
User interface changes
No user interface changes.
API changes
No API changes beside the fact that finally language fallback will work also for string translation.
Comment | File | Size | Author |
---|---|---|---|
#71 | locale-2122175-71.patch | 17.9 KB | dclavain |
#64 | locale-2122175-64.patch | 17.89 KB | tim.plunkett |
#64 | interdiff.txt | 1.66 KB | tim.plunkett |
Comments
Comment #1
SiliconMind CreditAttribution: SiliconMind commentedA patch that adds language fallback to string translation.
Comment #3
SiliconMind CreditAttribution: SiliconMind commentedPrevious patch failed testing on irrelevant tests. Try new one to make sure.
Comment #4
SiliconMind CreditAttribution: SiliconMind commentedTrying again original modifications. Last patch was kinda dummy.
Comment #5
SiliconMind CreditAttribution: SiliconMind commentedComment #7
SiliconMind CreditAttribution: SiliconMind commentedApparently the language_fallback_get_candidates() function was removed. This patch uses LanguageManager::getFallbackCandidates() instead.
Comment #9
SiliconMind CreditAttribution: SiliconMind commented7: add_language_fallback_to_string_translation-2122175-6.patch queued for re-testing.
Comment #11
SiliconMind CreditAttribution: SiliconMind commented7: add_language_fallback_to_string_translation-2122175-6.patch queued for re-testing.
Comment #13
SiliconMind CreditAttribution: SiliconMind commented7: add_language_fallback_to_string_translation-2122175-6.patch queued for re-testing.
Comment #15
SiliconMind CreditAttribution: SiliconMind commented7: add_language_fallback_to_string_translation-2122175-6.patch queued for re-testing.
Comment #17
SiliconMind CreditAttribution: SiliconMind commented7: add_language_fallback_to_string_translation-2122175-6.patch queued for re-testing.
Comment #18
Gábor HojtsyI don't get this patch. If I have German, Norvegian and English on my site in this order, would a German site page potentially contain Norvegian strings (instead of English) due to your proposed fallback logic? How is this useful?
Comment #19
SiliconMind CreditAttribution: SiliconMind commentedGabor, you could ask the same question in regard to language fallback for entity translation which is already present in core :) Besides since the language fallback feature exists it should be used consequently for all translation cases, that is entity translation and string translation.
There are scenarios where it is not desired to have a translation fallback directly to English. Take a look at original language fallback contributed module and it's use case:
Note that you can also provide your own fallback order, though UI for that is not in the core.
Comment #20
Gábor HojtsyRight, so the proposed logic makes sense for a subset of scenarios. Since there is no configuration proposed, applying it to all scenarios sounds like a problem to me.
Comment #21
SiliconMind CreditAttribution: SiliconMind commentedThere is also no configuration for entity translation fallback. It is enabled out of the box. This could be worked out as a separate issue. Note that this patch does not change anything until you actually provide a candidates for fallback.
Comment #22
matsbla CreditAttribution: matsbla commentedThis fallback function is very useful, especially when making a website containing minority languages, as it is possible to specify different fallback languages for different languages. E.g. I can have a website in Nortern Sami, but if I don't have the string translated in Northern Sami I would prefer more to have it displayed in Norwegian, than English (Most Sami people understand Norwegian much better than English). As a Norwegian I would also prefer to see strings not translated to Norwegian, in Danish, as Norwegian Bokmål writing is based on Danish; some words and sentences can even be written exactly the same. It can also be practical for other issues, e.g. Chinese simplified and Chinese traditional could benefit from using each other as fallback languages. And we can make it possible for visitors to specify their own fallback languages, so they can adapt the translations used specific for their own need.
So in general the language fallback function is practical for several use cases, but it should be a unified way to handle them. Why only use it for entity translations?
Comment #23
Gábor HojtsySo this is not useful in any way without the logic to specify those fallbacks which is not proposed. A module implementing that logic / UI could also swap the service with an implementation that does the fallback, which is a huge point of the service based architecture in Drupal 8. I'm not sure without proving this full circle is good especially since it harcodes a behaviour but only applies to some scenarios.
Comment #24
SiliconMind CreditAttribution: SiliconMind commentedBut you do realize that current implementation is inconsistent? Entity translation works differently than string translation. The goal of this patch is to unify this.
Comment #25
Gábor HojtsyThere is no field level language fallback system in entities either (in Drupal 8), so the language selected is global to the entity. Much like the language selected for the page is global to the page unless specific parts request otherwise. Same applies to configuration translation. I think the feature you are proposing here is more akin to the field level language fallback in content entities that Drupal 7 supported in entity translation. That is not present in Drupal 8 AFAIK.
Comment #26
SiliconMind CreditAttribution: SiliconMind commentedYou can see here that EntityManager uses LanguageManager::getFallbackCandidates to obtain a list of language fallbacks to check for translations if no translation for specified context exists. We only want to implement the same behavior for string translation.
Comment #27
matsbla CreditAttribution: matsbla commentedGábor;
If we later want to port the language fallback module to Drupal 8; could we do it without LanguageManager::getFallbackCandidates?
As you say that it makes no sense to implement the function without the UI: how could a "module [Drupal 8] implementing that logic [...] swap the service" ? Maybe you could explain more or share link that explain better how this "is a huge point of the service based architecture in Drupal 8"?
And can you explain better if/why the LanguageManager::getFallbackCandidates exists in Drupal 8?
Comment #28
Gábor HojtsyAll right I took another look at LanguageManager::getFallbackCandidates. I have not been involved in getting that into core and have only seen it in relation to content fallback. It does seem like a very generic solution to any kind of language fallback priority generation, however, that would need to segment this use case with its own context. At least that looks like it would segregate this feature to its own corner without the chance of unrelated content fallback setups interfering.
Extra whitespaces should not be here.
1. Pass on a context array with an operation of 'locale_lookup' or some other specific string.
2. Ensure no code in core does language fallback altering without checking for specific contexts, so this does not become a huge performance nightmare like it looks like :)
Also no core committer would consider committing this without test coverage. Basically multiple local languages set up with different sets of strings, a test module implementing a language fallback alter hook for this context and then a test to ensure the right fallbacks are applied.
Comment #29
penyaskito@matsbla: If needed, how to override services is described in Altering existing services, providing dynamic services and but the way this is the example mentioned there.
Just thinking out loud, I was thinking about getFallbackCandidates for string translation too, but in the case that we need different behaviors for string and content.
I can think of scenarios where we want to fallback strings from Spanish (Venezuela) to Spanish, but for content I want a different behavior (like a Not found or Forbidden) if the content has not translation.
Gábor pointed on IRC that we have $context['operation'] for that and we can provide different fallbacks based on 'locale_lookup' for the locale case and 'entity_view' for the other. We still have also hook_language_fallback_candidates_OPERATION_alter in place too.
So I'm +1 for trying to get this in as makes everything more consistent (although it took me a while to understand).
Comment #30
SiliconMind CreditAttribution: SiliconMind commentedOK, thanks for your input guys. Well add some context string and work out a test module along with some unit test for this after Christmas.
Have a great Christmas and a happy new year!
Comment #31
SiliconMind CreditAttribution: SiliconMind commentedAdded patch with unit tests for language fallback.
Comment #32
SiliconMind CreditAttribution: SiliconMind commentedDamn, that was too soon. The patch was missing actual changes to the LocaleLookup.php
Comment #35
dclavain CreditAttribution: dclavain commentedI'm going to work on this.
Comment #36
dclavain CreditAttribution: dclavain commentedI added the 3 required properties as variables because they were out of scope.
Comment #37
Gábor HojtsyFix tag.
Comment #38
gloob CreditAttribution: gloob commentedComment #39
Gábor HojtsyFound several code style problems:
Code style: add newlines before @var. (More instances not done here).
I don't think this indentation is Drupal code style compatible. Also lots more elsewhere.
Code style: first function comment line should be on its own line, one space between 'function' and function name.
Function doc comment should be at most up to 80 chars, not wrapped.
Not indented properly.
Comment #40
dclavain CreditAttribution: dclavain commentedok Gábor Hojtsy, I going review the problems that you comments.
Comment #41
SiliconMind CreditAttribution: SiliconMind commentedGábor,
I've fixed the coding issues that you've mentioned. It would be much easier if I could make my D8 sandbox to work with the coder module. Unfortunately it refuses even to register menu items :(
However there is one thing I don't get:
What exactly are you referring here? The chained method calls? Drupal coding standards doesn't say a word about that and this indentation style is the same as the one used in PHPUnit examples. Can you please be more specific?
Comment #42
dclavain CreditAttribution: dclavain commented@Gabor I fixed all problems that you comments. Could you review it?
Comment #43
dclavain CreditAttribution: dclavain commentedComment #44
penyaskitoFound a typo in a docblock, otherwise looks good to me. Hope that Gábor agrees.
*factory
Comment #45
dclavain CreditAttribution: dclavain commentedthanks for your comment @penyaskito , I added new patch that resolve this typo.
Comment #46
Gábor HojtsyThis may look cooler, but doing the three assignments on their own lines would be 2 lines less and less cycles (no intermediary array).
Comment #47
dclavain CreditAttribution: dclavain commentedThank you @Gábor. I added new patch.
Comment #48
Gábor HojtsyLooks good to me.
Comment #49
YesCT CreditAttribution: YesCT commentedsaw a few nits while checking if this still applied. went a head and fixed them.
[edit removed some dreditor mistake]
IDE says this use is unused. It was added in patch 6 (comment #7) when \Drupal::languageManager()->getFallbackCandidates() call was put in the patch. But it might not need to be "used" for that.
wrapping these to 80 chars and using period to end the sentence.
"Lines containing comments (including docblocks) must wrap as close to 80 characters as possible without going over, with a few exceptions..."
"All documentation and comments should form proper sentences, use proper grammar and punctuation..."
https://drupal.org/node/1354#drupal
This is Contains now with \Drupal... probably copied from an older example test . https://drupal.org/node/1354#file
should these and others be type hinted with | (or) like in PermissionsHashTest?
I think we dont want the type in the one line summary. Changing that to The translation.
I think we just indent the next lines of -> with two spaces. I looked for other examples in core, and 2 spaces seemed to be what was done. Also suggested in #1516460: Define coding standard for chained method calls.
:) I dont think we usually use $arg like this. Changed to $langcode since that is what it is.
a. StdClass doesn't seem like the best type hint here. but I'm not sure we dont have that many phpunit examples yet. But if it's just a object then.. hm. "object (NOT "stdClass")" https://drupal.org/node/1354#types
b. Also, should be bool
"bool (NOT "boolean" or "Boolean")" https://drupal.org/node/1354#types
c. why return *true* if you *dont* find the translation?
ran the tests locally per https://drupal.org/node/2116263
before patch, (1 test, 2 assertions)
after patch (3 tests, 28 assertions)
removing the needs tests tag, since the patch has all the tests it needs I think.
Comment #50
Gábor HojtsyAll changes look good, superb cleanups.
Comment #51
SiliconMind CreditAttribution: SiliconMind commentedYesCT,
Well, actually that function mimics behavior of actual drupal code. Take a look at LocaleTranslation::getStringTranslation - It looks like it expects that failures will return TRUE.
Comment #54
YesCT CreditAttribution: YesCT commented49: add_language_fallback_to_string_translation-2122175-49.patch queued for re-testing.
Comment #55
YesCT CreditAttribution: YesCT commentedok. opened another issue we can look at later. #2188715: Document return value in \Drupal\locale\LocaleTranslation::getStringTranslation
Comment #56
tim.plunkettCleaned up the unit test. We usually (always?) use an anonymous function with returnCallback().
Comment #57
YesCT CreditAttribution: YesCT commentedHere are some notes I took while I tried to do that... but my attempt (in the interdiff attached) was not as elegant. :)
------
actual callback static methods
->will($this->returnCallback('Drupal\locale\Tests\LocaleStringFallbackTest::stringStorageMockFindTranslation'));
->will($this->returnCallback('Drupal\locale\Tests\LocaleStringFallbackTest::getFallbackChain'));
should be inline anonymous functions,
since then
they would be
easier to understand, and
move the code closer to where it is called
Also, we dont have static methods for any other returnCallback in core, but we do do the inline anonymous function 15 times.
See
ag "returnCallback\(" --ignore-dir vendor
Looked at examples in
MigrateTestCase::getMigration()
FormBuilderTest::testSetErrorByName()
------------------
a. tim's probably more up on the @group stuff's so that's fine.
b. Note that a separate novice issue can be made to change the other lowercase group to be Locale also.
c. And adding the 2 explicit properties was something I thought of doing to, so that looks good. (could add the one line summary to them. I'll do that.)
d. Ah, I missed those camelCase property names. good.
e. it was the use, using the translations array in the returnCallback, that I saw in the examples, but couldn't quite make the leap to do. very cool.
f. Not sure about the container thing. It's probably obvious to tim, but maybe a little explanation as to why that's better would help me spot a similar situation in the future.
--
without patch
./vendor/bin/phpunit --group Locale
(4 tests, 16 assertions)
with
(6 tests, 40 assertions)
so that still looks good also.
--
should be green and I can't see anything else to improve. rtbc from me. (didn't try it manually though)
Comment #59
YesCT CreditAttribution: YesCT commentedthe two fails reported on patch 56 are unrelated/random and in the views_ui tests
In the test results, in the collapsed review log fieldset can see burried there:
Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 268435456 bytes) in /var/lib/drupaltestbot/sites/default/files/checkout/core/lib/Drupal/Core/Database/Connection.php on line 336
Let's see how 57 does.
Comment #60
SiliconMind CreditAttribution: SiliconMind commented56: string-translation-2122175-56.patch queued for re-testing.
Comment #62
tim.plunkettDiscussed more with @YesCT, we're testing the wrong class here.
We should be testing LocaleLookup in isolation.
In order to do so, we should properly inject everything.
The interdiff is vaguely useless, since I had to move the test.
Comment #64
tim.plunkettDoh!
Comment #65
YesCT CreditAttribution: YesCT commentedsweet! that is the same interdiff I was just about to upload. I might be getting the hang of this!
Comment #66
SiliconMind CreditAttribution: SiliconMind commentedThis looks great. I've been struggling to test this in real life scenario, but it seems that D8 entity translation doesn't work at all even without this patch. Can anyone confirm similar behavior that I described here #2189267: When content language detection is different from interface language detection, the detected language is not applied to the rendered content?
Comment #67
YesCT CreditAttribution: YesCT commented64: locale-2122175-64.patch queued for re-testing.
Comment #68
Gábor HojtsyGreat, improvements, thanks! Looks good to me.
Comment #69
YesCT CreditAttribution: YesCT commented64: locale-2122175-64.patch queued for re-testing.
Comment #70
alexpottlocale-2122175-64.patch no longer applies.
Comment #71
dclavain CreditAttribution: dclavain commentedreroll
Comment #72
YesCT CreditAttribution: YesCT commentedthat applies, removing needs reroll tag.
Comment #73
Gábor HojtsyThanks for the reroll.
Comment #74
plachMinor nitpick to be fixed on commit/reroll: missing white space before the parenthesis.
Comment #75
Gábor Hojtsy71: locale-2122175-71.patch queued for re-testing.
Comment #76
Gábor Hojtsy71: locale-2122175-71.patch queued for re-testing.
Comment #77
YesCT CreditAttribution: YesCT commentedthis is rtbc, but the issue summary is out of date.
I'm starting to work on that.
Comment #78
YesCT CreditAttribution: YesCT commentedok. no major changes to the summary. I think the proposed resolution is pretty much the same.
Comment #79
webchickCommitted and pushed to 8.x. Thanks!
Comment #81
Gábor HojtsySuperb, thanks!
Comment #82
SiliconMind CreditAttribution: SiliconMind commentedGreat!
Since this is resolved, has anyone seen that one: #2215507: Downloads broken for translated private files
Comment #84
Gábor HojtsyThis issue ended up sparking a critical bug. Applying the default content language fallback rules to interface translation is nonsensical. See #2240459: Applying default language fallback rules to interface translation has impressively bad results for a solution that removes the incorrect defaults but keeps the extensibility. @SiliconMind, @dclavain, @plach, @YesCT: reviews welcome there at your convenience :)