diff --git a/client/src/components/Map/poiCategories.ts b/client/src/components/Map/poiCategories.ts index ebf83128b..0c8feee50 100644 --- a/client/src/components/Map/poiCategories.ts +++ b/client/src/components/Map/poiCategories.ts @@ -58,5 +58,17 @@ export interface Poi { phone: string | null opening_hours: string | null cuisine: string | null + /** + * What a charging station offers, when it is one and OSM says so. + * + * Optional twice over: only charging hits carry it at all, and OSM knows a socket type + * for roughly a third of them, a capacity for about seven in ten. "Not stated" is its + * own answer here and must not be read as a no. + */ + charging?: { + sockets: { type: string; count: number | null; kw: number | null }[] + capacity: number | null + fee: boolean | null + } | null source: 'openstreetmap' } diff --git a/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx b/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx index 7cb72d113..556e98934 100644 --- a/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx +++ b/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx @@ -91,6 +91,10 @@ function corridor(over: Partial = {}, search: Partial void } +/** + * How each socket family is written on the plug. + * + * Not translated and not derived from the tag: these are proper nouns with an accepted + * spelling, and "Type 2" as a lowercase tag key is not what anybody looks for on a + * charger. Anything not listed falls through to the raw key rather than being hidden, + * because an unknown socket is still worth seeing. + */ +/** The steps worth offering: a household socket, a fast AC post, and the two DC tiers. */ +const KW_STEPS = [11, 22, 50, 150] + +const SOCKET_LABEL: Record = { + type2: 'Type 2', + type2_combo: 'CCS', + type2_cable: 'Type 2 cable', + ccs: 'CCS', + chademo: 'CHAdeMO', + type1: 'Type 1', + type1_combo: 'CCS1', + schuko: 'Schuko', + tesla_supercharger: 'Supercharger', + tesla_destination: 'Tesla Destination', +} + /** Label and icon per category, from the one table every road-trip surface reads. */ const CATEGORY_META = CORRIDOR_CATEGORY_BY_KEY @@ -85,6 +109,24 @@ function ResultRow({ poi, onAdd }: { poi: CorridorPoi; onAdd?: () => void }): Re ? t('roadtrip.poi.atStart') : t('roadtrip.poi.alongRoute', { distance: formatDistance(poi.alongKm, distanceUnit) })} + {/* What the charger offers, where OSM says. Socket names are proper nouns and + stay as they are; the numbers around them are what decides whether a car can + use it at all. A station that says nothing shows nothing rather than a row + of dashes, because "not stated" is not "no". */} + {poi.charging?.sockets.length ? ( + + {poi.charging.sockets.slice(0, 3).map(s => ( + + {SOCKET_LABEL[s.type] ?? s.type} + {s.kw ? ` ${s.kw} kW` : ''} + {s.count && s.count > 1 ? ` ×${s.count}` : ''} + + ))} + + ) : null} + {poi.charging?.fee === false ? ( + {t('roadtrip.poi.free')} + ) : null} {/* Always there, quiet until the row is under the pointer: a button that only @@ -186,6 +228,18 @@ export default function RoadtripCorridorPanel({ corridor, routes, onAddPoi }: Ro // Something was found, the filter just hides it — a different state from "not searched // yet" and from "the drive really has none of these". + /** + * The socket families this search actually turned up, in the order the table lists + * them. Offering all ten would put CHAdeMO in the dropdown for a corridor that has + * three Type 2 posts on it, which is a filter that can only produce an empty list. + */ + const socketsFound = useMemo(() => { + const seen = new Set() + for (const p of search.results) for (const s of p.charging?.sockets ?? []) seen.add(s.type) + return [...seen].sort() + }, [search.results]) + const hasCharging = socketsFound.length > 0 + const filteredToNothing = search.results.length > 0 && corridor.visible.length === 0 const canSearch = !search.loading && corridor.categories.length > 0 && (corridor.day?.stops.length ?? 0) > 1 @@ -258,16 +312,18 @@ export default function RoadtripCorridorPanel({ corridor, routes, onAddPoi }: Ro + {/* One word, so it can be read at the size a primary action deserves. The panel + it sits in is headed "along the route" and every control above it narrows the + same search; repeating that on the button only made it small. */} ) : null} + + {/* Only where there is a charger to narrow. Two answers OSM actually carries + often enough to filter on: which plug, and how fast. A station that states + neither stays in the list, because roughly two thirds of them state no + power at all and reading that silence as "too slow" would empty the map. */} + {hasCharging ? ( +
+ corridor.setSocketFilter(String(value))} + options={[ + { value: '', label: t('roadtrip.poi.anySocket') }, + ...socketsFound.map(s => ({ value: s, label: SOCKET_LABEL[s] ?? s })), + ]} + style={{ flex: 1 }} + size="sm" + /> + corridor.setMinKw(Number(value))} + options={[ + { value: '0', label: t('roadtrip.poi.anyPower') }, + ...KW_STEPS.map(kw => ({ value: String(kw), label: `${kw}+ kW` })), + ]} + style={{ flex: 1 }} + size="sm" + /> +
+ ) : null} ) : null} diff --git a/client/src/components/Roadtrip/useRoadtripCorridor.ts b/client/src/components/Roadtrip/useRoadtripCorridor.ts index 73fa2edea..5e77c8c78 100644 --- a/client/src/components/Roadtrip/useRoadtripCorridor.ts +++ b/client/src/components/Roadtrip/useRoadtripCorridor.ts @@ -23,6 +23,12 @@ export interface RoadtripCorridor { search: CorridorSearch /** Narrows what was found by name or brand. Empty means everything. */ nameFilter: string + /** Socket family the charging hits are narrowed to, or empty for any. */ + socketFilter: string + setSocketFilter: (value: string) => void + /** Minimum kW for charging hits, or 0 for any. */ + minKw: number + setMinKw: (value: number) => void setNameFilter: (value: string) => void /** * What both the panel and the map show: the hits that match `nameFilter`. @@ -50,6 +56,10 @@ export function useRoadtripCorridor(routes: RoadtripRoutes): RoadtripCorridor { const [categories, setCategories] = useState(['fuel']) const [widthKm, setWidthKm] = useState(5) const [nameFilter, setNameFilter] = useState('') + /** A socket family, or empty for any. Only ever applied to charging hits. */ + const [socketFilter, setSocketFilter] = useState('') + /** Minimum kW, or 0 for any. */ + const [minKw, setMinKw] = useState(0) // Falls back to the first day with a drive, so the panel is useful before the user // has picked anything — and follows along when that day disappears. @@ -93,10 +103,26 @@ export function useRoadtripCorridor(routes: RoadtripRoutes): RoadtripCorridor { */ const visible = useMemo(() => { const needle = nameFilter.trim().toLowerCase() - if (!needle) return search.results - return search.results.filter(p => - p.name.toLowerCase().includes(needle) || (p.brand ?? '').toLowerCase().includes(needle)) - }, [search.results, nameFilter]) + // The same array when nothing narrows it, not a copy of it: the map redraws off this + // reference, and handing it a fresh array on every render moves every pin. + if (!needle && !socketFilter && !minKw) return search.results + return search.results.filter(p => { + if (needle && !(p.name.toLowerCase().includes(needle) || (p.brand ?? '').toLowerCase().includes(needle))) { + return false + } + // The charging filters only ever hide charging hits. A rest area does not have a + // socket and is not answering the question, so filtering the whole list by one + // would empty it of everything the search also found. + if (p.category !== 'charging') return true + if (socketFilter && !p.charging?.sockets.some(s => s.type === socketFilter)) return false + // A station that does not state its power is kept. Roughly two thirds of them do + // not, and reading silence as "too slow" would throw away most of the map. + if (minKw && p.charging?.sockets.some(s => s.kw != null) && !p.charging.sockets.some(s => (s.kw ?? 0) >= minKw)) { + return false + } + return true + }) + }, [search.results, nameFilter, socketFilter, minKw]) /** * Where a hit belongs in the day's chain, as an index among its stops. @@ -131,6 +157,10 @@ export function useRoadtripCorridor(routes: RoadtripRoutes): RoadtripCorridor { setWidthKm, search, nameFilter, + socketFilter, + setSocketFilter, + minKw, + setMinKw, setNameFilter, visible, insertIndexFor, diff --git a/server/src/nest/maps/maps.helpers.ts b/server/src/nest/maps/maps.helpers.ts index 07b9d1f01..ded24a48e 100644 --- a/server/src/nest/maps/maps.helpers.ts +++ b/server/src/nest/maps/maps.helpers.ts @@ -264,6 +264,8 @@ export interface OverpassPoi { /** Brand name and its Wikidata id, when OSM carries them — the logo is looked up from the id. */ brand: string | null; brand_wikidata: string | null; + /** What a charging station offers, when it is one and OSM says. */ + charging: ChargingInfo | null; /** * Which index the row came from. Overture is not OpenStreetMap: it carries * OSM among other sources under other licences, so a row from the TREK index @@ -273,6 +275,72 @@ export interface OverpassPoi { source: 'openstreetmap' | 'trek-places'; } +/** + * The part of a charging station that decides whether it is any use to a particular car. + * + * Read out of tags the query has always returned and the projection has always thrown + * away: `out center tags` hands back the whole tag set, and only six keys of it were ever + * passed on. Nothing here costs an extra request. + * + * Coverage is the reason this is all optional. Across the charging stations in OSM, + * roughly a third carry a socket type, about seven in ten a capacity, and about half say + * whether they charge a fee. A filter built on it has to treat "not stated" as its own + * answer rather than as a no. + */ +export interface ChargingInfo { + /** One entry per socket family the station lists, with how many and how fast. */ + sockets: { type: string; count: number | null; kw: number | null }[]; + /** How many vehicles can charge at once, across all sockets. */ + capacity: number | null; + /** true = costs money, false = free, null = OSM does not say. */ + fee: boolean | null; +} + +/** + * OSM writes sockets as one key per family: `socket:type2=4` is the count, and + * `socket:type2:output=22 kW` the power. Both are free text in practice, so the count is + * only taken when it parses as a whole number and the power only when a number can be + * read off the front of it. + * + * The families are listed rather than derived from the tag names, because `socket:` also + * carries keys that are not a socket family at all. + */ +const SOCKET_FAMILIES = [ + 'type2', 'type2_combo', 'type2_cable', 'ccs', 'chademo', 'type1', 'type1_combo', + 'schuko', 'tesla_supercharger', 'tesla_destination', +] as const; + +/** Leading number out of a free-text value like "22 kW" or "50kw". */ +function leadingNumber(raw: string | undefined): number | null { + if (!raw) return null; + const n = Number.parseFloat(raw.replace(',', '.')); + return Number.isFinite(n) && n > 0 ? n : null; +} + +export function readChargingInfo(tags: Record): ChargingInfo | null { + const sockets: ChargingInfo['sockets'] = []; + for (const family of SOCKET_FAMILIES) { + const raw = tags[`socket:${family}`]; + if (raw === undefined) continue; + const count = Number.parseInt(raw, 10); + sockets.push({ + type: family, + count: Number.isInteger(count) && count > 0 ? count : null, + kw: leadingNumber(tags[`socket:${family}:output`]), + }); + } + const capacity = Number.parseInt(tags.capacity ?? '', 10); + const fee = tags.fee === 'yes' ? true : tags.fee === 'no' ? false : null; + const info: ChargingInfo = { + sockets, + capacity: Number.isInteger(capacity) && capacity > 0 ? capacity : null, + fee, + }; + // Nothing said is null rather than an empty shell, so the client can tell "no data" + // from "no sockets" without inspecting three fields. + return sockets.length || info.capacity !== null || fee !== null ? info : null; +} + // Each pill category → the OSM tag selectors it searches. Keys here are the // contract with the client's POI_CATEGORIES (same keys, label/icon/colour live // client-side). diff --git a/server/src/nest/maps/maps.mcp.ts b/server/src/nest/maps/maps.mcp.ts index ef5c939e1..cc0abc281 100644 --- a/server/src/nest/maps/maps.mcp.ts +++ b/server/src/nest/maps/maps.mcp.ts @@ -89,7 +89,7 @@ export class MapsMcp { */ @Tool({ name: 'search_pois', - description: 'List points of interest of one category inside a map rectangle, with address, opening hours, website, phone and cuisine wherever the source carries them. Answers from the TREK place index where it can and from OpenStreetMap otherwise; each result names which one it came from in its `source` field. This is the discovery tool: use it to answer "what is around here" for a neighbourhood or a whole city district. Prefer search_place when the user already named the place they mean. Never calls Google, so it costs nothing and works on an instance with no Places key.', + description: 'List points of interest of one category inside a map rectangle, with address, opening hours, website, phone and cuisine wherever the source carries them. Answers from the TREK place index where it can and from OpenStreetMap otherwise; each result names which one it came from in its `source` field. A charging station also reports its sockets: which plug families it has, how many of each and at what power, plus how many vehicles fit and whether it charges a fee. OSM states a socket type on roughly a third of them and a power on fewer, so treat a missing figure as unknown rather than as absent. This is the discovery tool: use it to answer "what is around here" for a neighbourhood or a whole city district. Prefer search_place when the user already named the place they mean. Never calls Google, so it costs nothing and works on an instance with no Places key.', inputSchema: { category: z.union([ z.enum(POI_CATEGORY_KEYS), diff --git a/server/src/nest/maps/maps.service.ts b/server/src/nest/maps/maps.service.ts index bf022ef51..a21741073 100644 --- a/server/src/nest/maps/maps.service.ts +++ b/server/src/nest/maps/maps.service.ts @@ -46,6 +46,7 @@ import { haversineMetres, namesOverlap, mergeSearchResults, + readChargingInfo, type GoogleOpeningHours, type OverpassPoi, } from './maps.helpers'; @@ -869,6 +870,10 @@ export class MapsService { // gets its own mark here exactly as it does on the Overpass path. brand: p.brand?.name ?? null, brand_wikidata: p.brand?.wikidata ?? null, + // Sockets are an OSM thing; the index has no charging fields, so + // a station answered from here reports "not stated" rather than + // claiming it offers nothing. + charging: null, // The index has no cuisine field, so this null is the truth // rather than a field being dropped on the way through. cuisine: null, @@ -1273,6 +1278,10 @@ export class MapsService { // Only the plain Q-id form is passed on; anything else would be a lookup we // would have to guess at. brand_wikidata: /^Q[0-9]+$/.test(tags['brand:wikidata'] || '') ? tags['brand:wikidata'] : null, + // Only where it means something. Every POI carries `capacity` and `fee` for its + // own reasons — a restaurant's capacity is seats — so reading them as charging + // data anywhere else would be wrong on most of the map. + charging: categoryOfFilter.get(matched) === 'charging' ? readChargingInfo(tags) : null, source: 'openstreetmap', }); } diff --git a/server/tests/unit/nest/maps.helpers.test.ts b/server/tests/unit/nest/maps.helpers.test.ts new file mode 100644 index 000000000..b6409b487 --- /dev/null +++ b/server/tests/unit/nest/maps.helpers.test.ts @@ -0,0 +1,64 @@ +import { describe, it, expect } from 'vitest'; +import { readChargingInfo } from '../../../src/nest/maps/maps.helpers'; + +/** + * The tag reading that turns an Overpass answer into something a traveller can filter on. + * + * Pure, so it is tested on its own: no request, no cache, no service. The query already + * asks for the whole tag set, and every one of these fields was being dropped on the way + * through. + */ +describe('readChargingInfo', () => { + // The query has always asked for `out center tags` and the projection has always + // thrown these away. Nothing here costs an extra request. + it('MAPS-CHARGE-001: reads the socket families, their count and their power', () => { + expect(readChargingInfo({ + 'socket:type2': '4', + 'socket:type2:output': '22 kW', + 'socket:ccs': '2', + 'socket:ccs:output': '150', + })).toEqual({ + sockets: [ + { type: 'type2', count: 4, kw: 22 }, + { type: 'ccs', count: 2, kw: 150 }, + ], + capacity: null, + fee: null, + }); + }); + + it('MAPS-CHARGE-002: a socket that states nothing beyond its presence still counts', () => { + // `socket:type2=yes` is common. The family is the useful part; the rest is unknown + // rather than zero, and a filter has to be able to tell those apart. + expect(readChargingInfo({ 'socket:type2': 'yes' })?.sockets).toEqual([ + { type: 'type2', count: null, kw: null }, + ]); + }); + + it('MAPS-CHARGE-003: free text power is read off the front, comma or not', () => { + expect(readChargingInfo({ 'socket:type2': '1', 'socket:type2:output': '11kW' })?.sockets[0].kw).toBe(11); + expect(readChargingInfo({ 'socket:type2': '1', 'socket:type2:output': '2,3 kW' })?.sockets[0].kw).toBe(2.3); + expect(readChargingInfo({ 'socket:type2': '1', 'socket:type2:output': 'unknown' })?.sockets[0].kw).toBeNull(); + }); + + it('MAPS-CHARGE-004: fee is three answers, not two', () => { + expect(readChargingInfo({ fee: 'yes' })?.fee).toBe(true); + expect(readChargingInfo({ fee: 'no' })?.fee).toBe(false); + // Half the charging stations in OSM say nothing about it, and "not stated" is not + // "free". A value nobody can act on is read as unknown; with a socket alongside it + // the station still reports, it just does not claim a price. + expect(readChargingInfo({ 'socket:type2': '2', fee: 'interval' })?.fee).toBeNull(); + // On its own it says nothing at all, so there is nothing to report. + expect(readChargingInfo({ fee: 'interval' })).toBeNull(); + }); + + it('MAPS-CHARGE-005: a station that says nothing at all is null, not an empty shell', () => { + // So the client can tell "no data" from "no sockets" without inspecting three fields. + expect(readChargingInfo({ amenity: 'charging_station', name: 'Ionity' })).toBeNull(); + }); + + it('MAPS-CHARGE-006: a nonsense count is dropped rather than written through', () => { + expect(readChargingInfo({ 'socket:type2': '0' })?.sockets[0].count).toBeNull(); + expect(readChargingInfo({ capacity: '-2' })).toBeNull(); + }); +}); diff --git a/shared/src/i18n/ar/roadtrip.ts b/shared/src/i18n/ar/roadtrip.ts index 1e777e015..b94e62d4b 100644 --- a/shared/src/i18n/ar/roadtrip.ts +++ b/shared/src/i18n/ar/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'اجعله توقفاً في الطريق', 'roadtrip.stop.changeKind': 'تغيير نوع التوقف', 'roadtrip.stop.backToDestination': 'العودة إلى وجهة', + 'roadtrip.poi.anySocket': 'أي قابس', + 'roadtrip.poi.anyPower': 'أي قدرة', + 'roadtrip.poi.free': 'مجاني', 'roadtrip.poi.rest': 'استراحة', 'roadtrip.poi.campsite': 'مخيم', 'roadtrip.poi.food': 'طعام', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'لا يوجد على الطريق ما يطابق «{name}».', 'roadtrip.poi.foundFiltered': '{count} من {total} على الطريق', 'roadtrip.poi.truncated': 'في {count} من المقاطع كان هناك أكثر مما تتسع له استجابة واحدة — قلّص الممر لرؤية البقية.', - 'roadtrip.poi.search': 'ابحث على طول المسار', + 'roadtrip.poi.search': 'بحث', 'roadtrip.poi.searching': 'جارٍ البحث {done} من {total}', 'roadtrip.poi.capped': 'المسار طويل — تم البحث في الجزء الأول فقط.', 'roadtrip.poi.partial': 'تعذّر البحث في {count} من المقاطع — لم يستجب البحث عن الأماكن.', diff --git a/shared/src/i18n/br/roadtrip.ts b/shared/src/i18n/br/roadtrip.ts index 85d0e33f1..0633bcfd4 100644 --- a/shared/src/i18n/br/roadtrip.ts +++ b/shared/src/i18n/br/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Tornar uma parada no caminho', 'roadtrip.stop.changeKind': 'Alterar o tipo de parada', 'roadtrip.stop.backToDestination': 'Voltar a ser um destino', + 'roadtrip.poi.anySocket': 'Qualquer tomada', + 'roadtrip.poi.anyPower': 'Qualquer potência', + 'roadtrip.poi.free': 'Gratuito', 'roadtrip.poi.rest': 'Área de descanso', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Comida', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Nada no trajeto corresponde a “{name}”.', 'roadtrip.poi.foundFiltered': '{count} de {total} no trajeto', 'roadtrip.poi.truncated': 'Em {count} trechos havia mais do que cabe em uma resposta — estreite o corredor para ver o restante.', - 'roadtrip.poi.search': 'Buscar ao longo da rota', + 'roadtrip.poi.search': 'Buscar', 'roadtrip.poi.searching': 'Buscando {done} de {total}', 'roadtrip.poi.capped': 'A rota é longa — apenas o primeiro trecho foi pesquisado.', 'roadtrip.poi.partial': 'Não foi possível pesquisar {count} trechos — a busca de locais não respondeu.', diff --git a/shared/src/i18n/ca/roadtrip.ts b/shared/src/i18n/ca/roadtrip.ts index c85143320..46fd31476 100644 --- a/shared/src/i18n/ca/roadtrip.ts +++ b/shared/src/i18n/ca/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Converteix en parada del camí', 'roadtrip.stop.changeKind': 'Canvia el tipus de parada', 'roadtrip.stop.backToDestination': 'Torna a ser una destinació', + 'roadtrip.poi.anySocket': 'Qualsevol connector', + 'roadtrip.poi.anyPower': 'Qualsevol potència', + 'roadtrip.poi.free': 'Gratuït', 'roadtrip.poi.rest': 'Àrea de descans', 'roadtrip.poi.campsite': 'Càmping', 'roadtrip.poi.food': 'Menjar', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Res del trajecte no coincideix amb «{name}».', 'roadtrip.poi.foundFiltered': '{count} de {total} al trajecte', 'roadtrip.poi.truncated': 'En {count} trams hi havia més del que cap en una resposta: estreny el corredor per veure la resta.', - 'roadtrip.poi.search': 'Cerca al llarg de la ruta', + 'roadtrip.poi.search': 'Cerca', 'roadtrip.poi.searching': 'Cercant {done} de {total}', 'roadtrip.poi.capped': 'La ruta és llarga: només s’ha cercat el primer tram.', 'roadtrip.poi.partial': 'No s’han pogut cercar {count} trams: la cerca de llocs no ha respost.', diff --git a/shared/src/i18n/cs/roadtrip.ts b/shared/src/i18n/cs/roadtrip.ts index 81fade817..3ef776484 100644 --- a/shared/src/i18n/cs/roadtrip.ts +++ b/shared/src/i18n/cs/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Změnit na zastávku po cestě', 'roadtrip.stop.changeKind': 'Změnit typ zastávky', 'roadtrip.stop.backToDestination': 'Zpět na cíl cesty', + 'roadtrip.poi.anySocket': 'Libovolná zásuvka', + 'roadtrip.poi.anyPower': 'Libovolný výkon', + 'roadtrip.poi.free': 'Zdarma', 'roadtrip.poi.rest': 'Odpočívadlo', 'roadtrip.poi.campsite': 'Kemp', 'roadtrip.poi.food': 'Jídlo', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Na trase nic neodpovídá „{name}“.', 'roadtrip.poi.foundFiltered': '{count} z {total} na trase', 'roadtrip.poi.truncated': 'Na {count} úsecích jich bylo více, než se vejde do jedné odpovědi — zužte koridor, abyste viděli zbytek.', - 'roadtrip.poi.search': 'Hledat podél trasy', + 'roadtrip.poi.search': 'Hledat', 'roadtrip.poi.searching': 'Hledání {done} z {total}', 'roadtrip.poi.capped': 'Trasa je dlouhá — prohledán byl jen první úsek.', 'roadtrip.poi.partial': '{count} úseků se nepodařilo prohledat — vyhledávání míst neodpovědělo.', diff --git a/shared/src/i18n/de/roadtrip.ts b/shared/src/i18n/de/roadtrip.ts index 0a318c59a..0dce5a690 100644 --- a/shared/src/i18n/de/roadtrip.ts +++ b/shared/src/i18n/de/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Zum Halt unterwegs machen', 'roadtrip.stop.changeKind': 'Art des Halts ändern', 'roadtrip.stop.backToDestination': 'Wieder ein Ziel', + 'roadtrip.poi.anySocket': 'Alle Stecker', + 'roadtrip.poi.anyPower': 'Alle Leistungen', + 'roadtrip.poi.free': 'Kostenlos', 'roadtrip.poi.rest': 'Rastplatz', 'roadtrip.poi.campsite': 'Campingplatz', 'roadtrip.poi.food': 'Essen', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Unterwegs passt nichts zu „{name}“.', 'roadtrip.poi.foundFiltered': '{count} von {total} unterwegs', 'roadtrip.poi.truncated': 'Auf {count} Abschnitten gab es mehr, als in eine Antwort passt — den Korridor schmaler machen, um den Rest zu sehen.', - 'roadtrip.poi.search': 'Entlang der Route suchen', + 'roadtrip.poi.search': 'Suchen', 'roadtrip.poi.searching': 'Suche {done} von {total}', 'roadtrip.poi.capped': 'Die Route ist lang — nur der erste Abschnitt wurde durchsucht.', 'roadtrip.poi.partial': '{count} Abschnitte konnten nicht durchsucht werden — die Ortssuche hat nicht geantwortet.', diff --git a/shared/src/i18n/en/roadtrip.ts b/shared/src/i18n/en/roadtrip.ts index 16243b2e8..8da570095 100644 --- a/shared/src/i18n/en/roadtrip.ts +++ b/shared/src/i18n/en/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Make it a stop on the way', 'roadtrip.stop.changeKind': 'Change what kind of stop this is', 'roadtrip.stop.backToDestination': 'Back to a destination', + 'roadtrip.poi.anySocket': 'Any plug', + 'roadtrip.poi.anyPower': 'Any power', + 'roadtrip.poi.free': 'Free', 'roadtrip.poi.rest': 'Rest area', 'roadtrip.poi.campsite': 'Campsite', 'roadtrip.poi.food': 'Food', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Nothing on the way matches “{name}”.', 'roadtrip.poi.foundFiltered': '{count} of {total} on the way', 'roadtrip.poi.truncated': '{count} stretches had more than fits in one answer — narrow the corridor to see the rest.', - 'roadtrip.poi.search': 'Search along the route', + 'roadtrip.poi.search': 'Search', 'roadtrip.poi.searching': 'Searching {done} of {total}', 'roadtrip.poi.capped': 'The route is long — only the first stretch was searched.', 'roadtrip.poi.partial': '{count} stretches could not be searched — the place search did not answer.', diff --git a/shared/src/i18n/es/roadtrip.ts b/shared/src/i18n/es/roadtrip.ts index 635b126f5..ff7bb41b5 100644 --- a/shared/src/i18n/es/roadtrip.ts +++ b/shared/src/i18n/es/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Convertir en parada del camino', 'roadtrip.stop.changeKind': 'Cambiar el tipo de parada', 'roadtrip.stop.backToDestination': 'Volver a ser un destino', + 'roadtrip.poi.anySocket': 'Cualquier conector', + 'roadtrip.poi.anyPower': 'Cualquier potencia', + 'roadtrip.poi.free': 'Gratis', 'roadtrip.poi.rest': 'Área de descanso', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Comida', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Nada en la ruta coincide con «{name}».', 'roadtrip.poi.foundFiltered': '{count} de {total} en la ruta', 'roadtrip.poi.truncated': 'En {count} tramos había más de lo que cabe en una respuesta: reduce el corredor para ver el resto.', - 'roadtrip.poi.search': 'Buscar a lo largo de la ruta', + 'roadtrip.poi.search': 'Buscar', 'roadtrip.poi.searching': 'Buscando {done} de {total}', 'roadtrip.poi.capped': 'La ruta es larga: solo se buscó en el primer tramo.', 'roadtrip.poi.partial': 'No se pudieron buscar {count} tramos: la búsqueda de lugares no respondió.', diff --git a/shared/src/i18n/fr/roadtrip.ts b/shared/src/i18n/fr/roadtrip.ts index eceae62ba..711f375ae 100644 --- a/shared/src/i18n/fr/roadtrip.ts +++ b/shared/src/i18n/fr/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'En faire un arrêt en chemin', 'roadtrip.stop.changeKind': 'Changer le type d’arrêt', 'roadtrip.stop.backToDestination': 'Redevenir une destination', + 'roadtrip.poi.anySocket': 'Toute prise', + 'roadtrip.poi.anyPower': 'Toute puissance', + 'roadtrip.poi.free': 'Gratuit', 'roadtrip.poi.rest': 'Aire de repos', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Restauration', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Rien sur le trajet ne correspond à « {name} ».', 'roadtrip.poi.foundFiltered': '{count} sur {total} sur le trajet', 'roadtrip.poi.truncated': 'Sur {count} tronçons, il y en avait plus que ce qu’une réponse peut contenir : réduisez le corridor pour voir le reste.', - 'roadtrip.poi.search': 'Chercher le long du trajet', + 'roadtrip.poi.search': 'Rechercher', 'roadtrip.poi.searching': 'Recherche {done} sur {total}', 'roadtrip.poi.capped': 'L’itinéraire est long : seule la première portion a été parcourue.', 'roadtrip.poi.partial': '{count} tronçons n’ont pas pu être parcourus : la recherche de lieux n’a pas répondu.', diff --git a/shared/src/i18n/gr/roadtrip.ts b/shared/src/i18n/gr/roadtrip.ts index 636d53954..bbc90ad4c 100644 --- a/shared/src/i18n/gr/roadtrip.ts +++ b/shared/src/i18n/gr/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Μετατροπή σε στάση στον δρόμο', 'roadtrip.stop.changeKind': 'Αλλαγή είδους στάσης', 'roadtrip.stop.backToDestination': 'Πίσω σε προορισμό', + 'roadtrip.poi.anySocket': 'Κάθε βύσμα', + 'roadtrip.poi.anyPower': 'Κάθε ισχύς', + 'roadtrip.poi.free': 'Δωρεάν', 'roadtrip.poi.rest': 'Χώρος ανάπαυσης', 'roadtrip.poi.campsite': 'Κάμπινγκ', 'roadtrip.poi.food': 'Φαγητό', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Τίποτα στη διαδρομή δεν ταιριάζει με «{name}».', 'roadtrip.poi.foundFiltered': '{count} από {total} στη διαδρομή', 'roadtrip.poi.truncated': 'Σε {count} τμήματα υπήρχαν περισσότερα από όσα χωρούν σε μία απάντηση — στενέψτε τον διάδρομο για να δείτε τα υπόλοιπα.', - 'roadtrip.poi.search': 'Αναζήτηση κατά μήκος της διαδρομής', + 'roadtrip.poi.search': 'Αναζήτηση', 'roadtrip.poi.searching': 'Αναζήτηση {done} από {total}', 'roadtrip.poi.capped': 'Η διαδρομή είναι μεγάλη — αναζητήθηκε μόνο το πρώτο τμήμα.', 'roadtrip.poi.partial': 'Δεν ήταν δυνατή η αναζήτηση σε {count} τμήματα — η αναζήτηση τοποθεσιών δεν απάντησε.', diff --git a/shared/src/i18n/hu/roadtrip.ts b/shared/src/i18n/hu/roadtrip.ts index e9669ede2..1ba6c03c6 100644 --- a/shared/src/i18n/hu/roadtrip.ts +++ b/shared/src/i18n/hu/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Legyen útközbeni megálló', 'roadtrip.stop.changeKind': 'Megálló típusának módosítása', 'roadtrip.stop.backToDestination': 'Vissza úti céllá', + 'roadtrip.poi.anySocket': 'Bármely csatlakozó', + 'roadtrip.poi.anyPower': 'Bármely teljesítmény', + 'roadtrip.poi.free': 'Ingyenes', 'roadtrip.poi.rest': 'Pihenőhely', 'roadtrip.poi.campsite': 'Kemping', 'roadtrip.poi.food': 'Étkezés', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Az útvonalon semmi sem egyezik ezzel: „{name}”.', 'roadtrip.poi.foundFiltered': '{count} / {total} az útvonalon', 'roadtrip.poi.truncated': '{count} szakaszon több volt, mint amennyi egy válaszba fér — szűkítse a folyosót a többi megtekintéséhez.', - 'roadtrip.poi.search': 'Keresés az útvonal mentén', + 'roadtrip.poi.search': 'Keresés', 'roadtrip.poi.searching': 'Keresés {done}/{total}', 'roadtrip.poi.capped': 'Az útvonal hosszú – csak az első szakaszon kerestünk.', 'roadtrip.poi.partial': '{count} szakaszon nem sikerült keresni – a helykeresés nem válaszolt.', diff --git a/shared/src/i18n/id/roadtrip.ts b/shared/src/i18n/id/roadtrip.ts index e161af9d2..db1ff47b1 100644 --- a/shared/src/i18n/id/roadtrip.ts +++ b/shared/src/i18n/id/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Jadikan perhentian di jalan', 'roadtrip.stop.changeKind': 'Ubah jenis perhentian', 'roadtrip.stop.backToDestination': 'Kembali jadi tujuan', + 'roadtrip.poi.anySocket': 'Semua colokan', + 'roadtrip.poi.anyPower': 'Semua daya', + 'roadtrip.poi.free': 'Gratis', 'roadtrip.poi.rest': 'Tempat istirahat', 'roadtrip.poi.campsite': 'Perkemahan', 'roadtrip.poi.food': 'Makanan', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Tidak ada di rute yang cocok dengan “{name}”.', 'roadtrip.poi.foundFiltered': '{count} dari {total} di rute', 'roadtrip.poi.truncated': 'Di {count} ruas ada lebih banyak daripada yang muat dalam satu jawaban — persempit koridor untuk melihat sisanya.', - 'roadtrip.poi.search': 'Cari di sepanjang rute', + 'roadtrip.poi.search': 'Cari', 'roadtrip.poi.searching': 'Mencari {done} dari {total}', 'roadtrip.poi.capped': 'Rute panjang — hanya bagian pertama yang dicari.', 'roadtrip.poi.partial': '{count} ruas tidak dapat dicari — pencarian tempat tidak merespons.', diff --git a/shared/src/i18n/it/roadtrip.ts b/shared/src/i18n/it/roadtrip.ts index 93103a10a..9b50a916c 100644 --- a/shared/src/i18n/it/roadtrip.ts +++ b/shared/src/i18n/it/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Trasforma in sosta lungo il percorso', 'roadtrip.stop.changeKind': 'Cambia il tipo di sosta', 'roadtrip.stop.backToDestination': 'Torna a essere una destinazione', + 'roadtrip.poi.anySocket': 'Qualsiasi presa', + 'roadtrip.poi.anyPower': 'Qualsiasi potenza', + 'roadtrip.poi.free': 'Gratuito', 'roadtrip.poi.rest': 'Area di sosta', 'roadtrip.poi.campsite': 'Campeggio', 'roadtrip.poi.food': 'Ristoro', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Lungo il percorso non c’è nulla che corrisponda a “{name}”.', 'roadtrip.poi.foundFiltered': '{count} di {total} lungo il percorso', 'roadtrip.poi.truncated': 'Su {count} tratti ce n’erano più di quanti ne stiano in una risposta: restringi il corridoio per vedere il resto.', - 'roadtrip.poi.search': 'Cerca lungo il percorso', + 'roadtrip.poi.search': 'Cerca', 'roadtrip.poi.searching': 'Ricerca {done} di {total}', 'roadtrip.poi.capped': 'Il percorso è lungo: è stato cercato solo il primo tratto.', 'roadtrip.poi.partial': 'Non è stato possibile cercare in {count} tratti: la ricerca dei luoghi non ha risposto.', diff --git a/shared/src/i18n/ja/roadtrip.ts b/shared/src/i18n/ja/roadtrip.ts index 718472611..7158f518d 100644 --- a/shared/src/i18n/ja/roadtrip.ts +++ b/shared/src/i18n/ja/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': '途中の立ち寄りにする', 'roadtrip.stop.changeKind': '立ち寄りの種類を変更', 'roadtrip.stop.backToDestination': '目的地に戻す', + 'roadtrip.poi.anySocket': 'すべてのプラグ', + 'roadtrip.poi.anyPower': 'すべての出力', + 'roadtrip.poi.free': '無料', 'roadtrip.poi.rest': 'サービスエリア', 'roadtrip.poi.campsite': 'キャンプ場', 'roadtrip.poi.food': '食事', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'ルート沿いに「{name}」に一致するものはありません。', 'roadtrip.poi.foundFiltered': 'ルート沿いの {total} 件中 {count} 件', 'roadtrip.poi.truncated': '{count} 区間で一度に返せる件数を超えました。回廊を狭めると残りを確認できます。', - 'roadtrip.poi.search': 'ルート沿いを検索', + 'roadtrip.poi.search': '検索', 'roadtrip.poi.searching': '検索中 {done}/{total}', 'roadtrip.poi.capped': 'ルートが長いため、最初の区間のみ検索しました。', 'roadtrip.poi.partial': '{count} 区間を検索できませんでした。場所の検索が応答しませんでした。', diff --git a/shared/src/i18n/ko/roadtrip.ts b/shared/src/i18n/ko/roadtrip.ts index d94e66a0b..3a515c9fe 100644 --- a/shared/src/i18n/ko/roadtrip.ts +++ b/shared/src/i18n/ko/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': '경유 정차로 변경', 'roadtrip.stop.changeKind': '정차 유형 변경', 'roadtrip.stop.backToDestination': '다시 목적지로', + 'roadtrip.poi.anySocket': '모든 플러그', + 'roadtrip.poi.anyPower': '모든 출력', + 'roadtrip.poi.free': '무료', 'roadtrip.poi.rest': '휴게소', 'roadtrip.poi.campsite': '캠핑장', 'roadtrip.poi.food': '식사', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': '경로상에 “{name}”와(과) 일치하는 곳이 없습니다.', 'roadtrip.poi.foundFiltered': '경로상 {total}곳 중 {count}곳', 'roadtrip.poi.truncated': '{count}개 구간에서 한 번에 반환할 수 있는 수를 넘었습니다. 회랑을 좁히면 나머지를 볼 수 있습니다.', - 'roadtrip.poi.search': '경로 주변 검색', + 'roadtrip.poi.search': '검색', 'roadtrip.poi.searching': '검색 중 {done}/{total}', 'roadtrip.poi.capped': '경로가 길어 앞부분만 검색했습니다.', 'roadtrip.poi.partial': '{count}개 구간을 검색하지 못했습니다. 장소 검색이 응답하지 않았습니다.', diff --git a/shared/src/i18n/nl/roadtrip.ts b/shared/src/i18n/nl/roadtrip.ts index 68625fc71..4aec8ac2b 100644 --- a/shared/src/i18n/nl/roadtrip.ts +++ b/shared/src/i18n/nl/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Maak er een stop onderweg van', 'roadtrip.stop.changeKind': 'Soort stop wijzigen', 'roadtrip.stop.backToDestination': 'Weer een bestemming', + 'roadtrip.poi.anySocket': 'Elke stekker', + 'roadtrip.poi.anyPower': 'Elk vermogen', + 'roadtrip.poi.free': 'Gratis', 'roadtrip.poi.rest': 'Rustplaats', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Eten', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Niets onderweg komt overeen met “{name}”.', 'roadtrip.poi.foundFiltered': '{count} van {total} onderweg', 'roadtrip.poi.truncated': 'Op {count} stukken waren er meer dan in één antwoord passen — maak de corridor smaller om de rest te zien.', - 'roadtrip.poi.search': 'Langs de route zoeken', + 'roadtrip.poi.search': 'Zoeken', 'roadtrip.poi.searching': 'Zoeken {done} van {total}', 'roadtrip.poi.capped': 'De route is lang — alleen het eerste deel is doorzocht.', 'roadtrip.poi.partial': '{count} stukken konden niet worden doorzocht — de plaatszoekfunctie antwoordde niet.', diff --git a/shared/src/i18n/pl/roadtrip.ts b/shared/src/i18n/pl/roadtrip.ts index cd7c3f573..9a2ccb5e7 100644 --- a/shared/src/i18n/pl/roadtrip.ts +++ b/shared/src/i18n/pl/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Zamień w postój po drodze', 'roadtrip.stop.changeKind': 'Zmień rodzaj postoju', 'roadtrip.stop.backToDestination': 'Z powrotem cel podróży', + 'roadtrip.poi.anySocket': 'Dowolna wtyczka', + 'roadtrip.poi.anyPower': 'Dowolna moc', + 'roadtrip.poi.free': 'Bezpłatnie', 'roadtrip.poi.rest': 'Miejsce odpoczynku', 'roadtrip.poi.campsite': 'Kemping', 'roadtrip.poi.food': 'Jedzenie', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Nic po drodze nie pasuje do „{name}”.', 'roadtrip.poi.foundFiltered': '{count} z {total} po drodze', 'roadtrip.poi.truncated': 'Na {count} odcinkach było więcej, niż mieści się w jednej odpowiedzi — zawęź korytarz, aby zobaczyć resztę.', - 'roadtrip.poi.search': 'Szukaj wzdłuż trasy', + 'roadtrip.poi.search': 'Szukaj', 'roadtrip.poi.searching': 'Szukanie {done} z {total}', 'roadtrip.poi.capped': 'Trasa jest długa — przeszukano tylko pierwszy odcinek.', 'roadtrip.poi.partial': 'Nie udało się przeszukać {count} odcinków — wyszukiwanie miejsc nie odpowiedziało.', diff --git a/shared/src/i18n/ru/roadtrip.ts b/shared/src/i18n/ru/roadtrip.ts index a87e396c2..75f746ec5 100644 --- a/shared/src/i18n/ru/roadtrip.ts +++ b/shared/src/i18n/ru/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Сделать остановкой в пути', 'roadtrip.stop.changeKind': 'Изменить тип остановки', 'roadtrip.stop.backToDestination': 'Снова пункт назначения', + 'roadtrip.poi.anySocket': 'Любой разъём', + 'roadtrip.poi.anyPower': 'Любая мощность', + 'roadtrip.poi.free': 'Бесплатно', 'roadtrip.poi.rest': 'Зона отдыха', 'roadtrip.poi.campsite': 'Кемпинг', 'roadtrip.poi.food': 'Еда', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'По пути ничего не соответствует «{name}».', 'roadtrip.poi.foundFiltered': '{count} из {total} по пути', 'roadtrip.poi.truncated': 'На {count} участках нашлось больше, чем помещается в один ответ — сузьте коридор, чтобы увидеть остальное.', - 'roadtrip.poi.search': 'Искать вдоль маршрута', + 'roadtrip.poi.search': 'Искать', 'roadtrip.poi.searching': 'Поиск {done} из {total}', 'roadtrip.poi.capped': 'Маршрут длинный — просмотрен только первый участок.', 'roadtrip.poi.partial': 'Не удалось просмотреть участков: {count} — поиск мест не ответил.', diff --git a/shared/src/i18n/sv/roadtrip.ts b/shared/src/i18n/sv/roadtrip.ts index 36252d4dd..1a1b36fea 100644 --- a/shared/src/i18n/sv/roadtrip.ts +++ b/shared/src/i18n/sv/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Gör det till ett stopp på vägen', 'roadtrip.stop.changeKind': 'Byt typ av stopp', 'roadtrip.stop.backToDestination': 'Tillbaka till ett resmål', + 'roadtrip.poi.anySocket': 'Alla kontakter', + 'roadtrip.poi.anyPower': 'Alla effekter', + 'roadtrip.poi.free': 'Gratis', 'roadtrip.poi.rest': 'Rastplats', 'roadtrip.poi.campsite': 'Campingplats', 'roadtrip.poi.food': 'Mat', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Inget längs vägen matchar ”{name}”.', 'roadtrip.poi.foundFiltered': '{count} av {total} längs vägen', 'roadtrip.poi.truncated': 'På {count} sträckor fanns fler än vad som får plats i ett svar – gör korridoren smalare för att se resten.', - 'roadtrip.poi.search': 'Sök längs rutten', + 'roadtrip.poi.search': 'Sök', 'roadtrip.poi.searching': 'Söker {done} av {total}', 'roadtrip.poi.capped': 'Rutten är lång – bara första sträckan genomsöktes.', 'roadtrip.poi.partial': '{count} sträckor kunde inte genomsökas – platssökningen svarade inte.', diff --git a/shared/src/i18n/tr/roadtrip.ts b/shared/src/i18n/tr/roadtrip.ts index 9193ced10..b2d42db81 100644 --- a/shared/src/i18n/tr/roadtrip.ts +++ b/shared/src/i18n/tr/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Yol üzerinde molaya dönüştür', 'roadtrip.stop.changeKind': 'Mola türünü değiştir', 'roadtrip.stop.backToDestination': 'Yeniden varış noktası yap', + 'roadtrip.poi.anySocket': 'Tum soketler', + 'roadtrip.poi.anyPower': 'Tum gucler', + 'roadtrip.poi.free': 'Ucretsiz', 'roadtrip.poi.rest': 'Dinlenme alanı', 'roadtrip.poi.campsite': 'Kamp alanı', 'roadtrip.poi.food': 'Yemek', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Yol boyunca “{name}” ile eşleşen bir şey yok.', 'roadtrip.poi.foundFiltered': 'yol üzerinde {total} içinden {count}', 'roadtrip.poi.truncated': '{count} bölümde bir yanıta sığandan fazlası vardı — geri kalanı görmek için koridoru daraltın.', - 'roadtrip.poi.search': 'Rota boyunca ara', + 'roadtrip.poi.search': 'Ara', 'roadtrip.poi.searching': 'Aranıyor {done}/{total}', 'roadtrip.poi.capped': 'Rota uzun — yalnızca ilk bölüm arandı.', 'roadtrip.poi.partial': '{count} bölüm aranamadı — yer araması yanıt vermedi.', diff --git a/shared/src/i18n/uk/roadtrip.ts b/shared/src/i18n/uk/roadtrip.ts index 44b8fd8a0..43a764a9e 100644 --- a/shared/src/i18n/uk/roadtrip.ts +++ b/shared/src/i18n/uk/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Зробити зупинкою в дорозі', 'roadtrip.stop.changeKind': 'Змінити тип зупинки', 'roadtrip.stop.backToDestination': 'Знову пункт призначення', + 'roadtrip.poi.anySocket': 'Будь-який роз’єм', + 'roadtrip.poi.anyPower': 'Будь-яка потужність', + 'roadtrip.poi.free': 'Безкоштовно', 'roadtrip.poi.rest': 'Зона відпочинку', 'roadtrip.poi.campsite': 'Кемпінг', 'roadtrip.poi.food': 'Їжа', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Дорогою ніщо не відповідає «{name}».', 'roadtrip.poi.foundFiltered': '{count} з {total} дорогою', 'roadtrip.poi.truncated': 'На {count} відрізках знайшлося більше, ніж уміщується в одну відповідь — звузьте коридор, щоб побачити решту.', - 'roadtrip.poi.search': 'Шукати вздовж маршруту', + 'roadtrip.poi.search': 'Шукати', 'roadtrip.poi.searching': 'Пошук {done} з {total}', 'roadtrip.poi.capped': 'Маршрут довгий — переглянуто лише перший відрізок.', 'roadtrip.poi.partial': 'Не вдалося переглянути відрізків: {count} — пошук місць не відповів.', diff --git a/shared/src/i18n/vi/roadtrip.ts b/shared/src/i18n/vi/roadtrip.ts index 54f822522..ad3e1a69b 100644 --- a/shared/src/i18n/vi/roadtrip.ts +++ b/shared/src/i18n/vi/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': 'Biến thành điểm dừng dọc đường', 'roadtrip.stop.changeKind': 'Đổi loại điểm dừng', 'roadtrip.stop.backToDestination': 'Trở lại là điểm đến', + 'roadtrip.poi.anySocket': 'Mọi loại phích', + 'roadtrip.poi.anyPower': 'Mọi công suất', + 'roadtrip.poi.free': 'Miễn phí', 'roadtrip.poi.rest': 'Trạm dừng nghỉ', 'roadtrip.poi.campsite': 'Khu cắm trại', 'roadtrip.poi.food': 'Đồ ăn', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': 'Không có gì trên đường khớp với “{name}”.', 'roadtrip.poi.foundFiltered': '{count} trong {total} trên đường', 'roadtrip.poi.truncated': 'Có {count} đoạn nhiều hơn mức một phản hồi chứa được — thu hẹp hành lang để xem phần còn lại.', - 'roadtrip.poi.search': 'Tìm dọc lộ trình', + 'roadtrip.poi.search': 'Tìm kiếm', 'roadtrip.poi.searching': 'Đang tìm {done}/{total}', 'roadtrip.poi.capped': 'Lộ trình dài — chỉ đoạn đầu được tìm kiếm.', 'roadtrip.poi.partial': 'Không tìm được {count} đoạn — tìm kiếm địa điểm không phản hồi.', diff --git a/shared/src/i18n/zh-TW/roadtrip.ts b/shared/src/i18n/zh-TW/roadtrip.ts index f4ced7272..a400887a7 100644 --- a/shared/src/i18n/zh-TW/roadtrip.ts +++ b/shared/src/i18n/zh-TW/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': '設為途中停靠', 'roadtrip.stop.changeKind': '變更停靠類型', 'roadtrip.stop.backToDestination': '恢復為目的地', + 'roadtrip.poi.anySocket': '任意接口', + 'roadtrip.poi.anyPower': '任意功率', + 'roadtrip.poi.free': '免費', 'roadtrip.poi.rest': '休息站', 'roadtrip.poi.campsite': '露營地', 'roadtrip.poi.food': '餐飲', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': '沿途沒有與「{name}」相符的地點。', 'roadtrip.poi.foundFiltered': '沿途 {total} 個中的 {count} 個', 'roadtrip.poi.truncated': '有 {count} 段的結果超出單次回應上限,縮小走廊寬度可查看其餘部分。', - 'roadtrip.poi.search': '沿路線搜尋', + 'roadtrip.poi.search': '搜尋', 'roadtrip.poi.searching': '搜尋中 {done}/{total}', 'roadtrip.poi.capped': '路線較長,僅搜尋了前段。', 'roadtrip.poi.partial': '有 {count} 段無法搜尋 — 地點搜尋沒有回應。', diff --git a/shared/src/i18n/zh/roadtrip.ts b/shared/src/i18n/zh/roadtrip.ts index fa4cef276..d6ddd157d 100644 --- a/shared/src/i18n/zh/roadtrip.ts +++ b/shared/src/i18n/zh/roadtrip.ts @@ -41,6 +41,9 @@ const roadtrip: TranslationStrings = { 'roadtrip.stop.makeService': '设为途中停靠', 'roadtrip.stop.changeKind': '更改停靠类型', 'roadtrip.stop.backToDestination': '恢复为目的地', + 'roadtrip.poi.anySocket': '任意接口', + 'roadtrip.poi.anyPower': '任意功率', + 'roadtrip.poi.free': '免费', 'roadtrip.poi.rest': '服务区', 'roadtrip.poi.campsite': '露营地', 'roadtrip.poi.food': '餐饮', @@ -50,7 +53,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.noMatch': '沿途没有与“{name}”匹配的地点。', 'roadtrip.poi.foundFiltered': '沿途 {total} 个中的 {count} 个', 'roadtrip.poi.truncated': '有 {count} 段的结果超出单次响应上限,缩小走廊宽度可查看其余部分。', - 'roadtrip.poi.search': '沿路线搜索', + 'roadtrip.poi.search': '搜索', 'roadtrip.poi.searching': '搜索中 {done}/{total}', 'roadtrip.poi.capped': '路线较长,仅搜索了前段。', 'roadtrip.poi.partial': '有 {count} 段无法搜索 — 地点搜索没有响应。',