Problem/Motivation
In #2212069: Non-English Drupal sites get default configuration in English, edited in English, originals not actually used if translated, the function locale_system_set_config_langcodes() was introduced in locale.module. This function is called whenever a module (hook_modules_installed()) or theme (hook_themes_installed()) is installed, and it forces the language of configuration to be set to the site default language.
My case is that we have a multi-site (shared codebase, separate databases). Some of these sites have English as default language, many of them have some other language as the default. English language is still enabled on all sites. Currently, each site has its full configuration exported to /config/<site name>. As the sites mostly consist of the same set of features, a lot of the configs are just duplicates.
Our plan is to start combining all of the common config using the config_split module, in order to reduce duplicate config files. Due to this, we want to keep the configuration language as English, since otherwise it would become really hard to start combining those.
We currently have the configuration language as English for all configs, but whenever a module gets installed, the behaviour of locale module causes the configuration language to change, which we would not want.
Another factor is that when we are developing the sites, for example creating new fields and such, we want to do the development in English as we don't necessarily speak all of the languages that the multi-site uses. Example: If I were to create a new field to example.no, I want to enter the field label in English and not in Norwegian. The same config would then be used also in example.se and example.fi, and the field label would be translated on each site via the Translate Interface tool.
Proposed resolution
I came up with three different approaches:
- Defining our own implementations of hook_modules/themes_installed(), make sure that they are called after locale modules hooks, and then call our own helper function, which reverses the logic of what
locale_system_set_config_langcodes()does - Defining our own implementations of hook_modules/themes_installed(), make them be run instead of locale modules hooks, and make them be identical with locale modules hooks with the difference that they don't call
locale_system_set_config_langcodes() - Patching the locale module and remove the body of the
locale_system_set_config_langcodes()function
Approach 1:
use Drupal\locale\Locale;
/**
* @file
* My module.
*
* We have multiple different sites and most of them have a non-English default
* language. Currently the sites have their own configs exported to their own
* directories, but we aim towards combining all of the common config using
* config_split. Therefore we want to keep all of the configs in English.
*
* The locale module implements hook_modules_installed() and
* hook_themes_installed(). Both implementations call
* locale_system_set_config_langcodes(), which will force the langcode of all
* config files to be set to the site's default language.
*
* In this module we implement the same hooks and then call our own function,
* which basically reverses what locale module does.
*
* We also make sure our hooks are implemented after local module's hooks.
*/
/**
* Implements hook_module_implements_alter().
*/
function mymodule_module_implements_alter(&$implementations, $hook) {
if ($hook === 'modules_installed' || $hook === 'themes_installed') {
// Move this module's hook's to be run immediately after the locale
// module's hooks, since our code fixes configuration langcodes issues that
// locale messes up.
// The code below removes 'mymodule' from which ever position it
// currently is in, finds the position of 'locale', and inserts
// 'mymodule' immediately after it.
$group = $implementations['mymodule'];
unset($implementations['mymodule']);
$local_position = array_search('locale', array_keys($implementations)) + 1;
// Slice 1 contains all elements from the start to 'locale'.
$slice1 = array_slice($implementations, 0, $local_position);
// Slice 2 contains all elements that are after 'locale'.
$slice2 = array_slice($implementations, $local_position);
// Insert 'mymodule' so it's directly after 'locale'.
$slice1['mymodule'] = $group;
// Merge slices back together.
$implementations = array_merge($slice1, $slice2);
}
}
/**
* Implements hook_modules_installed().
*/
function mymodule_modules_installed($modules) {
mymodule_set_config_langcodes();
}
/**
* Implements hook_themes_installed().
*/
function mymodule_themes_installed($theme_list) {
mymodule_set_config_langcodes();
}
/**
* Updates configuration langcodes to English.
*
* Basically this reverts what locale_system_set_config_langcodes() does, since
* we want config langcodes to be in English.
*/
function mymodule_set_config_langcodes() {
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
if ($default_langcode !== 'en') {
$names = Locale::config()->getComponentNames();
foreach ($names as $name) {
$config = \Drupal::configFactory()->reset($name)->getEditable($name);
if (!$config->isNew()) {
$langcode = $config->get('langcode');
if ($langcode === $default_langcode) {
$config->set('langcode', 'en')->save();
}
}
}
}
}
Approach 2:
use Drupal\locale\Locale;
/**
* @file
* My module.
*
* We have multiple different sites and most of them have a non-English default
* language. Currently the sites have their own configs exported to their own
* directories, but we aim towards combining all of the common config using
* config_split. Therefore we want to keep all of the configs in English.
*
* The locale module implements hook_modules_installed() and
* hook_themes_installed(). Both implementations call
* locale_system_set_config_langcodes(), which will force the langcode of all
* config files to be set to the site's default language.
*
* In this module we implement the same hooks and use them INSTEAD of locale's
* hooks. They are identical with the original hooks but with the callback to
* locale_system_set_config_langcodes() removed.
*
* We use hook_module_implements_alter() to remove the original hooks.
*/
/**
* Implements hook_module_implements_alter().
*/
function mymodule_config_module_implements_alter(&$implementations, $hook) {
if ($hook === 'modules_installed' || $hook === 'themes_installed') {
unset($implementations['locale']);
}
}
/**
* Implements hook_modules_installed().
*
* @see locale_modules_installed()
*/
function mymodule_config_modules_installed($modules) {
$components['module'] = $modules;
locale_system_update($components);
}
/**
* Implements hook_themes_installed().
*
* @see locale_themes_installed()
*/
function mymodule_config_themes_installed($themes) {
$components['theme'] = $themes;
locale_system_update($components);
}
Approach 3:
See attached patch and apply it using composer.
This approach allows us to notice if locale.module is changed by a core update, since the patch would probably not apply anymore.
I can only assume that others have also faced similar problems as what I've described here. Even though I understand that this is the intended behaviour of Drupal core and of the locale module, it just doesn't fit our needs at the moment, which is why I posted some different approaches here for those who are interested.
| Comment | File | Size | Author |
|---|---|---|---|
| #48 | drupal-3150540-D1122.patch | 3.49 KB | mscieszka |
| #46 | drupal-3150540-46.patch | 4.14 KB | kuldeepbarot |
| #45 | 3150540-45-MR9103-11.x.diff | 3.9 KB | kriboogh |
Issue fork drupal-3150540
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
rade commentedComment #4
rar9 commentedin this helping for my issue that was closed?
https://www.drupal.org/project/drupal/issues/3161643#comment-14110368
Comment #5
johnwebdev commentedSeems like a great solution for your use case. I've had the same use case as you did before, and I actually wish that I would come up with something like that back then.
It would be interesting to try split all translatable strings directly into language/xx folder as default, and only keep "language neutral" configuration in the config/sync directory.
Comment #6
ckaotikFor the
locale_system_set_config_langcodesfunction, can we assume that this langcode change is only necessary/desirable on non-English monolingual Drupal sites? Maybe also on sites that do not have English installed? Or on sites that do not useconfig_translation?config_languagemodule is installed,localeshouldn't mess with configuration at all. If the user requires translation, it will be imported as a translation override (e.g. fromconfig/sync/language/de), and most modules will ship with English configuration.What happens to modules that ship non-English default configuration (keep as is/pretend it's English/transform between languages)?
enoverrides?Personally, I'd much prefer the base configuration being in English, even if the site default is another language. Primarily because that's the default language for un-specified install configuration items.
Ideally, we would either ensure to always have English default configuration, or ensure that non-English default configuration is retained as such (optionally with
enlanguage overrides). The current handling is doing neither on sites with another default language.Comment #8
norman.lolYeah this behaviour can really create quite a mess. I wish default config always is EN, same as translatable strings.
Until then you'd probably need to ensure that default config simply is in the same language as the default language. Which makes life quite awful for editors though, because they might often see strings in the backend in a language they can't read.
Comment #9
bramvandenbulcke commentedI agree it's a mess right now.
For your information: I live in Belgium and we create lots of multilingual websites. We created a starter installation in English. We prefer to develop in English.
For a multilingual website there is not really a problem. We can set default language as English and disable English with the disable_language module.
But for websites with one (non-English) language it gets complicated. We want to roll out these websites without a path prefix for this one language. Disabling the path prefix is only possible on the default language, so the default language has to be non-English in that case. The moment we switch this setting all config languages change to the other language. Also, disabling the URL prefix globally (under Detection Method) doesn't seem to work and looks like a dangerous setting to disable.
So, we would also prefer to have English as the default config language!
Comment #10
ericdsd commentedThat's why i think, that for non english sites, config should probably be considered as english config by default and might generate translation in other languages even if only one language (not english). So for exemple a german site should have config considered as english one and have a german translation generated.
Comment #11
pasqualleI think patching core would be the preferred solution, as the current core behavior is not suitable for international teams.
The current patch is not acceptable for core, as it breaks the current core behavior. Existing sites applying this patch might get even messier.
The default language used for configuration entities needs to be configurable.
Comment #12
pasqualleSimilar patch in #2806009-65: Installing a module causes translations to be overwritten
Comment #13
eiriksmHere is the start of a patch that is backwards compatible, but makes it possible to disable what's in this function completely. Could this be acceptable as a first take?
Still needs tests, default config, schema updates and so on, but as a POC it should be possible to discuss.
Comment #14
eiriksmHm, I was writing it a bit quick I see. There is no default value param to the config get function. Not backwards compatible then... But now I have to run, so I will have to revisit
Comment #16
eiriksmI am glad to see we have tests that assume the default would be to have this option enabled. So here is a patch I think should pass tests (and as a consequence, hopefully be totally backwards compatible)
Comment #18
geek-merlin@eiriksm Great you took a stab at this!
Code-wise it makes sense to me. Did not look into the failing tests though.
Comment #19
hudri@bramvandenbulcke, #9
The current workaround here is to set the default site language to english, but the default language detection to your_lang. This way config should not get polluted, but visitors of
/should still see the frontpage (and all sub pages) in your_lang language, while all english pages need an explicit/enprefix.This is taken from https://www.drupal.org/project/drupal/issues/2806009#comment-14006988
Comment #20
eiriksmUpdated patch that passes the last test at least locally. Let's get it green and go from there
Comment #21
eiriksmAnd here is one with a test, and a test only patch. I also added the setting to the locale settings form. The text in the config form is, shall we say, not ideal. Would love to hear from someone with better English and/or better UX eye than me about that text.
Comment #22
eiriksmTrying again... Now hopefully without cspell error
Comment #24
eiriksmComment #25
eiriksmThe last patch was not the best test-only patch. It failed for other reasons. Included here is a test only patch that has everything of the patch, except the logic for doing "the stuff".
Comment #27
bramvandenbulcke commented@hudri, indeed this setup has some advantages over my current setup. I followed the comment from Sutharsan in the same thread you mention: https://www.drupal.org/project/drupal/issues/2806009#comment-14009513.
Steps to take:
The site is in this case in Dutch on the frontend and English in the backend. Only problem with this setup is that the backend is in English for both admins and editors. Some editors won't bother but others will prefer a backend in their native language.
Comment #28
michel.g commentedThe latest patch is missing a default value set for the checkbox on admin/config/regional/translate/settings
Comment #29
michel.g commentedAttached the updated patch with the default value set
Comment #30
michel.g commentedComment #32
smustgrave commentedThis issue is being reviewed by the kind folks in Slack, #needs-review-queue-initiative. We are working to keep the size of Needs Review queue [2700+ issues] to around 400 (1 month or less), following Review a patch or merge request as a guide.
This new feature request will require test coverage.
Adding the new configuration option will require an upgrade path for existing sites.
That new field may need a change record as well.
Thanks.
Comment #33
antonín slejška commentedI get the following error message after composer update:
Cannot apply patch #3150540 Config in en (patches/core/3150540-30.patch)!I have started a new test for 3150540-30.patch. The test failed: https://www.drupal.org/pift-ci-job/2657165
Comment #34
ericdsd commentedThis is quite logical as https://www.drupal.org/project/drupal/issues/2806009 has been fixed and committed in 9.5.9 release, i didn't test it yet but it should address this issue too.
Comment #36
kalvis commentedRerolled the patch from #30 against latest 9.5.x as it no longer applied due to some recent changes in core.
NB! For the patch to take effect, after applying you need to untick the new checkbox in /admin/config/regional/translate/settings :)
Couple of things I adjusted:
- Renamed the new
enable_set_config_langcodessetting toupdate_default_config_langcodesand updated some of the descriptions to be more aligned with the variable namings/descriptions that are already in core.- Fixed the default value assignment in settings form - if no value was present in config yet, checkbox was not selected (while it should be according to the defaults)
- Removed the alteration part, not sure if it's really needed. But please feel free to add it back if so :)
Here's the full diff, if you interested: https://www.drupal.org/files/issues/2023-05-24/reroll_diff_30_36.txt
@ericdsd I tested with latest 9.5.x release on the same project which @Rade was working on when reporting this problem an it was still present. Thus the rerolled patch :)
Comment #37
arnalyse commentedJust for completeness sake: Drupal 9.5.9 has removed the calls to
locale_system_set_config_langcodes()which were former placed inlocale_themes_installed()andlocale_modules_installed().If I'm not mistaken
locale_system_set_config_langcodes()isn't called anywhere else.Comment #38
edurenye commentedSeems like sometimes it does not work, but I'm not quite sure which conditions make it to not work, so I have the default language in Swedish, but we want the base config in English and have the translations in Swedish.
And most of the time it works but then today I changed some permissions and when I exported it wanted to add the labels in Swedish to the English base exported config, really weird.
Comment #39
edurenye commentedThe patch does no longuer apply to Drupal 10.2.2.
And `Locale::config()->updateDefaultConfigLangcodes();` is not called in `locale_system_set_config_langcodes()` anymore.
Not sure if the issue will still happen or if this should be fixed now.
Comment #40
andriic commentedRerolled the patch from #36 for Durpal 10.2.2.
Comment #41
kriboogh commented#40 works for us.
I think config management in drupal should be that, all config is considered English always and if you install a different language, translatable keys in config are then dealt with using normal locale translation by default, or by a language config override if that's present.
Comment #42
edurenye commentedRerolled the patch for Drupal 10.3.0.
Comment #44
kriboogh commentedCreated MR for 11.x based on patch from #42.
Added patch of MR for use in composer.
Applies to 11.x and 10.3.x.
Comment #45
kriboogh commentedFixed a spelling error so tests could run.
Comment #46
kuldeepbarot commentedRerolled the patch for Drupal 10.4.6.
Comment #48
mscieszka commentedPatch for Drupal 11.2.2
Comment #49
ericdsd commentedNote that when applying #46 you need to save the config if you want to have "Update default configuration when new modules or themes are installed." option disabled (at admin/config/regional/translate/settings) as if it's not saved the value will fallback to true and won't have any effect.
If properly saved it works over 10.4.6.
Comment #50
quietone commentedHi, in Drupal core changes are made on on 11.x (our main development branch) first, and are then back ported as needed according to the Core change policies. Thanks.
Comment #51
oily commentedThere were a lot of broken tests in the pipeline but did not look related. Re-ran pipeline and it is green.
Comment #53
oily commentedStarted code review. Change to the form field description.
Comment #54
oily commentedIt looks like the test coverage should go inside the Kernel test LocaleBuildTest.php.
Comment #55
anybodyComment #57
ericdsd commentedPatch #46 still applies on 10.5.8
Also needs to save /admin/config/regional/translate/settings as newly created option "Update default configuration when new modules or themes are installed." must be explicitely saved to false (as it defaults to true until first saved).