It should be possible to filter the overview page and only display entities which are not yet translated in all languages or are outdated. Possibly even per language, but that wouldn't be that important.

Comments

LoyC’s picture

Yes, this would be very usefull for sites with large amount of content.
Or is there another way to avoid searching for untranslated content by checking all pages in content overview views?

blueminds’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new19.75 KB

"Not translated to" filter functionality provided. Just failed to make it work for the node source overview (in the view edit preview it works, for the real display not).

berdir’s picture

  1. +++ b/sources/entity/tmgmt_entity.module
    @@ -115,12 +115,34 @@ function tmgmt_entity_get_translatable_entities($entity_type, $property_conditio
    +
    +  // Join entity_translation table for each enabled language to be able to
    +  // search by the translation status.
    +  foreach ($languages as $langcode) {
    +    $langcode = str_replace('-', '', $langcode);
    +    $query->leftJoin('entity_translation', "et_$langcode", "%alias.entity_type = :entity_type AND %alias.entity_id = e.$id_key AND %alias.language = '$langcode'",
    +      array(':entity_type' => $entity_type));
    +  }
    +
    +  // Searching for sources with missing translation.
    +  if (!empty($property_conditions['missing_target_language']) && in_array($property_conditions['missing_target_language'], $languages)) {
    +    $or = db_or();
    +    $or->isNull("et_{$property_conditions['missing_target_language']}.language");
    +    $or->condition("et_{$property_conditions['missing_target_language']}.translate", 1);
    +    $query->condition($or);
    +    // Remove the condition so we do not try to add it again below.
    +    unset($property_conditions['missing_target_language']);
    +  }
    

    I think it would be more performant to only add the join for the specific language we need and only when we have a filter.

    Unlike locale/i18n_string, where we actually need this data, here we depend on the loaded data from the entity_load() anyway.

  2. +++ b/sources/entity/tmgmt_entity.ui.inc
    @@ -181,6 +181,19 @@ abstract class TMGMTEntityDefaultSourceUIController extends TMGMTDefaultSourceUI
    +      '#empty_option' => '--',
    

    empty is often t("- None - "), but that doesn't make sense here, nor does "Any". But just "--" is also not great.

    Maybe someone else has a better idea?

    What we basically want is "Do not filter" or so.

Also wondering why the test didn't fail?

blueminds’s picture

blueminds’s picture

-- vs None vs Any
I think the best is "Any translation status" or "Any status" but then the "Missing translation" label is off-topic.

berdir’s picture

  1. +++ b/sources/node/ui/tmgmt_node_ui.overview.test
    @@ -116,19 +116,18 @@ class TMGMTNodeSourceUIOverviewTestCase extends TMGMTEntityTestCaseUtility {
         $this->assertText($node_not_translated->title);
    -    $this->drupalPost(NULL, array('search[missing_target_language]' => 'de'), t('Search'));
    +    // Submitting the search form will not work.
    +    $this->drupalGet('admin/tmgmt/sources/node', array('query' => array('tmgmt_node_missing_translation' => 'de')));
    

    Why?

  2. +++ b/sources/node/ui/tmgmt_node_ui.overview.test
    @@ -116,19 +116,18 @@ class TMGMTNodeSourceUIOverviewTestCase extends TMGMTEntityTestCaseUtility {
    -    db_query('UPDATE {node} SET translate = 1 WHERE AND nid = :nid', array(':nid' => $node1->nid));
    -    $this->drupalPost(NULL, array('search[missing_target_language]' => 'de'), t('Search'));
    +    db_query('UPDATE {node} SET translate = 1 WHERE nid = :nid', array(':nid' => $node1->nid));
    +    $this->drupalGet('admin/tmgmt/sources/node', array('query' => array('tmgmt_node_missing_translation' => 'de')));
    

    You should use db_update(), same for other tests.

Just discussed the UI quite a bit with miro.

An alternative that we have been discussing is using two selects:
- Target language: Any//English/German/...
- Target status: Any/Untranslated or outdated/Untranslated/Outdated

While that is a bit more complicated at first, it is way more flexible:
- We can solve the none/any label problem more or less elegantly
- We have explicit control over the outdated flag
- We are open to adding more status options *later*, for example a "job item needs review"
- We also discussed that we could only display the selected language *later*, to be able to focus just on that.

Can you to implement that for a single source to try it out? Views is going to be a bit more complicated I guess, which leaves us with entity (the others don't have outdated).

blueminds’s picture

Status: Needs review » Needs work

The last submitted patch, 7: 1900910-filter_on_missing_translation-3.patch, failed testing.

blueminds’s picture

Status: Needs work » Needs review
StatusFileSize
new23.48 KB

Status: Needs review » Needs work

The last submitted patch, 9: 1900910-filter_on_missing_translation-4.patch, failed testing.

blueminds’s picture

Status: Needs work » Needs review
StatusFileSize
new23.48 KB
blueminds’s picture

Filters with status implemented for:
- entity
- i18n (we have there the i18n_status field added to the locales_target table)
- node - in this case it is ugly.

Here is how the views extension is done:
- value_form() is overidden to provide two selectboxes. One of them must have the same name as the field. The value of the second one is not passed into the query() method. Tried to send and array using #tree set to TRUE. It ended up with fatal error as it does not expect that.
- both elements show up as #states do not work there, probably custom jquery will need to be provided
- in the query() method the target_status is accessed directly via $_GET
Any ideas how to make the form part in a more standard fashion?

miro_dietiker’s picture

Thx! Some quick review here...

  1. +++ b/sources/entity/tmgmt_entity.module
    @@ -131,6 +131,10 @@ function tmgmt_entity_get_translatable_entities($entity_type, $property_conditio
    +    // Exclude entities with having source language same as the target language
    +    // we search for.
    +    $query->condition('e.language', $property_conditions['target_language'], '!=');
    +
    

    I guess this should only be added conditionally, if target_language is set.

  2. +++ b/sources/i18n_string/tmgmt_i18n_string.test
    @@ -20,7 +20,7 @@ class TMGMTI18nStringSourceTestCase extends TMGMTBaseTestCase {
    -  function testI18nStringSourceTaxonomy() {
    +  function NOtestI18nStringSourceTaxonomy() {
    

    Please retest including all tests.

  3. +++ b/sources/i18n_string/tmgmt_i18n_string.test
    @@ -254,18 +260,50 @@ class TMGMTI18nStringSourceTestCase extends TMGMTBaseTestCase {
    +      'search[target_language]' => 'de',
    +      'search[target_status]' => 'untranslated',
    

    I would stay with the same prefix:
    target_language and target_language_status

  4. +++ b/sources/node/views/handlers/tmgmt_node_handler_filter_missing_translation.inc
    @@ -29,15 +29,46 @@ class tmgmt_node_handler_filter_missing_translation extends views_handler_filter
    +    $this->query->add_where_expression($this->options['group'], "{$this->table_alias}.language != :language", array(':language' => $this->value));
     
    +    if ($target_status == 'untranslated_or_outdated') {
    +      $this->query->add_where_expression($this->options['group'], "($table_alias.nid IS NULL OR {$this->table_alias}.translate = 1)");
    +    }
    +    elseif ($target_status == 'outdated') {
    +      $this->query->add_where_expression($this->options['group'], "{$this->table_alias}.translate = 1");
    +    }
    +    elseif ($target_status == 'untranslated') {
    +      $this->query->add_where_expression($this->options['group'], "$table_alias.nid IS NULL");
    +    }
    

    Again, why not checking first for target_language and only adding the filter if present?

  5. +++ b/sources/node/views/handlers/tmgmt_node_handler_filter_missing_translation.inc
    @@ -29,15 +29,46 @@ class tmgmt_node_handler_filter_missing_translation extends views_handler_filter
    +    $options = array();
    +    foreach (language_list() as $langcode => $language) {
    +      $options[$langcode] = $language->name;
         }
    

    If a source language filter is present, those languages should not be available as target language. This possibly is a bit special as it's depending on a separately added filter.

    Do we properly deal with possible key change of the exposed filters?

blueminds’s picture

1. that is the case - see a few lines above
2. updated
3. the status relates to the target (the translation) not the language itself.
4. the query method will not be called unless the target language is set
5. yes. But that would require some JS. Do we want to provide such cool UX thingies? But we could at least provide validation that target/source language cannot be the same.

And no, there is no dealing with possible key change. Not sure how to do it. Basically I am not sure of anything when it comes to views :|

cgalli’s picture

I have tried the patch.

It seems to work as intended for content.

Remarks:

  • On the source page for 'Node' (i.e. field translated entities), the 'Missing Translator' filter does not show up
  • The filtering on the 'Locale' does still lead to the same result. Missing Translation english still lists all nodes with english source.
  • The two filters on the 'content' page (Missing and Status) are not lined up but appear stacked. Thats ugly.
  • The non-choice in the laguage dropdown should be 'none' instead of 'any'.
blueminds’s picture

- locale and i18n sources do not have en language in the target language/missing translation select box
- entity/node sources have the target_language/status boxes, and validation for source/target langs to not be the same
- node source should work as expected, but the way it is implemented is basically hacking views

berdir’s picture

Status: Needs review » Needs work
+++ b/sources/i18n_string/tmgmt_i18n_string.ui.inc
@@ -146,6 +146,10 @@ class TMGMTI18nStringDefaultSourceUIController extends TMGMTDefaultSourceUIContr
     );
+
+    // Unset English as it is the source language for all locale strings.
+    unset($options['en']);

The *source* language should be unset, not "en".

Might or might not be the same. This needs to be the same logic as in getSourceLangcode()

blueminds’s picture

Status: Needs work » Needs review
StatusFileSize
new31.96 KB
new788 bytes

fixed

berdir’s picture

Status: Needs review » Needs work

The view currently seems to have the same bug that when you go back to any language, it still uses the status and then doesn't find anything?

  1. +++ b/sources/entity/tmgmt_entity.module
    @@ -115,12 +115,45 @@ function tmgmt_entity_get_translatable_entities($entity_type, $property_conditio
    +    $query->leftJoin('entity_translation', "et_$langcode", "%alias.entity_type = :entity_type AND %alias.entity_id = e.$id_key AND %alias.language = '$langcode'",
    +        array(':entity_type' => $entity_type));
    +
    

    It's not possible that this could result in sql injection as we check that the language is valid, but it should still be passed in as an argument for the condition, we can't do anything about the alias. Well, what we could think about is using db_escape_field() instead of the custom str_replace() above?

  2. +++ b/sources/entity/tmgmt_entity.module
    @@ -115,12 +115,45 @@ function tmgmt_entity_get_translatable_entities($entity_type, $property_conditio
    +    $query->condition('e.language', $property_conditions['target_language'], '!=');
    

    The correct not condition for queries is <>, only MySQL understands !=

  3. +++ b/sources/i18n_string/tmgmt_i18n_string.test
    @@ -253,6 +259,45 @@ class TMGMTI18nStringSourceTestCase extends TMGMTBaseTestCase {
    +
    +    // Test the missing translation filter.
    +    $edit = array(
    +      'search[target_language]' => 'de',
    +      'search[target_status]' => 'untranslated',
    +    );
    

    A test to verify that the source language isn't in the select would be great, basically the same as assertNoOptionSelected(), except that we don't want to find such an option. so copy and simplify the assert to verify that it doesn't find the option.

  4. +++ b/sources/i18n_string/tmgmt_i18n_string.test
    @@ -253,6 +259,45 @@ class TMGMTI18nStringSourceTestCase extends TMGMTBaseTestCase {
    +    $this->assertNOText($vocab_data_not_translated['name']);
    

    NoText, not NOText :)

  5. +++ b/sources/node/views/handlers/tmgmt_node_handler_filter_missing_translation.inc
    @@ -0,0 +1,93 @@
    +
    +  function query() {
    ...
    +
    +  function value_form(&$form, &$form_state) {
    

    Let's add some @inheritdocs. Just because views doesn't do it shouldn't mean that we don't :)

  6. +++ b/sources/node/views/handlers/tmgmt_node_handler_filter_missing_translation.inc
    @@ -0,0 +1,93 @@
    +    $identifier = $this->options['expose']['identifier'];
    +    // @todo - is this reliable? Especially the number "2" at the end.
    +    $target_language_id = 'edit-' . str_replace('_', '-', $identifier) . '--2';
    

    no, it's not really...

    I would suggest you give it your own, hardcoded #id and then use that. That default id is just added if there's none yet. The --2 is added by drupal_html_id(), which ensures unique ID's, also on through ajax requests, but it's sometimes weird, like all forms having --2 by default.

  7. +++ b/sources/node/views/handlers/tmgmt_node_handler_filter_missing_translation.inc
    @@ -0,0 +1,93 @@
    +  // @todo - this does not get triggered.
    +  function value_validate($form, &$form_state) {
    +    $identifier = $this->options['expose']['identifier'];
    +    debug($form_state['values']);
    +    if ($form_state['values'][$identifier] == $form_state['values']['target_status']) {
    +      form_set_error($identifier, t('The source and target languages cannot be the same.'));
    +    }
    

    You want expose_validate() I think, value_validate() is apparently only used in the backend.

  8. +++ b/sources/node/views/handlers/tmgmt_node_handler_filter_missing_translation.inc
    @@ -0,0 +1,93 @@
    +
    +  protected function get_target_status() {
    +    return isset($_GET['target_status']) ? $_GET['target_status'] : 'untranslated_or_outdated';
    +  }
    

    I'll check with @dawehner if there's an example for a filter with multiple form elements.

cgalli’s picture

Applied and tested. Works as advertized.

The long list of filters makes the break ugly in smaller settings. See #2210083: Language filters break badly around color legends

In addition, the long list makes the help text obsolete on the source overview page. There is no space left!

berdir’s picture

What about this for the views filter? This also fixes the reset problem mentioned above.

blueminds’s picture

Status: Needs work » Needs review
StatusFileSize
new32.89 KB
new8.99 KB

please see the patch. it also includes changes from #21

Status: Needs review » Needs work

The last submitted patch, 22: 1900910-filter_on_missing_translation-10.patch, failed testing.

blueminds’s picture

Status: Needs work » Needs review
StatusFileSize
new1.08 KB
new32.89 KB

uff, that was nice one...

berdir’s picture

Title: Filter for translated/untranslated/outdated content » Filter for untranslated/outdated content to source overviews.
Component: Source: Entity » User interface
Status: Needs review » Fixed

Ok, committed and pushed!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.