Släng.ee · 2026

Släng.ee

Estonian slang dictionary: words you won't find anywhere else.

Goal

The Estonian slang dictionary had to move from an old, scattered setup to a fast site where words are easy to find and anyone can add new ones, without losing the words and history collected over the years.

Results in numbers

What changed

5,919

words added by users

15

years of user content (since 2011)

679,377

names on the blocklist, checked without loading the whole list into memory

0

IP addresses stored

The situation

Släng.ee is an Estonian slang dictionary that anyone can add words to. The oldest entries date from 2011. Over the years the site had grown into a stack of plugins for page building, forms, voting, view counting, search and the shop. Every new plugin added code that slowed the site down, and each one kept its data its own way.

The new design had to replace them with one theme that does it all itself. At the same time three very different sources had to be merged into one dictionary: the Estonian Language Institute’s old web archive, the University of Tartu’s 2025 slang dictionary spreadsheet and hand-made lists. Old dates, votes and view counts had to survive, and the shop had to keep working exactly as before.

The most sensitive part, though, was that user-submitted words can touch real people. A slang dictionary is an easy way to mock a classmate. That had to be prevented without tracking a single user.

Constraints

  • Three very differently shaped sources (a legacy-encoded web page, a Google Sheet and hand-made lists) had to be unified without losing history.
  • User-submitted words must never name real people or schools, not even hidden inside a word.
  • Voting and view counting had to work without storing a single IP address.
  • The shop (WooCommerce, MakeCommerce payments and Printful fulfilment) had to work exactly as before. Order data couldn't be touched.
  • The Estonian letters Õ, Ä, Ö, Ü, Š and Ž must not turn into plain letters in URLs.

Solution

How I solved it

Migration

Three sources had to become one dictionary without losing anything or creating duplicates.

The data was moved in small batches. After each batch the content was checked against the original. The same word from several sources was merged into one, even when it was spelled slightly differently.

Every tool that changes data runs in trial mode by default: it shows what it would do but changes nothing. A real run needs a separate command, and the trial report is kept. Even removing words doesn’t delete anything, it moves them to the bin where they can be restored.

The old plugins kept votes and views in their own fields. The new theme uses exactly the same fields, so 15 years of votes and views survived without rewriting a single number. After the switch the old plugins stay installed for a while, just switched off, so it’s quick to go back if needed.

Legacy data

Old entries mixed layout leftovers with a submitter line. The search result description would have come from the wrong place.

In old entries the definition, example sentence and submitter’s name were written into one text field together with layout. If a search engine takes the first paragraph as the description, often that isn’t the definition at all.

Now every word has separate fields: word, meaning, example and submitter. The search result description is built from the structured meaning and cut at a whole word, not in the middle of an accented letter. Some old words had part of a URL (such as “haavatud-polv”) where the submitter’s name should be. Now the submitter’s real name is shown instead, and the link leads to their other words.

Privacy

User-submitted slang must never name a real person or school.

The blocklist holds 679,377 names from the population register’s public list. They’re stored as SHA-256 hashes, not readable text, as fixed-length records in sorted order. That means a name can be checked with a binary search: about 20 steps, without ever loading the whole 21.7 MB file into memory. Adding a word never calls an outside service.

On top of that there are 3,589 entries from the old blocklist, a list of schools, manual blocks and 157 allowed exceptions (ordinary words that also happen to be someone’s name). Register names are checked from three letters up, because two-letter foreign names would collide with too many ordinary words.

What didn’t work: the first version was too strict. The population register’s names include plenty of ordinary Estonian words, because someone carries them as a name: the words for cat, bear, wolf, birch, mouse, fish, and “anna” (give). A slang dictionary is full of words like that, and the filter rejected them. That is where the allowed-exceptions list came from. In the admin view a suspicious hit can be marked as an exception with one button, and the decision is kept.

Multi-word phrases had the same problem. Two words that are each someone’s surname don’t make a full name. Now a phrase counts as a full name only if it contains a known first name. The opposite mistake happened too: a few plain first names had slipped into the exceptions, and names could get into the dictionary through them. I took those out of the exceptions later.

Each step looks at the middle record and throws away half of what's left. Out of 679,377 names, one is left after 20 steps.
The name filter needed two directions: fewer false alarms and no holes.

Evasion

Anyone who wants to get a name into the dictionary tries to hide it: digits, repeated letters, compounds, spaces between letters.

Every trick is handled separately. Digits are removed (“jakob67” is still Jakob). Repeated letters are collapsed (“Joooonas” is Joonas). Compounds are checked for a first name at the start and the end (“Joonaspede”). Spaces and dashes between letters are removed (“L ü ü s”). In a multi-word phrase each word is checked on its own, and so is whether they add up to a first name and surname.

When an admin deletes a word, it’s added to the blocklist automatically, so the same word can’t be added again. If the word is restored, the block is lifted. Existing words can be rechecked against the whole list: first as a report only, then, if wanted, by moving them to the bin.

What didn’t work: the first version only checked the word as a whole. After launch, exactly the tricks described above got through: “julius 2.0”, “Kassspar” and compound words starting with a name. Every way around the filter got its own check and its own test. The compound-word rule has a limit too. The name has to be at least four letters and the word at least three letters longer than the name, or every ordinary word that happens to start with a short name would be caught. Single-letter fields are no longer accepted.

Every trick is undone before the check, so a hidden name reaches the block list in the same form as a plain one.

Community

Voting and view counting must not track users or collect their IP addresses.

A vote is remembered in the user’s own browser, in a cookie signed with a server secret (HMAC). The cookie can’t be forged by hand and no IP address is stored on the server. Changing a vote is a single database operation, so two simultaneous clicks can’t scramble the count or push it below zero. Requests have to come from the site itself, not from some other site.

Views are counted with a cookie that stops the same person being counted twice within 24 hours. The home page shows many words at once, so views for up to 50 words are sent in one request, instead of the server answering fifty separate ones.

On the home page and the letter pages, a word card only counts as viewed once the page has been open for at least three seconds. Scrolling quickly through a page doesn’t add views to any word.

Security

An open word submission form attracts spam and bots.

The form has an invisible trap field that only bots fill in. It has to be open for at least 2 seconds and at most 2 hours. One sender can submit up to 3 words per 10 minutes. To recognise the sender, the IP address isn’t stored, only a hash of it that disappears after 10 minutes. Request headers the sender can fake aren’t trusted.

The time limit only works if the form page is fresh every time. If the page cache had saved the form together with its opening time, everyone would have been shown the same old time and after a while the form would have rejected every sender. So the submission page is always excluded from the cache.

The same word can’t be added twice: words are compared in a normalised form, so spaces, capitals, apostrophes and dashes don’t make the same word “new”. If two people add the same new word at the same moment, the database locks that word briefly so only one gets through.

A bot submits the form instantly, a person doesn't. On top of that, a trap field and a limit of 3 words per 10 minutes.

Estonian

WordPress turns the Estonian letters Õ, Ä, Ö, Ü, Š and Ž into plain letters. Pages for words starting with Õ and O would have got mixed up.

The browse-by-letter pages now have URLs that keep Estonian letters intact. Old words didn’t always have their letter set, so the initial is taken straight from the word itself. Quotation marks and punctuation at the start mustn’t get in the way: a word in quotes that starts with Õ still belongs under Õ. The old letter pages redirect to the new ones, so search engines don’t see the same content at two addresses.

“Random word” picks from a list prepared in advance and refreshed every 12 hours, because finding a random row in a big table every time would be slow.

Search engines

Thousands of user-submitted pages can look thin or duplicated to a search engine.

Every word has markup for search engines saying it’s a dictionary entry (DefinedTerm). A submitter’s page is only opened to search engines once they have at least three words. Blocked words and placeholders (the word “word”, the meaning “definition”) are kept out of search and the sitemap. Every rule is reversible: when a word is fixed, it returns to search on its own.

The home page shows new words all the time. So that a random fresh entry doesn’t appear in search results, the home page description is fixed separately. The shop’s utility pages (cart, account) are kept out of search.

Shop

There's a shop next to the dictionary, but its code mustn't slow the dictionary down.

Shop styles and scripts load only on shop pages. Dictionary pages stay free of them, except for campaign tracking, so a purchase can be linked to the word page the visitor came from.

Cart and checkout use WooCommerce’s classic version, because that’s where MakeCommerce payments and Printful shipping hook in. Free shipping for two or more items changes only Printful’s standard shipping rate. It doesn’t touch express shipping, tax or order data. The release process runs the whole purchase path through a test environment first: successful, failed and cancelled payments, and a test order to Printful.

Result

Släng.ee now holds 5,919 user-submitted words in one fast WordPress theme, the oldest from 2011. The theme now does the work of the page builder, form, voting, search and cart plugins, and votes, views and the shop work as before.

User privacy is protected without storing a single IP address, and real people’s names can’t get into the dictionary even in disguise. Search engines see every word as a dictionary entry and thin pages aren’t indexed.

What's next

Next I’m improving search and discovery of new words, and keeping an eye on the shop so it doesn’t slow the dictionary down.

Next step

Your project could be next