Voting starts in March for the Drupal Association Board election.
When installing a Drupal 8 site that has no English language configured, the default configuration shipped is still in English. The user interfaces to edit shipped configuration (eg. views, maintenance mode setting, node types, fields, etc) are all editing the original configuration (in English). The user interface for editing will be in the foreign language installed however. When adding new elements, eg. new fields to a view, the user is expected to add them in English and translate them in configuration translation (so enabling the configuration translation module is required to keep it sane). That is silly.
Why is this so?
We keep shipped config as-is as a reference point for configuration translation, so we can apply configuration translation against the shipped configuration text. We break down default configuration to its translatable strings and each string that was not modified in active config (and shows up in the same config file) gets its translation applied. This is good for various reasons. (A) When you install, you don't need 100% of the translations, you can keep adding them later (eg. we can import translations after configuration import and don't bother if translations cannot be imported at one point, it can be fixed later). (B) When you add more languages, they still have the same original reference point, so their translations will work too.
Comparing this to Drupal 7, there default configuration was shipped in core and t()-ed in modulename.install when saved into variables, etc. Only translations available at that time were applied, so when you updated your interface translation later on, your fields, content types, etc. were still half-translated and there was no way to fix them. They were "user provided configuration" so the locale module did not concern those. If you changed the site default language, the configuration was still in the same possibly half-baked form and did not adapt to your language change. All of those are fixed with this "original reference configuration" approach.
The use cases and the current architecture
The reason we have the original config in English is to keep a relatively stable copy to reference.
1. We compare the install storage with the active config to figure out which ones to translate (to skip the strings you modified and don't attempt to translate strings for things you added to the config).
2. On the other side, we keep the interface translation as reference for the interface translation which mostly come from the community. If you modify a translation locally (in the interface translation UI or with the config translation UI), we save it back to this reference as customized, so further localization updates will not update it anymore. The user wanted to manually change it so we keep it as to what it was changed manually.
Here are some use cases we satisfy currently in Drupal 8 to see what we are up against:
A. Be able to translate any configuration to any other language anytime.
B. Be able translate shipped configuration with localize.drupal.org / interface translation module.
C. Be able to update translations to shipped configuration (given people assume it as part of the software) anytime.
D. Be able to modify translations locally and keep those translation modifications protected from these updates.
E. Be able to modify the configuration and still able to apply translations to parts of configuration which were not modified from the original (ie. if I modify the default value for article image fields, it should still get the label translated properly and get any updates to the translation of that label).
So we have a system where the order of installation / config import and translations does not matter and you can edit your configuration or your translations and its all respected. Here is the same in other words on a figure:
The user experience concern is that we do this by keeping the English (copy of original shipped config) as the active configuration and editing that is in some ways pointless where translations apply and adding to it is silly in English. It is even more confusing to need to edit the English view to add new fields and then either doing that in your own language (and ending up with a mixed language view) or English even though you don't have English on the site.
We satisfy all of the use cases above currently in Drupal 8. A solution that would not allow some of these would be a regression, so need to be considered if such regression is acceptable compared to the experience gain to achieve. The current proposal satisfies all those use cases.
The most UX friendly proposal is to have active configuration saved in the default foreign site language of the site instead of imported in English. That means, we apply translations at first when importing config. What comes out of that?
- Satisfying use case B needs more work if you add English later: If the site default language is English, and the shipped config is English, we can import the default config in English (as it is now). If the site does not have English, then we translate the default config after importing. If English is added later, we need to go through the original shipped configs and extract their English sources and save those as the English translation (for config strings which are still present). This is a new custom "translation" process that is not implemented in Drupal 8 now.
- Further modification to satisfy B: To be able to translate to other languages, we need to make changes to how we compare active config to shipped config, we need to ignore string changes. So if you modified your node admin view to say "Headline" instead of "Title" for your node lists in whatever language, we'll still keep translating "Title" in other languages. Because we can only compare key names then, changes to lists of items (eg. allowed values) may result in confusing results. Eg. if you change allowed values to "yes/no" from "on/off", the translations will still consider "yes/no" because allowed values are stored as lists.
- Modification for use case C: now would need to update active config for shipped config that was translated and update language overrides for other languages. So it needs to be aware of both active config and language overrides. Prior to this patch its only aware of language overrides and how to structure them.
- Modification for use case D: When editing and saving or importing original configuration, if that had a shipped configuration as well, we need to also save string changes as custom translation. Will need to compare the active config to shipped config to weed out strings that are still original English and not save those as customized translations. Should only save those as customized that are actually translated (AKA modified).
- Finally how we compare shipped config with active config would be a lot more fragile thanks to skipping string comparisons on translatable strings. There is an existing problem that shipped config may change in a module without the active config changing and we should compare active config to the shipped config of the time when the active config was originally imported. Given we skip comparing the translatable strings making the comparison more fragile, we need to make the system less fragile on the other end. We should keep a copy of the shipped config at the time of installation to compare for localization. This is in a separate issue, see .
Use case E is already satisfied with the above modifications.
A very important change in this system compared to current would be configuration translation would fall back to NOT ENGLISH but whatever happens to be the language of the active configuration instead. Ie. in this figure, German would fall back on French and not English (even after you added English).
A sample process with installing a file and updating translations as well as editing the file manually, editing translations manually and updating translations again:
User interface changes
1. You can now edit foreign language configuration as the active configuration on a non-English default site.
2. Language names are not provided in English anymore either, but in the language of the site at the time of adding the language.
Let's look at the current API first. The locale - config integration is done with two main drivers. The LocaleConfigSubscriber listens on configuration override changes and when configuration overrides are saved or deleted, it reacts by updating locale database data. The LocaleConfigManager is invoked after database changes in locale (when importing translations, editing translations manually, etc) and it reacts on those to update configuration overrides:
The basic structure of this setup is kept and responsibilities are cleaned up in the proposed patch.
1. When active configuration is edited, we also respond to that in LocaleConfigSubscriber.
2. When a new module is installed, we also react to that by updating the (just imported) configuration with the site default langcode (so LocaleConfigManager can later update it with translations as appropriate). When installing locale module itself, we update all prior active configuration that was imported from default configuration to the site default langcode.
3. The locale_config_update_multiple() function is now LocaleConfigManager::updateConfigTranslations().
4. We do not respond to modules uninstalled anymore. LanguageConfigFactoryOverride::onConfigDelete() already responds and removes config overrides when the config is removed. Locale's database is additive so we should not remove items when removing components.
Detailed API changes:
|---||TranslationWrapper::getUntranslatedString()||TW is used as wrapper of translatables|
|---||TranslationWrapper::getOption($name)||TW is used as wrapper of translatables|
|locale.config.typed (service)||locale.config_manager||There was not much typed about it before, and there is nothing typed about it now. Unified naming with other services in and outside of locale.|
|LocaleConfigManager::hasTranslation($language)||LocaleConfigManager::hasTranslation($langcode)||We took langcode in every other method.|
|locale_config_update_multiple($names, $langcodes)||LocaleConfigManager::updateConfigTranslations($names, $langcodes)||Did things that are the primary responsibility of the locale config manager.|
|---||locale_system_set_config_langcodes($components)||Responds on component install, invoked from hooks.|
|LocaleConfigManager::deleteComponentTranslations()||---||Locale is additive, should not respond on component removal.|
|locale_translate_english()||locale_is_translatable($langcode)||More flexible. Such a function with a langcode param was needed to avoid copy-pasting logic around several times.|
|LocaleConfigManager::get($name)||LocaleConfigManager::getTranslatableDefaultConfig($name)||Does not return a possibly half-broken typed config anymore. Instead its a nested array of TranslationWrappers.|
|public LocaleConfigManager::saveTranslationData($name, $langcode, $data)||protected LocaleConfigManager::saveTranslationOverride($name, $langcode, $data)||Now that both overrides and active config managed here, required a rename to be more specific.|
|---||protected LocaleConfigManager::saveTranslationActive($name, $data)||New method to save translation to active config.|
|public LocaleConfigManager::deleteTranslationData($name, $langcode)||protected LocaleConfigManager::deleteTranslationOverride($name, $langcode)||Now that both overrides and active config managed here, required a rename to be more specific.|
|LocaleConfigManager::getComponentNames(array $components)||LocaleConfigManager::getComponentNames(array $components = array())||Was documented to be optional, but the code did not do justice to the docs.|
|---||LocaleConfigManager::reset()||To reset the internal cache used by translateString(). Needed now that we use it for more things.|
|---||LocaleConfigManager::getStringTranslation($name, $langcode, $source, $context)||Takes on responsibility that LocaleConfigSubscriber had duplicate code for (which is now removed).|
|---||LocaleConfigManager::getDefaultConfigLangcode($name)||Utility method to look up default language code of shipped config.|
|---||LocaleConfigManager::getActiveConfigLangcode($name)||Utility method to look up active language code of current config.|
|---||LocaleConfigManager::isSupported($name)||Utility method to verify if this configuration is supported in the locale integration at all. Used both here and in LocaleConfigSubscriber to avoid duplicating logic.|
|LocaleConfigManager::isUpdatingConfigTranslation()||LocaleConfigManager::isUpdatingConfigTranslationFromLocale()||Renamed to make the responsibility clear. The prior code indicated this may not have been understood well.|
|LocaleConfigSubscriber::__construct(StringStorageInterface $string_storage, ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager)||LocaleConfigSubscriber::__construct(ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager)||Does not work with locale string storage anymore directly. Uses LocaleConfigManager::getStringTranslation() instead, which works with the cache there, so lots of duplicate code was removed from here.|
|LocaleConfigSubscriber::onSave(LanguageConfigOverrideCrudEvent $event)||LocaleConfigSubscriber::onOverrideChange(LanguageConfigOverrideCrudEvent $event)||Renamed due to the added responsibility of responding to active config events too (named Change because it reacts to save and delete too).|
|LocaleConfigSubscriber::onDelete(LanguageConfigOverrideCrudEvent $event)||LocaleConfigSubscriber::onOverrideChange(LanguageConfigOverrideCrudEvent $event)||Yes, same as for save.|
|---||LocaleConfigSubscriber::onConfigSave(ConfigCrudEvent $event)||Responds to active config saves.|
|LocaleTypedConfig (the whole class)||---||Removed entirely. It was only used with possibly broken input data and its code was duplicated elsewhere. A much simplified approach is used directly in LocaleConfigManager.|
|---||KernelTestBase::defaultLanguageData()||Allows kernel tests to simulate a foreign language default environment.|
|---||langcode key on top level config in config schemas now added by default, no need to define it in schema||Required now that we may modify any shipped config with an added langcode.|
|ConfigMapperInterface::getLanguageWithFallback()||---||Removed because English is not special anymore, so this method would not provide any fallback anymore. Use LanguageManager::getLanguage()|
|#161||interdiff.txt||6.89 KB||Gábor Hojtsy|
|#161||2212069-161.patch||129.36 KB||Gábor Hojtsy|
PASSED: [[SimpleTest]]: [PHP 5.4 MySQL] 90,567 pass(es). View
|#157||2212069-157.patch||129.33 KB||Gábor Hojtsy|
PASSED: [[SimpleTest]]: [PHP 5.4 MySQL] 90,621 pass(es). View
|#151||interdiff.txt||1.57 KB||Gábor Hojtsy|
|#151||2212069-151.patch||129.32 KB||Gábor Hojtsy|
FAILED: [[SimpleTest]]: [PHP 5.4 MySQL] Unable to apply patch 2212069-151.patch. Unable to apply patch. See the log in the details link for more information. View