You are here

localize_fields.drush.inc in Localize Fields 7

Drupal Localize Fields module

File

localize_fields.drush.inc
View source
<?php

/**
 * @file
 *  Drupal Localize Fields module
 */

/**
 * Implementation of hook_drush_command().
 *
 * @return array
 */
function localize_fields_drush_command() {
  $items = array(
    'localize-fields' => array(
      'description' => 'Makes all labels etc. of Features modules\' field and field_group files below arg path translatable, optionally using translation contexts to prevent translation mix-ups.',
      'arguments' => array(
        'path' => 'Path to ancestor directory of the Features modules\' files to convert.',
      ),
      'options' => array(
        'test' => 'Features modules\' files won\'t be overwritten but instead copied to private files dir, and database changes will be simulated.',
        'usecontext' => 'Override the site\'s \'Use translation context\' settings (check /admin/config/regional/localize_fields).',
        'removeempty' => 'Remove translation sources having same source text as a field (but no translation) when there actually exists a non-empty translation. Risky if some translation deliberately is empty.',
      ),
      'examples' => array(
        'drush localize-fields sites/all/modules/custom --test --usecontext --removeempty' => ' ',
      ),
      'aliases' => array(),
    ),
  );

  // Make this function usable in non-drush context.
  if (defined('DRUSH_BOOTSTRAP_DRUPAL_FULL')) {
    $items['localize-fields']['bootstrap'] = DRUSH_BOOTSTRAP_DRUPAL_FULL;
  }
  return $items;
}

/**
 * @param string $path
 */
function drush_localize_fields($path = '') {
  $success = TRUE;
  $overwrite = FALSE;
  drush_print(' ');

  // Get context delimiter.
  if (!class_exists('LocalizeFields')) {
    if (!file_exists('LocalizeFields.inc')) {
      drush_log(dt('Can\'t find class !class nor file !file.', array(
        '!class' => 'LocalizeFields',
        '!file' => 'LocalizeFields.inc',
      )), 'error');
      return;
    }
    require 'LocalizeFields.inc';
    if (!class_exists('LocalizeFields')) {
      drush_log(dt('Can\'t find file !file.', array(
        '!file' => 'LocalizeFields.inc',
      )), 'error');
      return;
    }
  }
  $cntxtDelim = LocalizeFields::CONTEXT_DELIMITER;
  if (!module_exists('locale')) {
    drush_log(dt('The locale module must be installed.'), 'error');
    return;
  }
  $test = drush_get_option('test', FALSE);
  if ($test) {
    drush_print(dt('In test mode') . ':', 2);
    drush_print(dt('- converted copies of the Features files will be written to private://drush/localize_fields'), 2);
    drush_print(dt('- all database changes are simulations') . "\n", 2);
  }
  else {
    $overwrite = TRUE;
  }
  $output_dir = '';
  if (!$overwrite) {

    // Set up output dir, if it doesnt exist.
    if (!drupal_strlen($output_dir = variable_get('file_private_path', ''))) {
      drush_log(dt('Private files directory not defined, check variable file_private_path, or in GUI: /admin/config/media/file-system'), 'error');
      return;
    }
    if (!is_dir($output_dir .= '/drush/localize_fields') && !mkdir($output_dir, 0775, TRUE)) {
      drush_log(dt('Output dir [!dir] doesnt exist and cannot be created.', array(
        '!dir' => $output_dir,
      )), 'error');
      return;
    }
  }

  // Establish and check path to find Features files in.
  $dir = $path;
  $document_root = DRUPAL_ROOT;
  $cli_working_dir = !empty($_SERVER['PWD']) ? $_SERVER['PWD'] : '';
  if (!($os_nix = DIRECTORY_SEPARATOR == '/')) {

    // Replace Windows directory separators.
    $dir = str_replace('\\', '/', $dir);
    $document_root = str_replace('\\', '/', $document_root);
    $cli_working_dir = str_replace('\\', '/', $cli_working_dir);
  }

  // Remove trailing slash.
  if ($dir[strlen($dir) - 1] == '/') {
    $dir = substr($dir, 0, strlen($dir) - 1);
  }

  // Absolute?
  if ($dir[0] === '/' || !$os_nix && preg_match('/^[a-zA-Z]:\\//', $dir)) {
    if (!file_exists($dir)) {
      drush_log(dt('Bad path argument, that absolute dir doesnt exist - saw path[' . $dir . '].'), 'error');
      return;
    }
  }
  else {
    if (!($resolved = realpath($cli_working_dir . '/' . $dir))) {
      drush_log(dt('Bad path argument, that relative dir doesnt exist - saw path[' . $dir . '].'), 'error');
      return;
    }
    $dir = $os_nix ? $resolved : str_replace('\\', '/', $resolved);
  }

  // Working outside current site's document root is forbidden.
  if (stripos($dir, $document_root . '/') !== 0) {
    drush_log(dt('This script is not allowed to work outside current site\'s document root - saw path[' . $dir . '].'), 'error');
    return;
  }
  if (!is_dir($dir)) {
    drush_log(dt('Bad path argument, that file isnt a directory - saw path[' . $dir . '].'), 'error');
    return;
  }
  drush_log(dt('Will work in dir and sub dirs of: !dir', array(
    '!dir' => $dir,
  )), 'status');
  $use_context = drush_get_option('usecontext', 'nully');
  $remove_empty = FALSE;
  if ($use_context === 'nully') {
    $use_context = variable_get('localize_fields_usecontext', LocalizeFields::USE_CONTEXT_NONCONTEXT);
  }
  if ($use_context) {
    drush_log(dt('Will use translation context - t()s will get context argument.'), 'status');
  }
  else {
    drush_log(dt('Will NOT use translation context. You may override by using the --usecontext option.'), 'warning');
    if (!($remove_empty = drush_get_option('removeempty', 0))) {
      drush_log(dt('And will NOT remove possibly overshadowing sources that equals fields\' sources, but have no translations. You may override by using the --removeempty option.'), 'status');
    }
    else {
      drush_log(dt('However - will remove possibly overshadowing sources that equals fields\' sources, but have no translations.'), 'status');
    }
    if (!drush_confirm(dt('Do you really want to continue, without translation context?'))) {
      drush_log(dt('Aborting.'));
    }
  }

  // Escaping.
  $escape_needles = array(
    "\n",
    "'",
  );
  $escape_substitutes = array(
    '\\n',
    '\\\'',
  );

  // Find all the Features files under path.
  // Details:
  // - skip .features.inc (the content type file): Features automatically insert t()s directly around the sentences, and it translation works.
  // - .field_group.inc: Find label and description, and add them t()'d to bottom of file (we don't translate field_group).
  // - .features.field_base.inc: Find list_ type options, and add them t()'d to bottom of file.
  // - .features.field_instance.inc: Find number_ type prefix/suffix, and add them t()'d to bottom of file.
  //  Find all the features files under path.
  $list = array();
  _localize_fields_list_files($list, $dir);
  if (!$list) {
    drush_log(dt('Aborted: Found no .features.field_base.inc, .features.field_instance.inc, .field_group.inc, .features.field_group.inc files.'), 'warning');
    return;
  }
  drush_print(' ');
  drush_print('Will update database tables locales_source and locales_target.');
  drush_print('A list of database changes will be logged to watchdog.');
  drush_print(' ');

  // List files for confirmation.
  $filenames = array_keys($list);
  $confirm_list = array();
  $current_module = '_impossible_.';
  $index_modules = -1;
  foreach ($filenames as $filename) {
    if (strpos($filename, $current_module) !== 0) {
      $current_module = substr($filename, 0, strpos($filename, '.')) . '.';
      $confirm_list[++$index_modules] = $filename;
    }
    else {
      $confirm_list[$index_modules] .= ' ..' . substr($filename, strpos($filename, '.'));
    }
  }
  unset($filenames);
  drush_print(dt('Will work on these files:'));
  foreach ($confirm_list as $filenames) {
    drush_print($filenames, 2);
  }
  drush_print(' ');
  if (!$test) {
    if (!drush_confirm(dt('Do you really want to continue?'))) {
      drush_log(dt('Aborting.'));
      return;
    }
    drush_print(' ');
  }
  else {
    drush_print(dt('Do you really want to continue?'));
    drush_print(dt('- Aye, it\'s just a test...' . "\n"));
  }

  // Use db.locales_target.l10n_status column if it exists.
  $column_l10n_status = module_exists('features_translations');
  $no_work_files = array();
  $identical_files = array();
  $work_files = array();
  $files_completed = array();
  $inspections = array();
  try {
    foreach ($list as $filename => $path) {

      // Find type.
      if (strpos($filename, '.field_base.inc')) {
        $type = 'field_base';
      }
      elseif (strpos($filename, '.field_instance.inc')) {
        $type = 'field_instance';
      }
      elseif (strpos($filename, '.field_group.inc')) {
        $type = 'field_group';
      }
      else {
        drush_log(dt('Unsupported Features file[' . $path . '].'), 'error');
        break;
      }

      // Read file and get rid of carriage returns.
      $original = file_get_contents($path);
      $output = str_replace("\r", '', $original);

      // Find first function.
      $matches = array();
      if (!preg_match('/\\nfunction\\ ?([a-z\\d\\_]+)[\\ \\(]/', $output, $matches) || empty($matches[1])) {
        drush_log(dt('Cant find any function in file[' . $path . '].'), 'error');
        return;
      }
      $function = $matches[1];

      // Find sources.
      switch ($type) {
        case 'field_base':
          $sources = _localize_fields_type_field_base($path, $function);
          break;
        case 'field_instance':
          $sources = _localize_fields_type_field_instance($path, $function);
          break;
        case 'field_group':
          $sources = _localize_fields_type_field_group($path, $function);
          break;
        default:
          drush_log(dt('Unsupported Features file type[' . $type . '].'), 'error');
          return;
      }
      if ($sources) {
        $ts = array();
        $sources_distinct = array();
        $sources_to_db = array();
        foreach ($sources as $props) {

          // NB: field_group items have no context.
          $context = $props['context'];
          foreach ($props['sources'] as $key => $src) {
            if (is_array($src)) {

              // List options.
              foreach ($src as $item) {

                // We don't translate integers, whereas decimals may have to be translated because of the decimal separator.
                if ($item && !ctype_digit('' . $item)) {
                  $source = str_replace($escape_needles, $escape_substitutes, $item);

                  // Don't update in db if no context at all (field_group).
                  if ($context) {
                    $sources_distinct[] = $source;
                    $sources_to_db[] = array(
                      $source,
                      !$context ? '' : $context . $cntxtDelim . $key,
                    );
                  }
                  $ts[] = 't' . "('" . $source . "'" . (!$use_context || !$context ? '' : ", array(), array('context' => '" . $context . $cntxtDelim . $key . "')") . ');';
                }
              }
            }
            elseif ($src && !ctype_digit('' . $src)) {
              $source = str_replace($escape_needles, $escape_substitutes, $src);

              // Don't update in db if no context at all (field_group).
              if ($context) {
                $sources_distinct[] = $source;
                $sources_to_db[] = array(
                  $source,
                  !$context ? '' : $context . $cntxtDelim . $key,
                );
              }
              $ts[] = 't' . "('" . $source . "'" . (!$use_context || !$context ? '' : ", array(), array('context' => '" . $context . $cntxtDelim . $key . "')") . ');';
            }
          }
        }

        // DB operations.
        // Not using context: remove context, if/when matching sources with matching context exist.
        // Using context: clone a source + target and add context to source (and user must clean up afterwards using the Potx module).
        // Location is unreliable, so we don't search by that.
        if ($sources_to_db) {
          $existing_sources = db_select('locales_source', 'src')
            ->fields('src', array(
            'lid',
            'location',
            'source',
            'context',
          ))
            ->condition('textgroup', 'default')
            ->condition('source', $sources_distinct, 'IN')
            ->orderBy('source')
            ->orderBy('context')
            ->execute()
            ->fetchAllAssoc('lid');
          if ($existing_sources) {
            $inspections[] = 'Sources exist in database for file: ' . $filename . '.';
            $existing_source_ids = array_keys($existing_sources);

            // Using context, or remove possibly overshadowing empty sources: we need the targets too.
            $existing_targets = array();

            // IDE.
            if ($use_context || $remove_empty) {
              $existing_targets = db_select('locales_target', 'trg')
                ->fields('trg', array(
                'lid',
                'translation',
                'language',
              ))
                ->condition('lid', $existing_source_ids, 'IN')
                ->condition('plural', 0)
                ->execute()
                ->fetchAll();
              if ($existing_targets) {
                $inspections[] = '...and targets exist.';
              }
            }

            // Make a copy of db sources that is searchable by source.
            $existing_sources_search = array();
            foreach ($existing_sources as $source_id => $props) {
              $existing_sources_search[$source_id] = $props->source;
            }
            foreach ($sources_to_db as $src_cntxt) {
              $source = $src_cntxt[0];
              $context = $src_cntxt[1];

              // There may be more db sources matching a source, if context was used previously.
              $matching_source_ids = array();
              $match_found = FALSE;
              if ($source_id = array_search($source, $existing_sources_search, TRUE)) {

                // Find all db sources that match source.
                $copy_sources_search = $existing_sources_search;
                $matching_source_ids[] = $source_id;
                unset($copy_sources_search[$source_id]);
                while ($source_id = array_search($source, $copy_sources_search, TRUE)) {
                  $matching_source_ids[] = $source_id;
                  unset($copy_sources_search[$source_id]);
                }
                foreach ($matching_source_ids as $source_id) {
                  if ($existing_sources[$source_id]->context === $context) {
                    $match_found = $source_id;

                    // Save it for !$use_context && $remove_empty.
                    if (!$use_context) {

                      // Remove context.
                      if (!$test) {
                        db_update('locales_source')
                          ->fields(array(
                          'context' => '',
                        ))
                          ->condition('lid', $source_id, '=')
                          ->execute();
                      }
                      $inspections[] = '- updated (cleared) context of db source: context [' . $context . '], source >>' . $source . '<<.';
                    }
                    else {

                      // Use context: found a match, no more work to do, continue to next source.
                      continue 2;
                    }
                    break;
                  }
                }

                // Get rid of overshadowing empty translations.
                if ($remove_empty) {
                  foreach ($matching_source_ids as $source_id) {
                    if ((!$match_found || $source_id != $match_found) && $existing_sources[$source_id]->context === '' && $existing_sources[$source_id]->location !== '' && $existing_sources[$source_id]->location[0] == '/') {
                      $removable_source = TRUE;
                      foreach ($existing_targets as $props) {

                        // A translation exists, thus we can't remove source.
                        if ($props->lid == $source_id && $props->translation !== '') {
                          $removable_source = FALSE;
                          break;
                        }
                      }
                      if ($removable_source) {
                        if (!$test) {
                          db_delete('locales_source')
                            ->condition('lid', $source_id, '=')
                            ->execute();
                        }
                        $inspections[] = '- deleted possibly overshadowing (no translation) for db source: context [' . $context . '], source >>' . $source . '<<.';
                      }

                      // There may actually be more than one (sic!).
                    }
                  }
                }
                elseif (!$match_found && $use_context) {

                  // Find first having same location (file only, disregarding line number), and having only one location.
                  foreach ($matching_source_ids as $source_id) {
                    if ($existing_sources[$source_id]->context === '' && strpos($existing_sources[$source_id]->location, $filename . ':') === 0 && strpos($existing_sources[$source_id]->location, ' ') === FALSE) {
                      $match_found = TRUE;

                      // Set context.
                      if (!$test) {
                        db_update('locales_source')
                          ->fields(array(
                          'context' => $context,
                        ))
                          ->condition('lid', $source_id, '=')
                          ->execute();
                      }
                      $inspections[] = '- updated (set) context of db source: context [' . $context . '], source >>' . $source . '<<.';

                      // We only do this once.
                      break;
                    }
                  }
                  if (!$match_found) {

                    // Find the first having a translation, and preferring empty context (see $existing_targets orderBy('context') ascending).
                    foreach ($matching_source_ids as $source_id) {

                      // Check if there's any targets at all.
                      $any_matching_targets = FALSE;
                      foreach ($existing_targets as $props) {
                        if ($props->lid == $source_id && $props->translation !== '') {
                          $any_matching_targets = TRUE;
                          break;
                        }
                      }
                      if ($any_matching_targets) {

                        // Insert dupe source, that has context.
                        $new_source_id = '';

                        // IDE.
                        if (!$test) {
                          $new_source_id = db_insert('locales_source')
                            ->fields(array(
                            'location' => $filename . ':0',
                            'source' => $source,
                            'context' => $context,
                          ))
                            ->execute();
                        }
                        $inspections[] = '- new source: context [' . $context . '], source >>' . $source . '<<.';

                        // Insert dupe targets, for every language
                        foreach ($existing_targets as $props) {
                          if ($props->lid == $source_id && $props->translation !== '') {
                            if (!$test) {
                              $fields = array(
                                'lid' => $new_source_id,
                                'translation' => $props->translation,
                                'language' => $props->language,
                              );
                              if ($column_l10n_status) {
                                $fields['l10n_status'] = 1;
                              }
                              db_insert('locales_target')
                                ->fields($fields)
                                ->execute();
                            }
                            $inspections[] = '- new (cloned) target: language [' . $props->language . '], source >>' . $props->translation . '<<.';
                          }
                        }
                        break;
                      }
                    }
                  }
                }
              }
            }
          }
          else {

            // Do nothing.
            $inspections[] = 'No sources exist in database for file: ' . $filename . '.';
          }
        }

        // Filing.
        if ($ts) {
          $comment = '// Translatables created/updated by the Localize Fields (localize_fields) module.';
          array_unshift($ts, $comment);

          // Remove this module's comment.
          $needle = '/\\n\\n?\\ *' . preg_quote($comment, '/') . '/';

          // Double? \n fixes that our lines tend to move down one line for every file write.
          if (preg_match($needle, $output)) {
            $output = preg_replace($needle, '', $output);
          }

          // Remove existing t()'s at end of file.
          $needle = '/\\n\\ *t\\(\'.+\'\\)?\\);/ms';

          // Multiline and lazy.
          if (preg_match($needle, $output)) {
            $output = preg_replace($needle, '', $output);
          }

          // Find end of file.
          $needle = '/(\\n\\ *)return \\$([a-zA-Z\\d_]+);\\ *\\n\\n?\\}\\ *\\n?/';
          if (!preg_match($needle, $output)) {
            drush_log(dt('Can\'t find return/end of file in file[' . $path . '].'), 'error');
            continue;

            // Skip.
          }
          else {
            $output = preg_replace($needle, '  ' . join("\n  ", $ts) . "\n\n" . '$1return \\$$2;' . "\n}\n", $output);
          }

          // No actual changes?
          if (strlen($output) == strlen($original) && $output == $original) {
            $identical_files[] = $filename;
          }
          else {
            $work_files[] = $filename;
            if (!$overwrite) {
              file_put_contents($output_dir . '/' . $filename, $output);
            }
            else {
              file_put_contents($path, $output);
            }
          }
        }
        else {
          $no_work_files[] = $filename;
        }
      }
      $files_completed[] = $filename;
    }
  } catch (Exception $xc) {
    $success = FALSE;
    watchdog('localize_fields', 'Extraction of localization sources failed: @error_message', array(
      '@error_message' => $xc
        ->getMessage(),
    ), WATCHDOG_ERROR);
    drush_log(dt('Failed, error: @error', array(
      '@error' => $xc
        ->getMessage(),
    )), 'error');
  }
  if ($inspections) {
    $nl = module_exists('dblog') ? '<br>' : ' ';
    watchdog('localize_fields', 'Extraction of localization sources, db operations:' . $nl . join($nl, $inspections), array(), WATCHDOG_INFO);
  }
  if ($no_work_files) {
    drush_print(dt('These !n files contained no translatable labels:!files', array(
      '!n' => count($no_work_files),
      '!files' => "\n  " . join("\n  ", $no_work_files),
    )) . "\n");
  }
  if ($identical_files) {
    drush_print(dt('These !n files needed no file changes, but may have resulted in database changes (check log):!files', array(
      '!n' => count($identical_files),
      '!files' => "\n  " . join("\n  ", $identical_files),
    )) . "\n");
  }
  if ($work_files) {
    if (!$overwrite) {
      drush_print(dt('These !n files got t()ed in copies filed to private files sub dir [!dir]:!files', array(
        '!n' => count($work_files),
        '!files' => "\n  " . join("\n  ", $work_files),
        '!dir' => count($output_dir),
      )) . "\n");
    }
    else {
      drush_print(dt('These !n files got t()ed and overwritten!files', array(
        '!n' => count($work_files),
        '!files' => "\n  " . join("\n  ", $work_files),
      )) . "\n");
    }
  }
  if (!$success) {
    $files_remaining = array_diff(array_keys($list), $files_completed);
    drush_log(dt('These !n files were not completed:!files', array(
      '!n' => count($files_remaining),
      '!files' => "\n  " . join("\n  ", $files_remaining),
    )), 'warning');
  }
  if ($success) {
    if (!$test) {
      cache_clear_all('locale:', 'cache', TRUE);
    }
    drush_log(dt('Cleared translation cache for all languages.'), 'ok');
  }
  drush_log(dt('!testFeatures files translation extraction !succeeded.', array(
    '!test' => !$test ? '' : '(test) ',
    '!succeeded' => $success ? 'succeeded' : 'failed',
  )), $success ? 'success' : 'failed');
  if (!module_exists('localize_fields')) {
    drush_log(dt('Field label translation will only work if the localize_fields module is enabled.'), 'warning');
  }
}

/**
 * Finds names and paths (path/filename) of all relevant Features files in a directory, recursively.
 *
 *  Details:
 *  - skip .features.inc (content type file): Features automatically insert t()s directly around the sentences, and core translation works.
 *  - (.features)?.field_group.inc: Find label and description, and add them t()'d (without context) to bottom of file (not translated by FLF; core translation works).
 *  - .features.field_base.inc: Find list_ type options, and add them t()'d to bottom of file.
 *  - .features.field_instance.inc: Find number_ type prefix/suffix, and add them t()'d to bottom of file.
 *
 * @param array   &$list
 * @param string  $dir
 * @param integer $depth_max
 *  - default: 5
 * @param integer $depth
 *  - default: zero
 *
 * @return void
 */
function _localize_fields_list_files(&$list, $dir, $depth_max = 5, $depth = 0) {
  if (++$depth > $depth_max) {
    return;
  }
  $le = count($sub_list = scandir($dir));
  for ($i = 0; $i < $le; $i++) {
    $item = $sub_list[$i];
    if ($item != '.' && $item != '..') {
      if (is_dir($dir . '/' . $item)) {
        _localize_fields_list_files($list, $dir . '/' . $item, $depth_max, $depth);
      }
      elseif (preg_match('/^.+\\.(features\\.field_base|features\\.field_instance|field_group|features\\.field_group)\\.inc$/', $item)) {
        $list[$item] = $dir . '/' . $item;
      }
    }
  }
}

/**
 * Finds translatables of a field_base file.
 *
 * @param $path_file
 * @param $function
 * @return array|boolean
 */
function _localize_fields_type_field_base($path_file, $function) {
  require $path_file;
  if (!function_exists($function)) {
    drush_log(dt('Failed to include file[!path_file], or function[!function] doesnt exist.', array(
      '!path_file' => $path_file,
      '!function' => $function,
    )), 'error');
    return FALSE;
  }
  $cntxtDelim = LocalizeFields::CONTEXT_DELIMITER;
  $field_bases = $function();
  $sources = array();
  foreach ($field_bases as $field_name => $props) {
    if ($props['module'] == 'list') {

      // The field settings flag 'allowed_values_no_localization' ought not to collide with other,
      // and field _value_ translation is flagged with 'translatable' (in field settings' root).
      if (empty($props['settings']['allowed_values_no_localization'])) {
        $sources[$field_name] = array(
          'context' => 'field' . $cntxtDelim . $field_name,
          'sources' => array(
            'allowed_values' => $props['settings']['allowed_values'],
          ),
        );
      }
      else {
        drush_log('Skipped extracting allowed_values of field [' . $field_name . '] because has \'allowed_values_no_localization\' flag.', 'status');
      }
    }
    else {

      // 'Add another item' button label, if multi-cardinal.
      if (!empty($props['cardinality']) && $props['cardinality'] != 1) {
        $sources[$field_name] = array(
          'context' => 'field' . $cntxtDelim . $field_name,
          'sources' => array(
            'add_row' => !empty($props['settings']['add_row_localization_source']) ? $props['settings']['add_row_localization_source'] : variable_get('localize_fields_source_add_row', 'Add another item'),
          ),
        );
      }
    }
  }
  return $sources;
}

/**
 * Finds translatables of a field_instance file.
 *
 * @param $path_file
 * @param $function
 * @return array|boolean
 */
function _localize_fields_type_field_instance($path_file, $function) {
  require $path_file;
  if (!function_exists($function)) {
    drush_log(dt('Failed to include file[!path_file], or function[!function] doesnt exist.', array(
      '!path_file' => $path_file,
      '!function' => $function,
    )), 'error');
    return FALSE;
  }
  $cntxtDelim = LocalizeFields::CONTEXT_DELIMITER;
  $cntxtDelimBundle = LocalizeFields::CONTEXT_DELIMITER_BUNDLE;
  $field_instances = $function();
  $sources = array();
  foreach ($field_instances as $key_path => $props) {
    $sources[$key_path] = array(
      'context' => 'field_instance' . $cntxtDelim . $props['bundle'] . $cntxtDelimBundle . $props['field_name'],
      'sources' => array(
        'label' => $props['label'],
        'description' => $props['description'],
      ),
    );
    if (!empty($props['settings'])) {
      if (array_key_exists('prefix', $props['settings'])) {
        $sources[$key_path]['sources']['prefix'] = $props['settings']['prefix'];
      }
      if (array_key_exists('suffix', $props['settings'])) {
        $sources[$key_path]['sources']['suffix'] = $props['settings']['suffix'];
      }
    }
  }
  return $sources;
}

/**
 * Finds translatables of a field_group file.
 *
 * @param $path_file
 * @param $function
 * @return array|boolean
 */
function _localize_fields_type_field_group($path_file, $function) {
  require $path_file;
  if (!function_exists($function)) {
    drush_log(dt('Failed to include file[!path_file], or function[!function] doesnt exist.', array(
      '!path_file' => $path_file,
      '!function' => $function,
    )), 'error');
    return FALSE;
  }
  $field_group = $function();
  $sources = array();
  foreach ($field_group as $key_path => $props) {
    $sources[$key_path] = array(
      'context' => '',
      // Don't use context, because won't be translated by this module.
      'sources' => array(
        'label' => $props->data['label'],
      ),
    );
    if (!empty($props->data['format_settings']['instance_settings'])) {
      if (array_key_exists('description', $props->data['format_settings']['instance_settings'])) {
        $sources[$key_path]['sources']['description'] = $props->data['format_settings']['instance_settings']['description'];
      }
    }
  }
  return $sources;
}

Functions

Namesort descending Description
drush_localize_fields
localize_fields_drush_command Implementation of hook_drush_command().
_localize_fields_list_files Finds names and paths (path/filename) of all relevant Features files in a directory, recursively.
_localize_fields_type_field_base Finds translatables of a field_base file.
_localize_fields_type_field_group Finds translatables of a field_group file.
_localize_fields_type_field_instance Finds translatables of a field_instance file.