Importing translation string with script without reinstall in Drupal 8

Importing translation string with script without reinstall in Drupal 8

Drupal 8 supports multilingual out of the box now and provides a translation interface to add translated strings in different languages. But it's not always feasible to add translation through this translation interface as translation strings might be larger in number.

To tackle the bulk translation strings import we use .po files for each language. You can put .po files in the modules translation directory and translations will be imported at the time of module installation. But one of the problems with this approach is you need to always reinstall the module to import new translations strings if they are added later.

One way to tackle this issue is to import translations with a script that can be used with hook_install(), hook_update(), or with any other custom interface as well. 

An array of translation strings can be passed to the function with the respective language within.

$strings = [
    'First name' => [
      'ar' => 'الاسم الأول',
    ],
    'Log in' => [
      'de' => 'Anmeldung',
      'ar' => 'تسجيل الدخول',
    ],
    'Log out' => [
      'de' => 'Ausloggen',
      'ar' => 'تسجيل الخروج',
    ],
  ];
my_module_import_translations($strings);
function my_module_import_translations($strings) {
  /** @var \Drupal\locale\StringDatabaseStorage $locale_storage */
  $locale_storage = \Drupal::service('locale.storage');

  // Loop through each string for translation.
  foreach ($strings as $source => $translations) {
    // Translation context.
    $context = !empty($translations['context']) ? $translations['context'] : '';
    // Removing key so to create no problem in looping.
    unset($translations['context']);
    // First create the translation source entry.
    $source_param = [
      'source' => $source,
      'version' => \Drupal::VERSION,
    ];
    // If context is available.
    if (!empty($context)) {
      $source_param += ['context' => $context];
    }

    if (!$locale_storage->findString($source_param)) {
      $locale_storage->createString($source_param)->save();
    }

    // Loop through all translations and save them.
    foreach ($translations as $langcode => $translation) {
      $conditions = ['source' => $source];
      // If context is available.
      if (!empty($context)) {
        $conditions += ['context' => $context];
      }
      $options = ['translated' => FALSE, 'untranslated' => TRUE];

      // Look for existing translation sources.
      $data = $locale_storage->getTranslations($conditions, $options);
      foreach ($data as $key => $value) {
        if (isset($value->source) && $value->source == $source) {
          // Save translation.
          $params = [
            'lid' => $value->lid,
            'language' => $langcode,
          ];
          // If context is available.
          if (!empty($context)) {
            $params += ['context' => $context];
          }

          try {
            try {
              if ($target = $locale_storage->findTranslation($params)) {
                $target->delete();
              }
            }
            catch (\Exception $e) {
              // This is fine, it's ok if a text is not present.
            }

            $target = $locale_storage->createTranslation($params);

            $target->setPlurals([$translation])
              ->setCustomized()
              ->save();
          }
          catch (\Exception $e) {
            \Drupal::logger('my_module')
              ->critical('Error occurred while saving translation for @string in @langcode', [
                '@string' => $translation,
                '@langcode' => $langcode,
              ]);
          }
        }
      }
    }
  }
}

This script can also be used with a custom interface where the admin can upload a .csv file with translations in a particular format and import them. If you are planning to do this make sure to test the script and make appropriate changes in it to make it work perfectly.