tinusg/syllable-counter

Counts and hyphenates the syllables of Dutch (and Dutch-spelled international) names and words

Maintainers

Package info

github.com/tinusg/syllable-counter

pkg:composer/tinusg/syllable-counter

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-07-28 21:11 UTC

This package is auto-updated.

Last update: 2026-07-28 21:15:07 UTC


README

Counts and hyphenates the syllables of Dutch — and Dutch-spelled international — names and words, without a dictionary, a pattern file or an API call. Pure spelling rules, one small class, no runtime dependencies beyond illuminate/support for the service provider.

$counter->count('Sophia');      // 3
$counter->hyphenate('Sophia');  // "So-phi-a"
$counter->split('Sophia');      // ['So', 'phi', 'a']

Installation

composer require tinusg/syllable-counter

The service provider is auto-discovered. Publishing the config is optional:

php artisan vendor:publish --tag=syllable-counter-config

Usage

Resolve it from the container (recommended — it picks up your config) or just new it up:

use TinusG\SyllableCounter\SyllableCounter;

class NameController
{
    public function show(SyllableCounter $syllables, string $name)
    {
        return view('names.show', [
            'syllableCount' => $syllables->count($name),
            'hyphenated' => $syllables->hyphenate($name),
        ]);
    }
}
Method Returns Example
count(string $value, ?bool $muteFinalE = null): int Number of syllables, never less than 1 count('Olivia')4
split(string $value, ?bool $muteFinalE = null): array The syllables, casing and accents intact split('Sophia')['So', 'phi', 'a']
hyphenate(string $value, string $separator = '-', ?bool $muteFinalE = null): string The hyphenated word hyphenate('Olivia')'O-li-vi-a'

count() runs through split(), so counting and hyphenating can never disagree.

Spaces and hyphens are word boundaries; every word contributes its own syllables:

$counter->count('Anne-Marie');      // 4
$counter->split('Anne-Marie');      // ['An', 'ne', 'Ma', 'rie']

How it works

The whole thing rests on one rule: a syllable has exactly one vowel nucleus. So finding the nuclei and dividing the consonants between them gives you both the count and the hyphenation.

1. Normalise, but remember the accents

The word is lower-cased and diacritics are reduced to their base letter (ée, çc). Positions that carried a diaeresis or an accent are flagged, because those mark a vowel that is pronounced on its own. Without that flag Chloë and Chloé would both read as the digraph oe and come out as one syllable; with it they correctly split into Chlo-ë and Chlo-é.

2. Find the vowel nuclei

Adjacent vowels are collected into runs, and each run is cut into nuclei with a greedy match on known vowel groups:

  • Trigraphsaai, ooi, oei, eeu, ieu, eau → one nucleus.
  • Digraphsaa, ee, oo, uu, ie, ei, ui, ou, au, oe, eu, ae, ai, ay, ey, oy, uy, ij, oi → one nucleus. So Thijs, Fleur and Loes are one syllable, and Marie is two.
  • Anything else adjacent is a hiatus — two nuclei. That is what makes So-phi-a, Le-o, Mat-te-o and An-to-ni-o come out right; naive vowel-group counters collapse these into one.
  • A flagged vowel (step 1) always breaks a group open, so Zoë never becomes one nucleus.

Two letters get special treatment:

  • y is a glide, not a vowel, when a vowel follows itYa-ra and Ma-ya (2), but Ly-di-a (3), where the y is the nucleus.
  • u after q belongs to the /kw/ consonantQuin-ten (2), Mo-nique (2).

3. Divide the consonants: maximal onset

For the consonant cluster between two nuclei, as much of it as possible moves to the following syllable, as long as the part that moves is a valid Dutch onset (br, chr, kn, zw, th, …). A single consonant is always a valid onset, so V-CV is the default:

So-phie      ph is a valid onset, so it moves along
An-dre-a     dr is valid → An-dre-a, not And-re-a
I-sa-bel-la  ll is not an onset → the split falls in the middle
Chris-ti-aan st is not an onset → only t moves

4. The final e

Whether a final e is pronounced is not a spelling question — Céline (mute) and Eline (pronounced) share the same -ine ending — so it cannot be derived from the letters. The package handles this in two layers:

  • The safe heuristic. After a c or qu the Dutch schwa-e simply does not occur, so there the e is mute: Flo-rence (2), A-lice (2), Grace (1). Wider rules (-ne, -lle) are deliberately not applied, because they would break Han-ne, Jel-le and E-li-ne.
  • Your own knowledge wins. Pass $muteFinalE when you know the answer for that specific name — from a database column, an editor, a lookup, an LLM:
$counter->count('Céline', muteFinalE: true);   // 2 → Cé-line
$counter->count('Eline', muteFinalE: false);   // 3 → E-li-ne
$counter->count('Hatice', muteFinalE: false);  // 3 → Ha-ti-ce, overriding the -ce rule

null (the default) means "no knowledge, use the heuristic". The flag applies to the last word only, so hyphenate('Marie-Alice', '-', true) gives Ma-rie-A-lice.

5. Exceptions, last

A handful of names break every rule Dutch spelling has — James is one syllable, Ga-bri-el splits where the digraph rule says it should not, Turkish -ce names pronounce their final e. Those live in a small exception list that is checked before the rules run.

An exception stores the syllables in lower case; the original is then cut at those same lengths, so capitals and accents survive: GABRIELGA-BRI-EL. If the exception does not match the length of the input, it is ignored and the normal rules apply.

Add your own in config/syllable-counter.php (an entry overrides a built-in one with the same key):

return [
    'exceptions' => [
        'ilse' => ['il', 'se'],
    ],
];

Or pass them straight to the constructor:

new SyllableCounter(['ilse' => ['il', 'se']]);

What it does not do

It produces orthographic hyphenation (So-phi-a), not a phonetic transcription, and it is tuned for names. The rules are Dutch, so an English word with an English-only spelling pattern can come out wrong. It is a rule set, not an oracle — when you need a specific word to be exact, add an exception.

Testing

composer test

The suite is a large table of real names — Dutch, Turkish, Arabic, French, Latin, English — with their expected counts and hyphenations. If you find a name that comes out wrong, a pull request with that name added to the table is the most useful thing you can send.

Thanks

This package was extracted from the code that powers two Dutch sites, and it exists because they needed it:

  • Naampedia — baby names, meanings, origins and popularity. The syllable rules here were written for its name pages and tested against tens of thousands of real first names.
  • Puzzelpedia — a puzzle dictionary and solving aid, where counting syllables and splitting words is daily business.

If this package is useful to you, a link back to naampedia.nl and puzzelpedia.nl — in your README, your credits page or wherever you list what you build on — would be very much appreciated. That is the whole price.

License

MIT. See LICENSE.