Tunisian address search: French, Arabic and Arabizi
TMaps example: maps and geocoding API for Tunisia. Replace YOUR_API_KEY with your key from https://app.tmaps.tn. More: https://www.tmaps.tn
by Tmaps
HTML
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"></script>
<div id="map"></div>
<div class="panel">
<b>Address search for Tunisia</b><br>
<small>Try <i>Rue de Marseille</i>, <i>نهج مرسيليا</i> or <i>9asr said</i></small>
<form id="form">
<input id="q" type="search" dir="auto" autocomplete="off" value="9asr said">
<select id="lang"><option value="fr">FR</option><option value="en">EN</option><option value="ar">AR</option></select>
</form>
<ul id="results"></ul>
</div>
CSS
html, body { height: 100%; margin: 0; font-family: system-ui, sans-serif; }
#map { position: absolute; inset: 0; }
.panel {
position: absolute; z-index: 1000; top: 12px; left: 12px; max-width: 320px;
background: #fff; border-radius: 8px; padding: 10px 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, .2); font-size: 14px;
}
.panel a { color: #0b63f6; }
form { display: flex; gap: 6px; margin-top: 8px; }
#q { flex: 1; padding: 6px 8px; border: 1px solid #ccd; border-radius: 6px; }
#results { list-style: none; margin: 8px 0 0; padding: 0; max-height: 50vh; overflow: auto; }
#results li { padding: 6px; border-radius: 6px; cursor: pointer; }
#results li:hover, #results li.active { background: #eef3ff; }
#results small { color: #667; }
JavaScript
// TMaps: maps and geocoding API for Tunisia. https://www.tmaps.tn
// 1. Create a free API key on https://app.tmaps.tn
// 2. Add fiddle.jshell.net to its authorized domains
// 3. Paste it below
const API_KEY = 'YOUR_API_KEY';
const map = L.map('map').setView([36.8002, 10.1815], 12); // Tunis
L.tileLayer(`https://tiles.tmaps.tn/r-tile/{z}/{x}/{y}?api_key=${API_KEY}`, {
maxZoom: 18,
attribution: '© <a href="https://www.tmaps.tn">TMaps</a>',
}).addTo(map);
const input = document.getElementById('q');
const list = document.getElementById('results');
const marker = L.marker([0, 0]);
let timer;
let controller;
// Tunisian dialect words and Arabizi digits, rewritten the way addresses are spelled
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 codes (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(' ');
}
async function geocode(q, signal) {
const center = map.getCenter();
const url = new URL('https://api.tmaps.tn/geocoding/forward');
url.search = new URLSearchParams({
q,
country: 'TN',
limit: 5,
lang: document.getElementById('lang').value,
proximity: `${center.lat.toFixed(5)},${center.lng.toFixed(5)}`,
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;
}
// Search the raw input and its normalized version in parallel, then merge...