Implement i18next
This commit is contained in:
parent
96f4f3b8e4
commit
0f7e3dee1e
11 changed files with 270 additions and 49 deletions
2
db.go
2
db.go
|
|
@ -41,7 +41,7 @@ func (a *StringArray) Scan(src interface{}) error {
|
|||
}
|
||||
|
||||
func (a StringArray) Value() (driver.Value, error) {
|
||||
if a == nil || len(a) == 0 {
|
||||
if len(a) == 0 {
|
||||
return "{}", nil
|
||||
}
|
||||
elems := make([]string, len(a))
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@
|
|||
}
|
||||
acTimer = setTimeout(function () {
|
||||
acController = new AbortController();
|
||||
listEl.innerHTML = '<li class="ac-loading"><span class="spinner"></span>Searching...</li>';
|
||||
listEl.innerHTML = '<li class="ac-loading"><span class="spinner"></span>' + t("searching") + '</li>';
|
||||
listEl.classList.add("open");
|
||||
fetch(
|
||||
"https://nominatim.openstreetmap.org/search?" +
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
format: "json",
|
||||
limit: "5",
|
||||
addressdetails: "0",
|
||||
"accept-language": "de",
|
||||
"accept-language": getLocale(),
|
||||
}),
|
||||
{ signal: acController.signal }
|
||||
)
|
||||
|
|
@ -234,7 +234,7 @@
|
|||
}
|
||||
|
||||
function showCount(count) {
|
||||
countEl.textContent = count + " repeater" + (count !== 1 ? "s" : "");
|
||||
countEl.textContent = t("repeater_count", { count: count });
|
||||
}
|
||||
|
||||
function showStatus(msg) {
|
||||
|
|
@ -247,50 +247,50 @@
|
|||
var html = '<div class="rptr-popup">';
|
||||
html += '<h3><a href="https://brandmeister.network/?page=repeater&id=' + r.id + '" target="_blank" rel="noopener">' + escapeHtml(r.callsign) + '</a> <span class="band-tag ' + bandClass + '">' + escapeHtml(r.band) + "</span></h3>";
|
||||
html += "<table>";
|
||||
html += "<tr><td>TX</td><td>" + r.freq_tx.toFixed(4) + " MHz</td></tr>";
|
||||
html += "<tr><td>" + t("popup_tx") + "</td><td>" + r.freq_tx.toFixed(4) + " MHz</td></tr>";
|
||||
if (r.freq_rx)
|
||||
html += "<tr><td>RX</td><td>" + r.freq_rx.toFixed(4) + " MHz</td></tr>";
|
||||
html += "<tr><td>" + t("popup_rx") + "</td><td>" + r.freq_rx.toFixed(4) + " MHz</td></tr>";
|
||||
if (r.freq_offset)
|
||||
html +=
|
||||
"<tr><td>Offset</td><td>" +
|
||||
"<tr><td>" + t("popup_offset") + "</td><td>" +
|
||||
escapeHtml(r.freq_offset) +
|
||||
" MHz</td></tr>";
|
||||
html += "<tr><td>CC</td><td>" + r.color_code + "</td></tr>";
|
||||
html += "<tr><td>" + t("popup_cc") + "</td><td>" + r.color_code + "</td></tr>";
|
||||
var loc = escapeHtml(r.city);
|
||||
if (r.state) loc += ", " + escapeHtml(r.state);
|
||||
if (r.country) loc += ", " + escapeHtml(r.country);
|
||||
html += "<tr><td>Location</td><td>" + loc + "</td></tr>";
|
||||
html += "<tr><td>" + t("popup_location") + "</td><td>" + loc + "</td></tr>";
|
||||
if (r.networks && r.networks.length)
|
||||
html +=
|
||||
"<tr><td>Network</td><td>" +
|
||||
"<tr><td>" + t("popup_network") + "</td><td>" +
|
||||
r.networks.map(escapeHtml).join(", ") +
|
||||
"</td></tr>";
|
||||
if (r.trustee)
|
||||
html +=
|
||||
"<tr><td>Trustee</td><td>" +
|
||||
"<tr><td>" + t("popup_trustee") + "</td><td>" +
|
||||
escapeHtml(r.trustee) +
|
||||
"</td></tr>";
|
||||
if (r.ts_linked)
|
||||
html +=
|
||||
"<tr><td>Timeslots</td><td>" +
|
||||
"<tr><td>" + t("popup_timeslots") + "</td><td>" +
|
||||
escapeHtml(r.ts_linked) +
|
||||
"</td></tr>";
|
||||
html +=
|
||||
"<tr><td>Status</td><td>" +
|
||||
"<tr><td>" + t("popup_status") + "</td><td>" +
|
||||
escapeHtml(r.status) +
|
||||
"</td></tr>";
|
||||
if (r.bm_status_text)
|
||||
html += "<tr><td>BM Status</td><td>" + escapeHtml(r.bm_status_text) + "</td></tr>";
|
||||
html += "<tr><td>" + t("popup_bm_status") + "</td><td>" + escapeHtml(r.bm_status_text) + "</td></tr>";
|
||||
if (r.last_seen)
|
||||
html += "<tr><td>Last seen</td><td>" + escapeHtml(r.last_seen.replace("T", " ").substring(0, 19)) + "</td></tr>";
|
||||
html += "<tr><td>" + t("popup_last_seen") + "</td><td>" + escapeHtml(r.last_seen.replace("T", " ").substring(0, 19)) + "</td></tr>";
|
||||
if (r.hardware)
|
||||
html += "<tr><td>Hardware</td><td>" + escapeHtml(r.hardware) + "</td></tr>";
|
||||
html += "<tr><td>" + t("popup_hardware") + "</td><td>" + escapeHtml(r.hardware) + "</td></tr>";
|
||||
if (r.pep)
|
||||
html += "<tr><td>Power</td><td>" + r.pep + " W</td></tr>";
|
||||
html += "<tr><td>" + t("popup_power") + "</td><td>" + r.pep + " W</td></tr>";
|
||||
if (r.agl)
|
||||
html += "<tr><td>Antenna</td><td>" + r.agl + " m AGL</td></tr>";
|
||||
html += "<tr><td>" + t("popup_antenna") + "</td><td>" + r.agl + " m AGL</td></tr>";
|
||||
if (r.inactive)
|
||||
html += '<tr><td></td><td style="color:#ef5350;font-weight:600">Inactive</td></tr>';
|
||||
html += '<tr><td></td><td style="color:#ef5350;font-weight:600">' + t("popup_inactive") + '</td></tr>';
|
||||
html += "</table></div>";
|
||||
return html;
|
||||
}
|
||||
|
|
@ -357,7 +357,7 @@
|
|||
.catch(function (err) {
|
||||
if (err.name === "AbortError") return;
|
||||
console.error("Fetch error:", err);
|
||||
showStatus("Error");
|
||||
showStatus(t("error"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +369,7 @@
|
|||
q: address,
|
||||
format: "json",
|
||||
limit: "1",
|
||||
"accept-language": "de",
|
||||
"accept-language": getLocale(),
|
||||
})
|
||||
)
|
||||
.then(function (resp) {
|
||||
|
|
@ -414,7 +414,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
showStatus("Loading...");
|
||||
showStatus(t("loading"));
|
||||
return fetch("/api/repeaters/route", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
|
@ -440,7 +440,7 @@
|
|||
})
|
||||
.catch(function (err) {
|
||||
console.error("Route fetch error:", err);
|
||||
showStatus("Error");
|
||||
showStatus(t("error"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -522,8 +522,8 @@
|
|||
if (isPinMode) clearPin();
|
||||
if (isSearchMode) clearSearch();
|
||||
routeBtn.disabled = true;
|
||||
routeBtn.innerHTML = '<span class="spinner"></span>Routing...';
|
||||
showStatus("Geocoding...");
|
||||
routeBtn.innerHTML = '<span class="spinner"></span>' + t("routing");
|
||||
showStatus(t("geocoding"));
|
||||
|
||||
geocode(fromAddr)
|
||||
.then(function (from) {
|
||||
|
|
@ -532,7 +532,7 @@
|
|||
});
|
||||
})
|
||||
.then(function (endpoints) {
|
||||
showStatus("Routing...");
|
||||
showStatus(t("routing"));
|
||||
return getRoute(endpoints.from, endpoints.to);
|
||||
})
|
||||
.then(function (latLngs) {
|
||||
|
|
@ -560,7 +560,7 @@
|
|||
})
|
||||
.then(function () {
|
||||
routeBtn.disabled = false;
|
||||
routeBtn.textContent = "Route";
|
||||
routeBtn.textContent = t("route_btn");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -596,7 +596,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
showStatus("Loading...");
|
||||
showStatus(t("loading"));
|
||||
var params = new URLSearchParams({
|
||||
lat: pinLatLng.lat,
|
||||
lng: pinLatLng.lng,
|
||||
|
|
@ -619,7 +619,7 @@
|
|||
})
|
||||
.catch(function (err) {
|
||||
console.error("Pin fetch error:", err);
|
||||
showStatus("Error");
|
||||
showStatus(t("error"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -912,7 +912,7 @@
|
|||
|
||||
if (searchController) searchController.abort();
|
||||
searchController = new AbortController();
|
||||
showStatus("Searching...");
|
||||
showStatus(t("searching"));
|
||||
|
||||
fetch("/api/repeaters/search?q=" + encodeURIComponent(q), { signal: searchController.signal })
|
||||
.then(function (resp) {
|
||||
|
|
@ -939,7 +939,7 @@
|
|||
if (data.total > data.count) {
|
||||
var msg = document.createElement("div");
|
||||
msg.className = "search-more-msg";
|
||||
msg.textContent = "Showing " + data.count + " of " + data.total + " results";
|
||||
msg.textContent = t("showing_results", { count: data.count, total: data.total });
|
||||
searchListEl.appendChild(msg);
|
||||
}
|
||||
searchListEl.style.display = "";
|
||||
|
|
@ -947,7 +947,7 @@
|
|||
.catch(function (err) {
|
||||
if (err.name === "AbortError") return;
|
||||
console.error("Search error:", err);
|
||||
showStatus("Error");
|
||||
showStatus(t("error"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1007,9 +1007,17 @@
|
|||
window.addEventListener("hashchange", checkHash);
|
||||
|
||||
// === Init ===
|
||||
function initApp() {
|
||||
if (window.location.hash) {
|
||||
checkHash();
|
||||
} else {
|
||||
fetchRepeaters();
|
||||
}
|
||||
}
|
||||
|
||||
if (i18next.isInitialized) {
|
||||
initApp();
|
||||
} else {
|
||||
document.addEventListener("i18n-ready", initApp);
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
38
static/i18n.js
Normal file
38
static/i18n.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
var SUPPORTED_LANGS = ["en", "de", "es", "fr"];
|
||||
|
||||
function translateDOM() {
|
||||
document.querySelectorAll("[data-i18n]").forEach(function (el) {
|
||||
el.textContent = i18next.t(el.getAttribute("data-i18n"));
|
||||
});
|
||||
document.querySelectorAll("[data-i18n-placeholder]").forEach(function (el) {
|
||||
el.placeholder = i18next.t(el.getAttribute("data-i18n-placeholder"));
|
||||
});
|
||||
}
|
||||
|
||||
function t(key, opts) {
|
||||
return i18next.t(key, opts);
|
||||
}
|
||||
|
||||
function getLocale() {
|
||||
return i18next.resolvedLanguage || "en";
|
||||
}
|
||||
|
||||
i18next
|
||||
.use(i18nextBrowserLanguageDetector)
|
||||
.use(i18nextHttpBackend)
|
||||
.init({
|
||||
supportedLngs: SUPPORTED_LANGS,
|
||||
fallbackLng: "en",
|
||||
detection: {
|
||||
order: ["querystring", "navigator"],
|
||||
lookupQuerystring: "lang",
|
||||
caches: [],
|
||||
},
|
||||
backend: {
|
||||
loadPath: "/locales/{{lng}}.json",
|
||||
},
|
||||
}, function () {
|
||||
document.documentElement.lang = getLocale();
|
||||
translateDOM();
|
||||
document.dispatchEvent(new Event("i18n-ready"));
|
||||
});
|
||||
|
|
@ -16,39 +16,39 @@
|
|||
<span id="count" class="count"></span>
|
||||
</div>
|
||||
<div class="controls-row search-row">
|
||||
<input type="text" id="search-input" placeholder="Search callsign, city, ID..." autocomplete="off" />
|
||||
<input type="text" id="search-input" data-i18n-placeholder="search_placeholder" placeholder="Search callsign, city, ID..." autocomplete="off" />
|
||||
<button type="button" id="search-clear" class="search-clear-btn" style="display:none">×</button>
|
||||
</div>
|
||||
<div id="search-list" class="pin-list" style="display:none"></div>
|
||||
<details class="advanced">
|
||||
<summary>Filters</summary>
|
||||
<summary data-i18n="filters">Filters</summary>
|
||||
<div class="controls-row network-row">
|
||||
<label class="net-label net-bm"><input type="checkbox" id="net-bm" checked> BM</label>
|
||||
<label class="net-label net-dmrplus"><input type="checkbox" id="net-dmrplus"> DMR+</label>
|
||||
<label class="net-label net-tgif"><input type="checkbox" id="net-tgif"> TGIF</label>
|
||||
<label class="net-label net-other"><input type="checkbox" id="net-other"> Other</label>
|
||||
</div>
|
||||
<label class="hotspot-label"><input type="checkbox" id="show-hotspots"> Show personal hotspots</label>
|
||||
<label class="hotspot-label"><input type="checkbox" id="show-inactive"> Show inactive repeaters</label>
|
||||
<label class="hotspot-label"><input type="checkbox" id="show-hotspots"> <span data-i18n="show_hotspots">Show personal hotspots</span></label>
|
||||
<label class="hotspot-label"><input type="checkbox" id="show-inactive"> <span data-i18n="show_inactive">Show inactive repeaters</span></label>
|
||||
</details>
|
||||
<details class="experimental">
|
||||
<summary>Experimental</summary>
|
||||
<label class="hotspot-label"><input type="checkbox" id="show-heatmap"> Visualize Last Heard Heatmap</label>
|
||||
<summary data-i18n="experimental">Experimental</summary>
|
||||
<label class="hotspot-label"><input type="checkbox" id="show-heatmap"> <span data-i18n="show_heatmap">Visualize Last Heard Heatmap</span></label>
|
||||
</details>
|
||||
<div class="controls-row route-row autocomplete-wrap">
|
||||
<input type="text" id="route-from" placeholder="From address" autocomplete="off" />
|
||||
<input type="text" id="route-from" data-i18n-placeholder="from_address" placeholder="From address" autocomplete="off" />
|
||||
<ul class="autocomplete-list" id="ac-from"></ul>
|
||||
</div>
|
||||
<div class="controls-row route-row autocomplete-wrap">
|
||||
<input type="text" id="route-to" placeholder="To address" autocomplete="off" />
|
||||
<input type="text" id="route-to" data-i18n-placeholder="to_address" placeholder="To address" autocomplete="off" />
|
||||
<ul class="autocomplete-list" id="ac-to"></ul>
|
||||
</div>
|
||||
<div class="controls-row route-row">
|
||||
<button id="route-btn" disabled>Route</button>
|
||||
<button id="clear-btn">Clear</button>
|
||||
<button id="route-btn" data-i18n="route_btn" disabled>Route</button>
|
||||
<button id="clear-btn" data-i18n="clear_btn">Clear</button>
|
||||
</div>
|
||||
<div class="controls-row route-row corridor-row" id="corridor-row" style="display:none">
|
||||
<label class="corridor-label">Corridor
|
||||
<label class="corridor-label"><span data-i18n="corridor">Corridor</span>
|
||||
<input type="range" id="corridor-range" min="1" max="50" value="10" />
|
||||
<span id="corridor-val">10</span> km
|
||||
</label>
|
||||
|
|
@ -56,11 +56,11 @@
|
|||
<div id="route-list" class="pin-list" style="display:none"></div>
|
||||
<div id="pin-controls" class="pin-controls" style="display:none">
|
||||
<div class="controls-row pin-header">
|
||||
<label class="pin-radius-label">Radius
|
||||
<label class="pin-radius-label"><span data-i18n="radius">Radius</span>
|
||||
<input type="range" id="pin-radius" min="10" max="200" value="50" step="10" />
|
||||
<span id="pin-radius-val">50</span> km
|
||||
</label>
|
||||
<button id="pin-clear" class="pin-clear-btn">Clear pin</button>
|
||||
<button id="pin-clear" class="pin-clear-btn" data-i18n="clear_pin">Clear pin</button>
|
||||
</div>
|
||||
<div id="pin-list" class="pin-list"></div>
|
||||
</div>
|
||||
|
|
@ -73,9 +73,13 @@
|
|||
Routing by <a href="https://project-osrm.org" target="_blank" rel="noopener">OSRM</a>.
|
||||
Map by <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a>.
|
||||
Built by <a rel="me" href="https://social.darc.de/@DA6KM" target="_blank" rel="noopener">DA6KM</a>.<br>
|
||||
<a href="/impressum.html">Impressum</a> · <a href="/datenschutz.html">Datenschutz</a> · <a href="https://git.kida.io/marcus/DMRmap" target="_blank" rel="noopener">Source</a><br>
|
||||
<a href="/impressum.html" id="link-impressum" data-i18n="impressum">Impressum</a> · <a href="/datenschutz.html" id="link-datenschutz" data-i18n="datenschutz">Datenschutz</a> · <a href="https://git.kida.io/marcus/DMRmap" target="_blank" rel="noopener" data-i18n="source">Source</a><br>
|
||||
</div>
|
||||
</div>
|
||||
<script src="lib/i18next.min.js"></script>
|
||||
<script src="lib/i18next-browser-languagedetector.min.js"></script>
|
||||
<script src="lib/i18next-http-backend.min.js"></script>
|
||||
<script src="i18n.js"></script>
|
||||
<script src="lib/leaflet.js"></script>
|
||||
<script src="lib/socket.io.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
|
|
|
|||
1
static/lib/i18next-browser-languagedetector.min.js
vendored
Normal file
1
static/lib/i18next-browser-languagedetector.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/lib/i18next-http-backend.min.js
vendored
Normal file
1
static/lib/i18next-http-backend.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
static/lib/i18next.min.js
vendored
Normal file
1
static/lib/i18next.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
42
static/locales/de.json
Normal file
42
static/locales/de.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"search_placeholder": "Rufzeichen, Stadt, ID suchen...",
|
||||
"filters": "Filter",
|
||||
"show_hotspots": "Persönliche Hotspots anzeigen",
|
||||
"show_inactive": "Inaktive Relais anzeigen",
|
||||
"experimental": "Experimentell",
|
||||
"show_heatmap": "Last Heard Heatmap anzeigen",
|
||||
"from_address": "Startadresse",
|
||||
"to_address": "Zieladresse",
|
||||
"route_btn": "Route",
|
||||
"clear_btn": "Löschen",
|
||||
"corridor": "Korridor",
|
||||
"radius": "Radius",
|
||||
"clear_pin": "Pin löschen",
|
||||
"repeater_count_one": "{{count}} Relais",
|
||||
"repeater_count_other": "{{count}} Relais",
|
||||
"loading": "Laden...",
|
||||
"searching": "Suche...",
|
||||
"geocoding": "Geocodierung...",
|
||||
"routing": "Routenberechnung...",
|
||||
"error": "Fehler",
|
||||
"showing_results": "{{count}} von {{total}} Ergebnissen",
|
||||
"popup_tx": "TX",
|
||||
"popup_rx": "RX",
|
||||
"popup_offset": "Offset",
|
||||
"popup_cc": "CC",
|
||||
"popup_location": "Standort",
|
||||
"popup_network": "Netzwerk",
|
||||
"popup_trustee": "Treuhänder",
|
||||
"popup_timeslots": "Zeitschlitze",
|
||||
"popup_status": "Status",
|
||||
"popup_bm_status": "BM Status",
|
||||
"popup_last_seen": "Zuletzt gesehen",
|
||||
"popup_hardware": "Hardware",
|
||||
"popup_power": "Leistung",
|
||||
"popup_antenna": "Antenne",
|
||||
"popup_inactive": "Inaktiv",
|
||||
"back_to_map": "Zur Karte",
|
||||
"impressum": "Impressum",
|
||||
"datenschutz": "Datenschutz",
|
||||
"source": "Quellcode"
|
||||
}
|
||||
42
static/locales/en.json
Normal file
42
static/locales/en.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"search_placeholder": "Search callsign, city, ID...",
|
||||
"filters": "Filters",
|
||||
"show_hotspots": "Show personal hotspots",
|
||||
"show_inactive": "Show inactive repeaters",
|
||||
"experimental": "Experimental",
|
||||
"show_heatmap": "Visualize Last Heard Heatmap",
|
||||
"from_address": "From address",
|
||||
"to_address": "To address",
|
||||
"route_btn": "Route",
|
||||
"clear_btn": "Clear",
|
||||
"corridor": "Corridor",
|
||||
"radius": "Radius",
|
||||
"clear_pin": "Clear pin",
|
||||
"repeater_count_one": "{{count}} repeater",
|
||||
"repeater_count_other": "{{count}} repeaters",
|
||||
"loading": "Loading...",
|
||||
"searching": "Searching...",
|
||||
"geocoding": "Geocoding...",
|
||||
"routing": "Routing...",
|
||||
"error": "Error",
|
||||
"showing_results": "Showing {{count}} of {{total}} results",
|
||||
"popup_tx": "TX",
|
||||
"popup_rx": "RX",
|
||||
"popup_offset": "Offset",
|
||||
"popup_cc": "CC",
|
||||
"popup_location": "Location",
|
||||
"popup_network": "Network",
|
||||
"popup_trustee": "Trustee",
|
||||
"popup_timeslots": "Timeslots",
|
||||
"popup_status": "Status",
|
||||
"popup_bm_status": "BM Status",
|
||||
"popup_last_seen": "Last seen",
|
||||
"popup_hardware": "Hardware",
|
||||
"popup_power": "Power",
|
||||
"popup_antenna": "Antenna",
|
||||
"popup_inactive": "Inactive",
|
||||
"back_to_map": "Back to map",
|
||||
"impressum": "Legal Notice",
|
||||
"datenschutz": "Privacy Policy",
|
||||
"source": "Source"
|
||||
}
|
||||
42
static/locales/es.json
Normal file
42
static/locales/es.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"search_placeholder": "Buscar indicativo, ciudad, ID...",
|
||||
"filters": "Filtros",
|
||||
"show_hotspots": "Mostrar hotspots personales",
|
||||
"show_inactive": "Mostrar repetidores inactivos",
|
||||
"experimental": "Experimental",
|
||||
"show_heatmap": "Visualizar mapa de calor Last Heard",
|
||||
"from_address": "Dirección de origen",
|
||||
"to_address": "Dirección de destino",
|
||||
"route_btn": "Ruta",
|
||||
"clear_btn": "Borrar",
|
||||
"corridor": "Corredor",
|
||||
"radius": "Radio",
|
||||
"clear_pin": "Borrar marcador",
|
||||
"repeater_count_one": "{{count}} repetidor",
|
||||
"repeater_count_other": "{{count}} repetidores",
|
||||
"loading": "Cargando...",
|
||||
"searching": "Buscando...",
|
||||
"geocoding": "Geocodificando...",
|
||||
"routing": "Calculando ruta...",
|
||||
"error": "Error",
|
||||
"showing_results": "Mostrando {{count}} de {{total}} resultados",
|
||||
"popup_tx": "TX",
|
||||
"popup_rx": "RX",
|
||||
"popup_offset": "Offset",
|
||||
"popup_cc": "CC",
|
||||
"popup_location": "Ubicación",
|
||||
"popup_network": "Red",
|
||||
"popup_trustee": "Responsable",
|
||||
"popup_timeslots": "Intervalos de tiempo",
|
||||
"popup_status": "Estado",
|
||||
"popup_bm_status": "Estado BM",
|
||||
"popup_last_seen": "Última vez visto",
|
||||
"popup_hardware": "Hardware",
|
||||
"popup_power": "Potencia",
|
||||
"popup_antenna": "Antena",
|
||||
"popup_inactive": "Inactivo",
|
||||
"back_to_map": "Volver al mapa",
|
||||
"impressum": "Aviso legal",
|
||||
"datenschutz": "Política de privacidad",
|
||||
"source": "Código fuente"
|
||||
}
|
||||
42
static/locales/fr.json
Normal file
42
static/locales/fr.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"search_placeholder": "Rechercher indicatif, ville, ID...",
|
||||
"filters": "Filtres",
|
||||
"show_hotspots": "Afficher les hotspots personnels",
|
||||
"show_inactive": "Afficher les relais inactifs",
|
||||
"experimental": "Expérimental",
|
||||
"show_heatmap": "Visualiser la carte thermique Last Heard",
|
||||
"from_address": "Adresse de départ",
|
||||
"to_address": "Adresse d'arrivée",
|
||||
"route_btn": "Itinéraire",
|
||||
"clear_btn": "Effacer",
|
||||
"corridor": "Corridor",
|
||||
"radius": "Rayon",
|
||||
"clear_pin": "Supprimer le marqueur",
|
||||
"repeater_count_one": "{{count}} relais",
|
||||
"repeater_count_other": "{{count}} relais",
|
||||
"loading": "Chargement...",
|
||||
"searching": "Recherche...",
|
||||
"geocoding": "Géocodage...",
|
||||
"routing": "Calcul de l'itinéraire...",
|
||||
"error": "Erreur",
|
||||
"showing_results": "Affichage de {{count}} sur {{total}} résultats",
|
||||
"popup_tx": "TX",
|
||||
"popup_rx": "RX",
|
||||
"popup_offset": "Offset",
|
||||
"popup_cc": "CC",
|
||||
"popup_location": "Emplacement",
|
||||
"popup_network": "Réseau",
|
||||
"popup_trustee": "Responsable",
|
||||
"popup_timeslots": "Créneaux horaires",
|
||||
"popup_status": "Statut",
|
||||
"popup_bm_status": "Statut BM",
|
||||
"popup_last_seen": "Dernière activité",
|
||||
"popup_hardware": "Matériel",
|
||||
"popup_power": "Puissance",
|
||||
"popup_antenna": "Antenne",
|
||||
"popup_inactive": "Inactif",
|
||||
"back_to_map": "Retour à la carte",
|
||||
"impressum": "Mentions légales",
|
||||
"datenschutz": "Politique de confidentialité",
|
||||
"source": "Code source"
|
||||
}
|
||||
Loading…
Reference in a new issue