Hello,

I've seen the great work done by Leksat as part of this ticket: https://www.drupal.org/node/2322883
There is something that does not work as I was expecting with menu items though:

Here is my setup: I have 3 languages (en, fr, nl), the source language for i18n_string is EN, and here is how I configured fallbacks:

  • EN: FR, NL
  • FR: EN, NL
  • NL: FR, EN

This seems to work great for entities.
But here is what does not work as I would expect:
I created a menu with the i18n option "Translate and Localize. Menu items with language will allow translations. Menu items without language will be localized".
I added a "Language neutral" menu entry to it so it is localized only (so it is available in EN only at this moment)
I then added a NL translation to it.
Here is what I get in the "translate" tab for this menu item:

LANGUAGE            TITLE            STATUS                   OPERATIONS
Dutch               My item in NL    translated               translate
English (source)    My item in EN    original                 edit
French              My item in NL    fallback from Dutch      translate

I was expecting the French version to fallback from English, not from Dutch.
I looked at the code in language_fallback.i18n.inc and my understanding is that language_fallback_i18n_string_textgroup_default->load_translation(...) looks for translation candidates in the locales_target database table only.
In my opinion this is wrong as it overlooks the source translation (English in our case) which causes my example above to fail.
When I patch this code so it looks in the locales_source table too, it works as expected and falls back to English, not to Dutch.

This said, I'm questioning my overall understanding of all the mechanics behind language_fallback and i18n_string, hence my question:
Am I missing something?


Here is how I recoded load_translation to test my theory, just for info:

  public static function load_translation($i18nstring, $langcode) {
    $chain = language_fallback_get_chain($langcode);
    if (empty($chain)) {
      return parent::load_translation($i18nstring, $langcode);
    }
    $langcodes = array_values(array_merge(array($langcode), $chain));

    // Retrieve LID.
    $lid = NULL;
    if (empty($i18nstring->lid)) {
      $query = db_select('i18n_string', 's');
      $query->addField('s', 'lid');
      $query->condition('s.textgroup', $i18nstring->textgroup);
      $query->condition('s.context', $i18nstring->context);

      $lid = $query->execute()->fetchField();
    }
    else {
      $lid = $i18nstring->lid;
    }

    $translations = array();
    $source_lang_code = variable_get('i18n_string_source_language', 'en');
    // Get the source string.
    $query = db_select('locales_source', 'l');
    $query->fields('l', array('source'));
    $query->condition('l.lid', $lid);
    $source_lang_translation = $query->execute()->fetchAssoc();
    if (!empty($source_lang_translation)) {
      $translations[$source_lang_code] = (object) array(
        'language' => $source_lang_code,
        'translation' => $source_lang_translation['source'],
        'i18n_status' => I18N_STRING_STATUS_CURRENT,
      );
    }
    // Get all target translations.
    $query = db_select('locales_target', 'l');
    $query->fields('l', array('language', 'translation', 'i18n_status'));
    $query->condition('l.lid', $lid);
    // The i18n module can save empty strings as translations. In this case, it
    // behaves as there is no translation and uses the original strings.
    // Handle this case by simple excluding of empty translations.
    $query->condition('l.translation', '', '<>');
    $target_translations = $query->execute()->fetchAllAssoc('language', PDO::FETCH_ASSOC);
    foreach ($target_translations as $target_lang_code => $target_translation) {
      $translations[$target_lang_code] = (object) array(
        'language' => $target_lang_code,
        'translation' => $target_translation['translation'],
        'i18n_status' => $target_translation['i18n_status'],
      );
    }

    // Seek best candidate and return it.
    foreach ($langcodes as $langcode) {
      if (isset($translations[$langcode])) {
        return $translations[$langcode];
      }
    }

    // No match found, fail.
    return NULL;
  }

Disclaimer: If the issue is confirmed, this piece of code is not enough to fully fix the issue (it does not address the language_fallback_i18n_string_textgroup_default->multiple_translation_load(...) case, amongst other things...)

Comments

pacproduct created an issue.