Chris@0: [], Chris@0: 'customized' => LOCALE_NOT_CUSTOMIZED, Chris@0: 'finish_feedback' => TRUE, Chris@0: ]; Chris@0: Chris@0: if (!empty($options['langcode'])) { Chris@0: $langcodes = [$options['langcode']]; Chris@0: } Chris@0: else { Chris@0: // If langcode was not provided, make sure to only import files for the Chris@0: // languages we have added. Chris@0: $langcodes = array_keys(\Drupal::languageManager()->getLanguages()); Chris@0: } Chris@0: Chris@0: $files = locale_translate_get_interface_translation_files([], $langcodes); Chris@0: Chris@0: if (!$force) { Chris@18: $result = \Drupal::database()->select('locale_file', 'lf') Chris@0: ->fields('lf', ['langcode', 'uri', 'timestamp']) Chris@0: ->condition('langcode', $langcodes) Chris@0: ->execute() Chris@0: ->fetchAllAssoc('uri'); Chris@0: foreach ($result as $uri => $info) { Chris@0: if (isset($files[$uri]) && filemtime($uri) <= $info->timestamp) { Chris@0: // The file is already imported and not changed since the last import. Chris@0: // Remove it from file list and don't import it again. Chris@0: unset($files[$uri]); Chris@0: } Chris@0: } Chris@0: } Chris@0: return locale_translate_batch_build($files, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Get interface translation files present in the translations directory. Chris@0: * Chris@0: * @param array $projects Chris@0: * (optional) Project names from which to get the translation files and Chris@0: * history. Defaults to all projects. Chris@0: * @param array $langcodes Chris@0: * (optional) Language codes from which to get the translation files and Chris@0: * history. Defaults to all languages. Chris@0: * Chris@0: * @return array Chris@0: * An array of interface translation files keyed by their URI. Chris@0: */ Chris@0: function locale_translate_get_interface_translation_files(array $projects = [], array $langcodes = []) { Chris@0: module_load_include('compare.inc', 'locale'); Chris@0: $files = []; Chris@0: $projects = $projects ? $projects : array_keys(locale_translation_get_projects()); Chris@0: $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); Chris@0: Chris@0: // Scan the translations directory for files matching a name pattern Chris@0: // containing a project name and language code: {project}.{langcode}.po or Chris@0: // {project}-{version}.{langcode}.po. Chris@0: // Only files of known projects and languages will be returned. Chris@0: $directory = \Drupal::config('locale.settings')->get('translation.path'); Chris@0: $result = file_scan_directory($directory, '![a-z_]+(\-[0-9a-z\.\-\+]+|)\.[^\./]+\.po$!', ['recurse' => FALSE]); Chris@0: Chris@0: foreach ($result as $file) { Chris@0: // Update the file object with project name and version from the file name. Chris@0: $file = locale_translate_file_attach_properties($file); Chris@0: if (in_array($file->project, $projects)) { Chris@0: if (in_array($file->langcode, $langcodes)) { Chris@0: $files[$file->uri] = $file; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: return $files; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Build a locale batch from an array of files. Chris@0: * Chris@0: * @param array $files Chris@0: * Array of file objects to import. Chris@0: * @param array $options Chris@0: * An array with options that can have the following elements: Chris@0: * - 'langcode': The language code. Optional, defaults to NULL, which means Chris@0: * that the language will be detected from the name of the files. Chris@0: * - 'overwrite_options': Overwrite options array as defined in Chris@0: * Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array. Chris@0: * - 'customized': Flag indicating whether the strings imported from $file Chris@0: * are customized translations or come from a community source. Use Chris@0: * LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to Chris@0: * LOCALE_NOT_CUSTOMIZED. Chris@0: * - 'finish_feedback': Whether or not to give feedback to the user when the Chris@0: * batch is finished. Optional, defaults to TRUE. Chris@0: * Chris@0: * @return array|bool Chris@0: * A batch structure or FALSE if $files was empty. Chris@0: */ Chris@0: function locale_translate_batch_build(array $files, array $options) { Chris@0: $options += [ Chris@0: 'overwrite_options' => [], Chris@0: 'customized' => LOCALE_NOT_CUSTOMIZED, Chris@0: 'finish_feedback' => TRUE, Chris@0: ]; Chris@0: if (count($files)) { Chris@0: $operations = []; Chris@0: foreach ($files as $file) { Chris@0: // We call locale_translate_batch_import for every batch operation. Chris@0: $operations[] = ['locale_translate_batch_import', [$file, $options]]; Chris@0: } Chris@0: // Save the translation status of all files. Chris@0: $operations[] = ['locale_translate_batch_import_save', []]; Chris@0: Chris@0: // Add a final step to refresh JavaScript and configuration strings. Chris@0: $operations[] = ['locale_translate_batch_refresh', []]; Chris@0: Chris@0: $batch = [ Chris@0: 'operations' => $operations, Chris@0: 'title' => t('Importing interface translations'), Chris@0: 'progress_message' => '', Chris@0: 'error_message' => t('Error importing interface translations'), Chris@0: 'file' => drupal_get_path('module', 'locale') . '/locale.bulk.inc', Chris@0: ]; Chris@0: if ($options['finish_feedback']) { Chris@0: $batch['finished'] = 'locale_translate_batch_finished'; Chris@0: } Chris@0: return $batch; Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements callback_batch_operation(). Chris@0: * Chris@0: * Perform interface translation import. Chris@0: * Chris@0: * @param object $file Chris@0: * A file object of the gettext file to be imported. The file object must Chris@0: * contain a language parameter (other than Chris@0: * LanguageInterface::LANGCODE_NOT_SPECIFIED). This is used as the language of Chris@0: * the import. Chris@0: * @param array $options Chris@0: * An array with options that can have the following elements: Chris@0: * - 'langcode': The language code. Chris@0: * - 'overwrite_options': Overwrite options array as defined in Chris@0: * Drupal\locale\PoDatabaseWriter. Optional, defaults to an empty array. Chris@0: * - 'customized': Flag indicating whether the strings imported from $file Chris@0: * are customized translations or come from a community source. Use Chris@0: * LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to Chris@0: * LOCALE_NOT_CUSTOMIZED. Chris@0: * - 'message': Alternative message to display during import. Note, this must Chris@0: * be sanitized text. Chris@0: * @param array|\ArrayAccess $context Chris@0: * Contains a list of files imported. Chris@0: */ Chris@0: function locale_translate_batch_import($file, array $options, &$context) { Chris@0: // Merge the default values in the $options array. Chris@0: $options += [ Chris@0: 'overwrite_options' => [], Chris@0: 'customized' => LOCALE_NOT_CUSTOMIZED, Chris@0: ]; Chris@0: Chris@0: if (isset($file->langcode) && $file->langcode != LanguageInterface::LANGCODE_NOT_SPECIFIED) { Chris@0: Chris@0: try { Chris@0: if (empty($context['sandbox'])) { Chris@0: $context['sandbox']['parse_state'] = [ Chris@14: 'filesize' => filesize(\Drupal::service('file_system')->realpath($file->uri)), Chris@0: 'chunk_size' => 200, Chris@0: 'seek' => 0, Chris@0: ]; Chris@0: } Chris@0: // Update the seek and the number of items in the $options array(). Chris@0: $options['seek'] = $context['sandbox']['parse_state']['seek']; Chris@0: $options['items'] = $context['sandbox']['parse_state']['chunk_size']; Chris@0: $report = Gettext::fileToDatabase($file, $options); Chris@0: // If not yet finished with reading, mark progress based on size and Chris@0: // position. Chris@0: if ($report['seek'] < filesize($file->uri)) { Chris@0: Chris@0: $context['sandbox']['parse_state']['seek'] = $report['seek']; Chris@0: // Maximize the progress bar at 95% before completion, the batch API Chris@0: // could trigger the end of the operation before file reading is done, Chris@0: // because of floating point inaccuracies. See Chris@0: // https://www.drupal.org/node/1089472. Chris@0: $context['finished'] = min(0.95, $report['seek'] / filesize($file->uri)); Chris@0: if (isset($options['message'])) { Chris@0: $context['message'] = t('@message (@percent%).', ['@message' => $options['message'], '@percent' => (int) ($context['finished'] * 100)]); Chris@0: } Chris@0: else { Chris@0: $context['message'] = t('Importing translation file: %filename (@percent%).', ['%filename' => $file->filename, '@percent' => (int) ($context['finished'] * 100)]); Chris@0: } Chris@0: } Chris@0: else { Chris@0: // We are finished here. Chris@0: $context['finished'] = 1; Chris@0: Chris@0: // Store the file data for processing by the next batch operation. Chris@0: $file->timestamp = filemtime($file->uri); Chris@0: $context['results']['files'][$file->uri] = $file; Chris@0: $context['results']['languages'][$file->uri] = $file->langcode; Chris@0: } Chris@0: Chris@0: // Add the reported values to the statistics for this file. Chris@0: // Each import iteration reports statistics in an array. The results of Chris@0: // each iteration are added and merged here and stored per file. Chris@0: if (!isset($context['results']['stats']) || !isset($context['results']['stats'][$file->uri])) { Chris@0: $context['results']['stats'][$file->uri] = []; Chris@0: } Chris@0: foreach ($report as $key => $value) { Chris@0: if (is_numeric($report[$key])) { Chris@0: if (!isset($context['results']['stats'][$file->uri][$key])) { Chris@0: $context['results']['stats'][$file->uri][$key] = 0; Chris@0: } Chris@0: $context['results']['stats'][$file->uri][$key] += $report[$key]; Chris@0: } Chris@0: elseif (is_array($value)) { Chris@0: $context['results']['stats'][$file->uri] += [$key => []]; Chris@0: $context['results']['stats'][$file->uri][$key] = array_merge($context['results']['stats'][$file->uri][$key], $value); Chris@0: } Chris@0: } Chris@0: } Chris@0: catch (Exception $exception) { Chris@0: // Import failed. Store the data of the failing file. Chris@0: $context['results']['failed_files'][] = $file; Chris@0: \Drupal::logger('locale')->notice('Unable to import translations file: @file', ['@file' => $file->uri]); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements callback_batch_operation(). Chris@0: * Chris@0: * Save data of imported files. Chris@0: * Chris@0: * @param array|\ArrayAccess $context Chris@0: * Contains a list of imported files. Chris@0: */ Chris@0: function locale_translate_batch_import_save($context) { Chris@0: if (isset($context['results']['files'])) { Chris@0: foreach ($context['results']['files'] as $file) { Chris@0: // Update the file history if both project and version are known. This Chris@0: // table is used by the automated translation update function which tracks Chris@0: // translation status of module and themes in the system. Other Chris@0: // translation files are not tracked and are therefore not stored in this Chris@0: // table. Chris@0: if ($file->project && $file->version) { Chris@0: $file->last_checked = REQUEST_TIME; Chris@0: locale_translation_update_file_history($file); Chris@0: } Chris@0: } Chris@0: $context['message'] = t('Translations imported.'); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements callback_batch_operation(). Chris@0: * Chris@0: * Refreshes translations after importing strings. Chris@0: * Chris@0: * @param array|\ArrayAccess $context Chris@0: * Contains a list of strings updated and information about the progress. Chris@0: */ Chris@0: function locale_translate_batch_refresh(&$context) { Chris@0: if (!isset($context['sandbox']['refresh'])) { Chris@0: $strings = $langcodes = []; Chris@0: if (isset($context['results']['stats'])) { Chris@0: // Get list of unique string identifiers and language codes updated. Chris@0: $langcodes = array_unique(array_values($context['results']['languages'])); Chris@0: foreach ($context['results']['stats'] as $report) { Chris@0: $strings = array_merge($strings, $report['strings']); Chris@0: } Chris@0: } Chris@0: if ($strings) { Chris@0: // Initialize multi-step string refresh. Chris@0: $context['message'] = t('Updating translations for JavaScript and default configuration.'); Chris@0: $context['sandbox']['refresh']['strings'] = array_unique($strings); Chris@0: $context['sandbox']['refresh']['languages'] = $langcodes; Chris@0: $context['sandbox']['refresh']['names'] = []; Chris@0: $context['results']['stats']['config'] = 0; Chris@0: $context['sandbox']['refresh']['count'] = count($strings); Chris@0: Chris@0: // We will update strings on later steps. Chris@0: $context['finished'] = 0; Chris@0: } Chris@0: else { Chris@0: $context['finished'] = 1; Chris@0: } Chris@0: } Chris@0: elseif ($name = array_shift($context['sandbox']['refresh']['names'])) { Chris@0: // Refresh all languages for one object at a time. Chris@0: $count = Locale::config()->updateConfigTranslations([$name], $context['sandbox']['refresh']['languages']); Chris@0: $context['results']['stats']['config'] += $count; Chris@0: // Inherit finished information from the "parent" string lookup step so Chris@0: // visual display of status will make sense. Chris@0: $context['finished'] = $context['sandbox']['refresh']['names_finished']; Chris@0: $context['message'] = t('Updating default configuration (@percent%).', ['@percent' => (int) ($context['finished'] * 100)]); Chris@0: } Chris@0: elseif (!empty($context['sandbox']['refresh']['strings'])) { Chris@0: // Not perfect but will give some indication of progress. Chris@0: $context['finished'] = 1 - count($context['sandbox']['refresh']['strings']) / $context['sandbox']['refresh']['count']; Chris@0: // Pending strings, refresh 100 at a time, get next pack. Chris@0: $next = array_slice($context['sandbox']['refresh']['strings'], 0, 100); Chris@0: array_splice($context['sandbox']['refresh']['strings'], 0, count($next)); Chris@0: // Clear cache and force refresh of JavaScript translations. Chris@0: _locale_refresh_translations($context['sandbox']['refresh']['languages'], $next); Chris@0: // Check whether we need to refresh configuration objects. Chris@0: if ($names = Locale::config()->getStringNames($next)) { Chris@0: $context['sandbox']['refresh']['names_finished'] = $context['finished']; Chris@0: $context['sandbox']['refresh']['names'] = $names; Chris@0: } Chris@0: } Chris@0: else { Chris@0: $context['message'] = t('Updated default configuration.'); Chris@0: $context['finished'] = 1; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements callback_batch_finished(). Chris@0: * Chris@0: * Finished callback of system page locale import batch. Chris@0: * Chris@0: * @param bool $success Chris@0: * TRUE if batch successfully completed. Chris@0: * @param array $results Chris@0: * Batch results. Chris@0: */ Chris@0: function locale_translate_batch_finished($success, array $results) { Chris@0: $logger = \Drupal::logger('locale'); Chris@0: if ($success) { Chris@0: $additions = $updates = $deletes = $skips = $config = 0; Chris@0: if (isset($results['failed_files'])) { Chris@0: if (\Drupal::moduleHandler()->moduleExists('dblog') && \Drupal::currentUser()->hasPermission('access site reports')) { Chris@18: $message = \Drupal::translation()->formatPlural(count($results['failed_files']), 'One translation file could not be imported. See the log for details.', '@count translation files could not be imported. See the log for details.', [':url' => Url::fromRoute('dblog.overview')->toString()]); Chris@0: } Chris@0: else { Chris@0: $message = \Drupal::translation()->formatPlural(count($results['failed_files']), 'One translation file could not be imported. See the log for details.', '@count translation files could not be imported. See the log for details.'); Chris@0: } Chris@17: \Drupal::messenger()->addError($message); Chris@0: } Chris@0: if (isset($results['files'])) { Chris@0: $skipped_files = []; Chris@0: // If there are no results and/or no stats (eg. coping with an empty .po Chris@0: // file), simply do nothing. Chris@0: if ($results && isset($results['stats'])) { Chris@0: foreach ($results['stats'] as $filepath => $report) { Chris@0: $additions += $report['additions']; Chris@0: $updates += $report['updates']; Chris@0: $deletes += $report['deletes']; Chris@0: $skips += $report['skips']; Chris@0: if ($report['skips'] > 0) { Chris@0: $skipped_files[] = $filepath; Chris@0: } Chris@0: } Chris@0: } Chris@17: \Drupal::messenger()->addStatus(\Drupal::translation()->formatPlural(count($results['files']), Chris@0: 'One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', Chris@0: '@count translation files imported. %number translations were added, %update translations were updated and %delete translations were removed.', Chris@0: ['%number' => $additions, '%update' => $updates, '%delete' => $deletes] Chris@0: )); Chris@0: $logger->notice('Translations imported: %number added, %update updated, %delete removed.', ['%number' => $additions, '%update' => $updates, '%delete' => $deletes]); Chris@0: Chris@0: if ($skips) { Chris@0: if (\Drupal::moduleHandler()->moduleExists('dblog') && \Drupal::currentUser()->hasPermission('access site reports')) { Chris@18: $message = \Drupal::translation()->formatPlural($skips, 'One translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.', [':url' => Url::fromRoute('dblog.overview')->toString()]); Chris@0: } Chris@0: else { Chris@0: $message = \Drupal::translation()->formatPlural($skips, 'One translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.'); Chris@0: } Chris@17: \Drupal::messenger()->addWarning($message); Chris@0: $logger->warning('@count disallowed HTML string(s) in files: @files.', ['@count' => $skips, '@files' => implode(',', $skipped_files)]); Chris@0: } Chris@0: } Chris@0: } Chris@0: // Add messages for configuration too. Chris@0: if (isset($results['stats']['config'])) { Chris@0: locale_config_batch_finished($success, $results); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a file object and populates the timestamp property. Chris@0: * Chris@0: * @param string $filepath Chris@0: * The filepath of a file to import. Chris@0: * Chris@0: * @return object Chris@0: * An object representing the file. Chris@0: */ Chris@0: function locale_translate_file_create($filepath) { Chris@0: $file = new stdClass(); Chris@18: $file->filename = \Drupal::service('file_system')->basename($filepath); Chris@0: $file->uri = $filepath; Chris@0: $file->timestamp = filemtime($file->uri); Chris@0: return $file; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Generates file properties from filename and options. Chris@0: * Chris@0: * An attempt is made to determine the translation language, project name and Chris@0: * project version from the file name. Supported file name patterns are: Chris@0: * {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po. Chris@0: * Alternatively the translation language can be set using the $options. Chris@0: * Chris@0: * @param object $file Chris@0: * A file object of the gettext file to be imported. Chris@0: * @param array $options Chris@0: * An array with options: Chris@0: * - 'langcode': The language code. Overrides the file language. Chris@0: * Chris@0: * @return object Chris@0: * Modified file object. Chris@0: */ Chris@0: function locale_translate_file_attach_properties($file, array $options = []) { Chris@0: // If $file is a file entity, convert it to a stdClass. Chris@0: if ($file instanceof FileInterface) { Chris@0: $file = (object) [ Chris@0: 'filename' => $file->getFilename(), Chris@0: 'uri' => $file->getFileUri(), Chris@0: ]; Chris@0: } Chris@0: Chris@0: // Extract project, version and language code from the file name. Supported: Chris@0: // {project}-{version}.{langcode}.po, {prefix}.{langcode}.po or {langcode}.po Chris@0: preg_match('! Chris@0: ( # project OR project and version OR empty (group 1) Chris@0: ([a-z_]+) # project name (group 2) Chris@0: \. # . Chris@0: | # OR Chris@0: ([a-z_]+) # project name (group 3) Chris@0: \- # - Chris@0: ([0-9a-z\.\-\+]+) # version (group 4) Chris@0: \. # . Chris@0: | # OR Chris@0: ) # (empty) Chris@0: ([^\./]+) # language code (group 5) Chris@0: \. # . Chris@0: po # po extension Chris@0: $!x', $file->filename, $matches); Chris@0: if (isset($matches[5])) { Chris@0: $file->project = $matches[2] . $matches[3]; Chris@0: $file->version = $matches[4]; Chris@0: $file->langcode = isset($options['langcode']) ? $options['langcode'] : $matches[5]; Chris@0: } Chris@0: else { Chris@0: $file->langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED; Chris@0: } Chris@0: return $file; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Deletes interface translation files and translation history records. Chris@0: * Chris@0: * @param array $projects Chris@0: * (optional) Project names from which to delete the translation files and Chris@0: * history. Defaults to all projects. Chris@0: * @param array $langcodes Chris@0: * (optional) Language codes from which to delete the translation files and Chris@0: * history. Defaults to all languages. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if files are removed successfully. FALSE if one or more files could Chris@0: * not be deleted. Chris@0: */ Chris@0: function locale_translate_delete_translation_files(array $projects = [], array $langcodes = []) { Chris@0: $fail = FALSE; Chris@0: locale_translation_file_history_delete($projects, $langcodes); Chris@0: Chris@0: // Delete all translation files from the translations directory. Chris@0: if ($files = locale_translate_get_interface_translation_files($projects, $langcodes)) { Chris@0: foreach ($files as $file) { Chris@18: try { Chris@18: \Drupal::service('file_system')->delete($file->uri); Chris@18: } Chris@18: catch (FileException $e) { Chris@0: $fail = TRUE; Chris@0: } Chris@0: } Chris@0: } Chris@0: return !$fail; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds a locale batch to refresh configuration. Chris@0: * Chris@0: * @param array $options Chris@0: * An array with options that can have the following elements: Chris@0: * - 'finish_feedback': (optional) Whether or not to give feedback to the user Chris@0: * when the batch is finished. Defaults to TRUE. Chris@0: * @param array $langcodes Chris@0: * (optional) Array of language codes. Defaults to all translatable languages. Chris@0: * @param array $components Chris@0: * (optional) Array of component lists indexed by type. If not present or it Chris@0: * is an empty array, it will update all components. Chris@0: * Chris@0: * @return array Chris@0: * The batch definition. Chris@0: */ Chris@0: function locale_config_batch_update_components(array $options, array $langcodes = [], array $components = []) { Chris@0: $langcodes = $langcodes ? $langcodes : array_keys(\Drupal::languageManager()->getLanguages()); Chris@0: if ($langcodes && $names = Locale::config()->getComponentNames($components)) { Chris@0: return locale_config_batch_build($names, $langcodes, $options); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a locale batch to refresh specific configuration. Chris@0: * Chris@0: * @param array $names Chris@0: * List of configuration object names (which are strings) to update. Chris@0: * @param array $langcodes Chris@0: * List of language codes to refresh. Chris@0: * @param array $options Chris@0: * (optional) An array with options that can have the following elements: Chris@0: * - 'finish_feedback': Whether or not to give feedback to the user when the Chris@0: * batch is finished. Defaults to TRUE. Chris@0: * Chris@0: * @return array Chris@0: * The batch definition. Chris@0: * Chris@0: * @see locale_config_batch_refresh_name() Chris@0: */ Chris@0: function locale_config_batch_build(array $names, array $langcodes, array $options = []) { Chris@0: $options += ['finish_feedback' => TRUE]; Chris@0: $i = 0; Chris@0: $batch_names = []; Chris@0: $operations = []; Chris@0: foreach ($names as $name) { Chris@0: $batch_names[] = $name; Chris@0: $i++; Chris@0: // During installation the caching of configuration objects is disabled so Chris@0: // it is very expensive to initialize the \Drupal::config() object on each Chris@0: // request. We batch a small number of configuration object upgrades Chris@0: // together to improve the overall performance of the process. Chris@0: if ($i % 20 == 0) { Chris@0: $operations[] = ['locale_config_batch_refresh_name', [$batch_names, $langcodes]]; Chris@0: $batch_names = []; Chris@0: } Chris@0: } Chris@0: if (!empty($batch_names)) { Chris@0: $operations[] = ['locale_config_batch_refresh_name', [$batch_names, $langcodes]]; Chris@0: } Chris@0: $batch = [ Chris@0: 'operations' => $operations, Chris@0: 'title' => t('Updating configuration translations'), Chris@0: 'init_message' => t('Starting configuration update'), Chris@0: 'error_message' => t('Error updating configuration translations'), Chris@0: 'file' => drupal_get_path('module', 'locale') . '/locale.bulk.inc', Chris@0: ]; Chris@0: if (!empty($options['finish_feedback'])) { Chris@0: $batch['completed'] = 'locale_config_batch_finished'; Chris@0: } Chris@0: return $batch; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements callback_batch_operation(). Chris@0: * Chris@0: * Performs configuration translation refresh. Chris@0: * Chris@0: * @param array $names Chris@0: * An array of names of configuration objects to update. Chris@0: * @param array $langcodes Chris@0: * (optional) Array of language codes to update. Defaults to all languages. Chris@0: * @param array|\ArrayAccess $context Chris@0: * Contains a list of files imported. Chris@0: * Chris@0: * @see locale_config_batch_build() Chris@0: */ Chris@0: function locale_config_batch_refresh_name(array $names, array $langcodes, &$context) { Chris@0: if (!isset($context['result']['stats']['config'])) { Chris@0: $context['result']['stats']['config'] = 0; Chris@0: } Chris@0: $context['result']['stats']['config'] += Locale::config()->updateConfigTranslations($names, $langcodes); Chris@0: foreach ($names as $name) { Chris@0: $context['result']['names'][] = $name; Chris@0: } Chris@0: $context['result']['langcodes'] = $langcodes; Chris@0: $context['finished'] = 1; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Implements callback_batch_finished(). Chris@0: * Chris@0: * Finishes callback of system page locale import batch. Chris@0: * Chris@0: * @param bool $success Chris@0: * Information about the success of the batch import. Chris@0: * @param array $results Chris@0: * Information about the results of the batch import. Chris@0: * Chris@0: * @see locale_config_batch_build() Chris@0: */ Chris@0: function locale_config_batch_finished($success, array $results) { Chris@0: if ($success) { Chris@0: $configuration = isset($results['stats']['config']) ? $results['stats']['config'] : 0; Chris@0: if ($configuration) { Chris@17: \Drupal::messenger()->addStatus(t('The configuration was successfully updated. There are %number configuration objects updated.', ['%number' => $configuration])); Chris@0: \Drupal::logger('locale')->notice('The configuration was successfully updated. %number configuration objects updated.', ['%number' => $configuration]); Chris@0: } Chris@0: else { Chris@17: \Drupal::messenger()->addStatus(t('No configuration objects have been updated.')); Chris@0: \Drupal::logger('locale')->warning('No configuration objects have been updated.'); Chris@0: } Chris@0: } Chris@0: }