Slugs without Umlaute

Hi, i try to exchange the Umlaute [‘ä’, ‘ö’, ‘ü’] with [‘ae’, ‘oe’, ‘ue’]

i do it in my ArticlesTable.php

```

public function beforeSave(EventInterface $event, $entity, $options)
{
if ($entity->isNew() && !$entity->slug) {
$sluggedTitle = Text::slug($entity->title);
// trim slug to maximum length defined in schema
$entity->slug = substr($sluggedTitle, 0, 191);
}

    if ($entity->isNew()) {
        $entity->slug = Text::slug($entity->title, [
            'replacement' => '-',
            'lowercase' => true,
            'transliterate' => true, // enable default transliteration
        ]);
        
        // perform custom transliteration
        $entity->slug = str_replace(['ä', 'ö', 'ü'], ['ae', 'oe', 'ue'], $entity->slug);
        
        debug($entity->slug); // Output the slug to the console
    }
}```

But the problem is that the Umlaute are not exchanged correctly… an ä will become an a and an ü will become an u and so on but i need an ä to become an ae…

idea?

There is no transliterate option (and no lowercase for that matter), but a transliteratorId one, which can be either null to use the default transliterator (which can be changed globally via Text::setTransliteratorId() or Text::setTransliterator()), a transliterator ID string, or a transliterator object.

The default transliterator ID is defiend as Any-Latin; Latin-ASCII; [\u0080-\u7fff] remove, which doesn’t include umlaut transliteration. You’d have to add de-ASCII; which holds rules for transforming umlauts:

$transliteratorId = 'de-ASCII;' . Text::getTransliteratorId();

$entity->slug = Text::slug($entity->title, [
    'transliteratorId' => $transliteratorId,
]);

If you want this to apply everywhere as the default, just set the default transliterator at bootstrapping time:

Text::setTransliteratorId($transliteratorId);

Note that de-ASCII does not have rules to transform the currency symbol, and IIRC there aren’t any that include it, so you’d had to create a custom rule if you need to transform that too:

$transliterator = transliterator_create_from_rules('
    \u20ac → EUR;
    :: de-ASCII;
    :: Any-Latin;
    :: Latin-ASCII;
    :: [\u0080-\u7fff] Remove;
');

Also in case your ICU version isn’t up to date, meaning pre 60 IIRC, then de-ASCII isn’t included, and you’d have to create the rules for that manually as well:

$transliterator = transliterator_create_from_rules('
    $AE = [Ä {A \u0308}];
    $OE = [Ö {O \u0308}];
    $UE = [Ü {U \u0308}];
    [ä {a \u0308}] → ae;
    [ö {o \u0308}] → oe;
    [ü {u \u0308}] → ue;
    {$AE} [:Lowercase:] → Ae;
    {$OE} [:Lowercase:] → Oe;
    {$UE} [:Lowercase:] → Ue;
    $AE → AE;
    $OE → OE;
    $UE → UE;
    \u20ac → EUR;
    ::Any-ASCII;
    ::Any-Latin;
    ::Latin-ASCII;
    ::[\u0080-\u7fff] Remove;
');

See also Transforms | ICU Documentation

Okay, thank you… the wrong options were provided by ChatGPT… i am new to ChatGPT and i didn’t know that this can happen:

my final solution to this was:

bootstrap:

use Cake\Utility\Text;
use Transliterator;

// Define your custom transliteration rules
$transliteratorRules = '
    $AE = [Ä {A \u0308}];
    $OE = [Ö {O \u0308}];
    $UE = [Ü {U \u0308}];
    [ä {a \u0308}] → ae;
    [ö {o \u0308}] → oe;
    [ü {u \u0308}] → ue;
    {$AE} [:Lowercase:] → Ae;
    {$OE} [:Lowercase:] → Oe;
    {$UE} [:Lowercase:] → Ue;
    $AE → AE;
    $OE → OE;
    $UE → UE;
    \u20ac → EUR;
    ::Any-ASCII;
    ::Any-Latin;
    ::Latin-ASCII;
    ::[\u0080-\u7fff] Remove;
';

// Create a transliterator instance with your custom rules
$transliterator = Transliterator::createFromRules($transliteratorRules, Transliterator::FORWARD);

// Get the default transliterator ID for your locale
$transliteratorId = 'de-ASCII;' . Text::getTransliteratorId();

// Set the default transliterator ID to use your custom rules
Text::setTransliteratorId($transliteratorId);

// Set the custom transliterator to use for slugging
Text::setTransliterator($transliterator);

// Anpassung für die Verwendung nur bei Slugs
Configure::write('Sluggable.transliterator', $transliterator);

ArticleController:

    protected function _setSlug($slug)
    {
        return Text::slug($slug, [
            'transliteratorId' => 'de-ASCII',
        ]);
    }
    
    public function beforeSave(EventInterface $event, $entity, $options)
    {
        if ($entity->isNew() && !$entity->slug) {
            $sluggedTitle = Text::slug($entity->title);
            // trim slug to maximum length defined in schema
            $entity->slug = substr($sluggedTitle, 0, 191);
        }
        
        if (!$entity->isNew() && $entity->isDirty('title')) {
            $entity->slug = $this->_setSlug($entity->title);
        }
    }

great thank you!

Why not using existing and well working behaviors - or at least get some ideas from those?
E.g. cakephp-tools/Slugged.md at master · dereuromark/cakephp-tools · GitHub (linked from awesome list).
Since they are usually more complete for all use cases.

Why not using existing and well working behaviors - or at least get some ideas from those?

Because then you would miss the chance to use the shiny new tool all the cool kids are using :stuck_out_tongue:.

1 Like