Address Autocomplete for Tunisia: Arabic and Arabizi
Ask ten people in Tunis to type the same address and you'll see three different alphabets: French: Rue de Marseille, Tunis Arabic script: نهج مرسيليا Arabizi, Tunisian Arabic written in Latin letters, with digits sta
Ask ten people in Tunis to type the same address and you'll see three different alphabets:
- French:
Rue de Marseille, Tunis - Arabic script:
نهج مرسيليا - Arabizi, Tunisian Arabic written in Latin letters, with digits standing in for sounds French doesn't have:
nahj marseille,zan9et el bacha,9asr said
An address field that only understands the first one is going to lose customers at checkout. In this post we'll build an autocomplete with the TMaps Geocoding API that copes with all three, and that displays Arabic results properly, from right to left.
Try it live: examples.tmaps.tn/04-geocoding-search. Switch the language to Arabic, or type 9asr said.
Or play with it right here, and fork it on CodePen to change the code:
Arabizi in 30 seconds
Arabizi borrows digits that look like the Arabic letter they replace:
| Digit | Arabic | Sound | Example | Usual French spelling |
|---|---|---|---|---|
3 |
ع | ʕayn | 3in |
Ain |
5 |
خ | kh | 5ayreddine |
Khayreddine |
7 |
ح | a strong h | 7ammamet |
Hammamet |
9 |
ق | q | 9asr said |
Ksar Said |
Add the dialect words for street types, nahj (rue), zan9a (impasse), chare3 (avenue) and 7ay (cité), and you've got the way a lot of people actually write addresses here.
Step 1: a solid basic autocomplete
At its core, the autocomplete calls GET /geocoding/forward as the user types. Three details make the difference:
let timer;
let controller;
input.addEventListener('input', () => {
clearTimeout(timer);
timer = setTimeout(search, 300); // wait for the user to pause typing
});
async function geocode(q, signal) {
const url = new URL('https://api.tmaps.tn/geocoding/forward');
url.search = new URLSearchParams({
q,
country: 'TN', // Tunisia only
limit: 5,
lang: languageSelect.value, // fr, en or ar
proximity: '36.8065,10.1815', // favour results near the user or the map center
api_key: API_KEY,
});
const res = await fetch(url, { signal });
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(`${res.status}: ${data.error ?? res.statusText}`);
return data.results;
}
- Debounce for 300 ms, so you send one request when the user pauses instead of one per letter.
- Use an
AbortController. When a new search starts, cancel the previous one, otherwise a slow old response can land after a newer one and overwrite it. - Pass
proximity. Tunisia has a Rue de Marseille in a lot of cities, and biasing towards the user's position (or the map center) puts the right one at the top.
Each result gives you the point in geometry.location (lat, lng), a formatted address, a confidence score, and components with the commune, delegation and governorate.
Step 2: Arabic results, right to left
Set lang=ar and the whole address comes back in Arabic:
{
"formatted": "نهج مرسيليا, حبيب ثامر, باب بحر, تونس, تونس",
"type": "street",
"confidence": 0.8
}
Arabic has to be displayed right to left. Since a suggestion list often mixes scripts (an Arabic address next to a French commune name, a road number in Latin digits…), don't hard-code the direction. Let the browser figure it out for each element:
const li = document.createElement('li');
li.dir = 'auto'; // the first strong character decides: Arabic means RTL, Latin means LTR
li.textContent = result.formatted;
<input id="address" type="search" dir="auto">
Putting dir="auto" on the input as well flips the caret and the alignment as soon as someone starts typing in Arabic. Without it, punctuation and numbers end up on the wrong side.
Step 3: what the API already understands
We ran a batch of real Tunisian queries against the API (lang=fr, country=TN). Here's the first result for each:
| Query | First result |
|---|---|
نهج مرسيليا |
✅ Rue de Marseille |
nahj marseille |
✅ Rue de Marseille, Tunis |
9asr said |
✅ Ksar Said, Le Bardo |
7ammamet |
✅ Hammamet |
5ayreddine |
✅ Rue Khayreddine |
3in zaghouan |
❌ no result |
zan9et el bacha |
⚠️ Khaireddine Pacha (not an impasse) |
So Arabic script, nahj, and the digits 7, 9 and 5 already work. The digit 3 and some dialect words need a hand, and that help can live on the client.
Step 4: normalize Arabizi on the client
A small function rewrites dialect words and Arabizi digits into the spelling used in addresses:
// Tunisian dialect words and Arabizi digits, rewritten as in addresses
const DIALECT = {
nahj: 'rue', nhej: 'rue', zan9a: 'impasse', zan9et: 'impasse', zanket: 'impasse',
chare3: 'avenue', chara3: 'avenue', hay: 'cité', '7ay': 'cité',
};
const ARABIZI = { 3: 'a', 5: 'kh', 7: 'h', 9: 'k' };
function normalizeTunisian(text) {
return text.toLowerCase().split(/\s+/).map((word) => {
if (DIALECT[word]) return DIALECT[word];
// Leave numbers (4014) and road names (rn9, c27) alone
if (!/[a-z]/.test(word) || /^[a-z]{1,2}\d+$/.test(word)) return word;
return word.replace(/3a/g, 'a').replace(/[3579]/g, (digit) => ARABIZI[digit]);
}).join(' ');
}
"3in zaghouan" → "ain zaghouan"
"nahj 5ayreddine" → "rue khayreddine"
"zan9et el bacha" → "impasse el bacha"
"chare3 bourguiba" → "avenue bourguiba"
"Rue 4014" → "rue 4014" (unchanged)
"RN9" → "rn9" (unchanged)
"نهج مرسيليا" → "نهج مرسيليا" (unchanged)
Don't skip the two guards. Tunisian addresses are full of numbers (Rue 4014) and road codes (RN9, C27), and rewriting those digits would break queries that worked perfectly well.
Step 5: search both versions in parallel
Normalizing can also make things worse: 9asr said already works as typed. So rather than replacing what the user wrote, search both versions and merge the results:
async function search() {
const q = input.value.trim();
if (q.length < 3) return;
controller?.abort();
controller = new AbortController();
const normalized = normalizeTunisian(q);
const queries = normalized === q.toLowerCase() ? [q] : [q, normalized];
const lists = await Promise.all(queries.map((text) => geocode(text, controller.signal)));
// Normalized results first, then the raw ones, without duplicates
const seen = new Set();
const results = lists.reverse().flat()
.filter((r) => !seen.has(r.formatted) && seen.add(r.formatted))
.slice(0, 5);
render(results);
}
Plain French or Arabic text comes out of normalizeTunisian unchanged, so it still costs a single request. Only Arabizi or dialect input triggers the second one.
The difference shows right away: 3in zaghouan goes from nothing at all to Rue Ain Zaghouane and the Ain villages around Zaghouan.
And 9asr said lands on Ksar Said, in Le Bardo:
Step 6: don't trust every match
For a delivery address, a wrong match costs more than no match at all. The confidence score helps you decide:
- at 0.85 or above, save the coordinates;
- below that, keep the suggestion but ask the customer to check the pin on a map.
The checkout form demo shows the whole flow: pick a suggestion, the commune, delegation and governorate fill themselves in, then the customer can drag the pin, which is refined with reverse geocoding.
Known limits today
To be upfront about what this approach doesn't solve yet:
- Some city names typed in Arabic script can come back as a different place with a similar name. If your users type city names in Arabic, keep
proximityset and show the commune and governorate in each suggestion so they can pick the right one. - When several places share a name in different regions (there's more than one El Aouina), the ranking depends a lot on
proximity. Send the user's real position whenever you have it.
To sum up
- Debounce, cancel stale requests, and bias results with
proximity. - Use
lang=arfor Arabic results, anddir="auto"wherever you display them. - Normalize dialect and Arabizi on the client, without touching numbers and road codes.
- Search the raw and normalized text in parallel, then merge and dedupe.
- Only save coordinates when
confidenceis high enough.
Going further
- TMaps for delivery and e-commerce: www.tmaps.tn
- Live demo: examples.tmaps.tn/04-geocoding-search
- Checkout form demo: examples.tmaps.tn/12-checkout-address-form
- Source code, MIT licensed: github.com/tmaps-tn/tmaps-snippets
- Postman collection: postman.com/tmaps-tn/tmaps-api
- Geocoding documentation: tmaps.tn/documentation
How do you type addresses? Share your trickiest Tunisian query in the comments, or write to us at [email protected].
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.

