Direkt zum Inhalt

Better filename transliteration in Drupal 8. Transliteration using non-default language.

Drupal has transliteration support already built into Core. It does it perfectly for the entities path aliases: it loads the transliteration table of the current language and changes the letters according to that language.

Filenames transliteration are more problematic when it comes to uploading the files with filenames in languages other than English.

In our project we needed to achieve the following:

  • Correct transliteration of German filenames, which would include mapping umlaut letters to correct ASCII representation: ä -> ae, etc. For filenames the out of the box transliteration worked by english transliteration rules (ä -> a), which client wanted to change,
  • Spaces to be replaced with underscores,
  • Additionally, the client wanted filenames renamed in UI and in the filesystem, as well. Out of the box Drupal changes the shown name in the database only without touching the file itself.
     

Luckily, Drupal 8 turns out to be quite simple in doing such things, once you get used to it. 

In this walkthrough we will see how to create a new filename transliteration service that can be used throughout the site. Also, we will see how to override the Drupal Core’s native transliteration service and do all the tasks listed above.

Let’s imagine our module name is my_module. 

Let’s create a filename postprocessor service. In your my_module.services.yml file declare the service, its class name, and variables that Drupal’s dependency injection backend should initialize for us:
 

services:
my_module.filename_postprocessor:
class: Drupal\my_module\FilenamePostprocessor
arguments: ['@config.factory', '@transliteration']

Let’s create the service class. In file my_module/src/FilenamePostprocessor.php:

namespace Drupal\my_module;

use Drupal\Component\Transliteration\TransliterationInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;

class FilenamePostprocessor {

protected $configFactory;
protected $transliteration;

public function __construct(ConfigFactoryInterface $config_factory, TransliterationInterface $transliteration) {
$this->configFactory = $config_factory;
$this->transliteration = $transliteration;
 	}

 	public function process($filename) {
 		$filename = Unicode::strtolower($filename);
$filename = str_replace(' ', '_', $filename);
 		$filename = $this->transliteration->transliterate($filename);

   		return $filename;
 	}
}

Service created. Now, have a look at why this is so useful.
At any point in your code and in any module, you can call this service in a very modular way, taking for granted that Drupal does all the initialization of possible dependencies in the background. Even if the author of that service decides that any of the dependencies must change, he will do it in his independent code only, and you as a consumer of the service will not have to touch anything.

$filename_postprocessor = Drupal::service('my_module.filename_postprocessor');
$new_filename = $filename_postprocessor->process($entity->getFilename());

Next, we need to hook into the file the creation and update process. It should rename the file if user has changed its name in the user interface. Then it should launch the filename transliteration service and save the changes.

We do it by implementing two hooks:
 

<?php

/**
 * Implements hook_ENTITY_TYPE_field_values_init().
 */
function my_module_file_field_values_init($entity) {
  $filename_postprocessor = Drupal::service('ge_assets.filename_postprocessor');
  $filename = $entity->getFilename();
  $filename = $filename_postprocessor->process($filename);
  $entity->setFilename($filename);
}

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function my_module_file_presave(DrupalCoreEntityEntityInterface $entity) {
  $filename_postprocessor = Drupal::service('ge_assets.filename_postprocessor');
  $new_filename = $filename_postprocessor->process($entity->getFilename());
  if ($new_filename != drupal_basename($entity->getFileUri())) {
    $uri = $entity->getFileUri();
    $directory = drupal_dirname($uri);
    $uri = $directory . '/' . $new_filename;
    if ($new_uri = file_unmanaged_move($entity->getFileUri() , $uri, FILE_EXISTS_RENAME)) {
      $entity->set('uri', $new_uri);
      $entity->set('filename', drupal_basename($new_uri));
    }
  }
}

Now, what we need to do is have the core transliteration service use German language by default. For this purpose we need to extend existing Transliteration core class and have Drupal always use it instead of the original.

There is a standard way your module can override an existing service.

Create a file called MyModuleServiceProvider.php inside my_module/src directory:
 

<?php

namespaceDrupalmy_module;
useDrupalCoreDependencyInjectionContainerBuilder;
useDrupalCoreDependencyInjectionServiceProviderBase;
class MyModuleServiceProvider extends ServiceProviderBase {
  
public function alter(ContainerBuilder $container) {
    $definition = $container->getDefinition('transliteration');
    $definition->setClass(Drupalmy_moduleTransliteration::class);

  }
}

In the alter method we read the definition of the transliteration service and change its class to our own, so that Drupal can initialize it instead of the original Core transliteration service.

The last step is to create the \Drupal\my_module\Transliteration class itself that will override the original core transliteration. In my_module/src directory create a Transliteration.php file:
 

<?php

namespaceDrupalmy_module;
useDrupalCoreTransliterationPhpTransliteration;
class Transliteration extends PhpTransliteration {
  
public function transliterate($string, $langcode = 'de', $unknown_character = '?', $max_length = NULL) {
    return parent::transliterate($string, $langcode, $unknown_character, $max_length);
  }
}

See, we don’t do much here, simply call the original transliteration method. If you go to the original definition of the PhpTransliteration service you will see that the $langcode variable default value is ‘en’. The only thing we have done here is change it from ‘en’ to ‘de’, letting it do the rest.

Über den Autor:
Alexander Belov
Alexander Belov
Teamleitung / Drupal

Sasha entwickelt komplexe Web-Applikationen seit Drupal 5 und leitet unser Drupal-Team als Senior Entwickler und Software-Architekt.

Wie können wir Sie unterstützen?

Kontaktieren Sie uns

Copyright © 2018 BUZZWOO! GmbH & Co. KG