Skip to content

fix: preserve German umlauts for proper transliteration - #54

Merged
lovell merged 3 commits into
lovell:mainfrom
abhu85:fix-umlaut-deburr
Mar 19, 2026
Merged

fix: preserve German umlauts for proper transliteration#54
lovell merged 3 commits into
lovell:mainfrom
abhu85:fix-umlaut-deburr

Conversation

@abhu85

@abhu85 abhu85 commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #50 - German umlauts (ä, ö, ü, Ä, Ö, Ü, ß) are now correctly transliterated to their digraph equivalents (ä→ae, ö→oe, ü→ue, ß→ss).

Before: Münchenmunchen
After: Münchenmuenchen

Problem

The NFKD normalization step was stripping German umlauts before speakingurl could apply its proper character mappings. For example, ü was being normalized to u (removing the combining diaeresis), so speakingurl never saw the original character and couldn't map it to ue.

Solution

Preserve German umlauts (and any characters specified in custom mappings) during the normalization step using placeholders, then restore them before passing to speakingurl. This allows speakingurl's comprehensive charMap to handle German characters correctly.

The normalization is still applied to other characters (like macrons) where speakingurl's handling is less ideal.

Test plan

  • Added new test suite for German umlaut handling
  • All 11 tests pass
  • Verified no regression in macron handling (Pōnekeponeke)
  • Custom mappings still override default behavior (München with {custom: {'ü': 'u'}}munchen)

@lovell

lovell commented Mar 17, 2026

Copy link
Copy Markdown
Owner

This looks like it was LLM-generated and overly verbose.

Could the fix be as simple as not removing the combining diaeresis?

-  const deburredText = text.normalize('NFKD').replace(/[\u0300-\u036f]/g, '');
+  const deburredText = text.normalize('NFKD').replace(/[\u0300-\u0307\u0309-\u036f]/g, '');

Exclude U+0308 (combining diaeresis) from removal so speakingurl
can properly map ä→ae, ö→oe, ü→ue. NFC recomposition ensures the
characters are in the precomposed form speakingurl expects.

Fixes lovell#50
@abhu85
abhu85 force-pushed the fix-umlaut-deburr branch from b20b6ba to 3165261 Compare March 18, 2026 07:08
@abhu85

abhu85 commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback @lovell! You're right - the PR was AI-generated and the original approach was overly verbose.

I've updated the PR with your simpler fix. I also added .normalize('NFC') after the selective removal to recompose the characters back into precomposed form (e.g., o + U+0308 → ö), since speakingurl expects precomposed characters for its mapping.

const deburredText = text.normalize('NFKD').replace(/[\u0300-\u0307\u0309-\u036f]/g, '').normalize('NFC');

Verified it works:

Schöner Städte Übung → schoener-staedte-uebung
München → muenchen  
Größe → groesse
Ärger → aerger

Thanks again for the better approach!

@lovell

lovell commented Mar 19, 2026

Copy link
Copy Markdown
Owner

Thanks for the update, are you able to add a couple of basic test cases to help prevent future regression?

Added 4 test cases to prevent regression:
- Schöner Städte Übung → schoener-staedte-uebung
- München → muenchen
- Größe → groesse
- Ärger → aerger
@abhu85

abhu85 commented Mar 19, 2026

Copy link
Copy Markdown
Contributor Author

Added test cases in commit 068299b:

ava('German umlaut transliteration', function (t) {
  t.plan(4);
  t.true(limax('Schöner Städte Übung', { lang: 'de' }) === 'schoener-staedte-uebung');
  t.true(limax('München', { lang: 'de' }) === 'muenchen');
  t.true(limax('Größe', { lang: 'de' }) === 'groesse');
  t.true(limax('Ärger', { lang: 'de' }) === 'aerger');
});

All 11 tests pass.

@lovell

lovell commented Mar 19, 2026

Copy link
Copy Markdown
Owner

Thanks, does this work without the { lang: 'de' } option? A test case for that would be good also.

@abhu85

abhu85 commented Mar 19, 2026

Copy link
Copy Markdown
Contributor Author

Yes, it works without { lang: 'de' } as well. Added 4 more test cases in commit e7e2e85:

// Without lang option (default behavior)
t.true(limax('München') === 'muenchen');
t.true(limax('Größe') === 'groesse');
t.true(limax('Ärger') === 'aerger');
t.true(limax('Schöner Städte Übung') === 'schoener-staedte-uebung');

All 11 tests pass (8 German umlaut tests total - 4 with lang: 'de', 4 without).

@lovell
lovell merged commit 5351f17 into lovell:main Mar 19, 2026
7 checks passed
@lovell

lovell commented Mar 19, 2026

Copy link
Copy Markdown
Owner

Thank you / danke

@lovell lovell mentioned this pull request Mar 19, 2026
qqilihq added a commit to qqilihq/mongoose-slugger that referenced this pull request Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mapping of umlauts broken

2 participants