feat(tours): via ferrata layer mounted on the tours map (2D and 3D)

This commit is contained in:
Maurice
2026-10-14 13:57:29 +02:00
parent 6b7461ee03
commit a9882a6415
113 changed files with 4406 additions and 99 deletions
+117
View File
@@ -0,0 +1,117 @@
import {
FERRATA_BBOX_INVALID_MESSAGE,
FERRATA_BBOX_TOO_LARGE_MESSAGE,
FERRATAS_ATTRIBUTION,
ferrataBboxSpanOk,
ferrataGradeSchema,
ferratasQuerySchema,
ferratasResponseSchema,
} from './ferratas.schema';
import { describe, expect, it } from 'vitest';
function messages(bbox: unknown): string[] {
const parsed = ferratasQuerySchema.safeParse({ bbox });
return parsed.success ? [] : parsed.error.issues.map((issue) => issue.message);
}
describe('ferratasQuerySchema', () => {
it('reads south,west,north,east into named edges', () => {
expect(ferratasQuerySchema.parse({ bbox: '47.1,11.2,47.4,11.6' }).bbox).toEqual({
south: 47.1,
west: 11.2,
north: 47.4,
east: 11.6,
});
});
it('tolerates spaces around the edges', () => {
expect(ferratasQuerySchema.parse({ bbox: ' 47.1 , 11.2,47.4 ,11.6 ' }).bbox.north).toBe(47.4);
});
it.each([
['too few edges', '47.1,11.2,47.4'],
['too many edges', '47.1,11.2,47.4,11.6,1'],
['an empty edge', '47.1,,47.4,11.6'],
['a word', '47.1,abc,47.4,11.6'],
['an empty string', ''],
['Infinity', '47.1,11.2,Infinity,11.6'],
])('refuses %s', (_label, bbox) => {
expect(messages(bbox)).toEqual([FERRATA_BBOX_INVALID_MESSAGE]);
});
it('refuses a missing bbox and a non-string one', () => {
expect(ferratasQuerySchema.safeParse({}).success).toBe(false);
expect(ferratasQuerySchema.safeParse({ bbox: ['47', '11', '48', '12'] }).success).toBe(false);
});
it('refuses edges in the wrong order', () => {
expect(messages('47.4,11.2,47.1,11.6')).toContain(FERRATA_BBOX_INVALID_MESSAGE);
expect(messages('47.1,11.6,47.4,11.2')).toContain(FERRATA_BBOX_INVALID_MESSAGE);
});
it('refuses coordinates off the globe', () => {
expect(messages('-91,11,-90.5,11.2').length).toBeGreaterThan(0);
expect(messages('47,180.1,47.2,180.3').length).toBeGreaterThan(0);
});
it('caps the span per side', () => {
expect(messages('47,11,47.5,11.5')).toEqual([]);
expect(messages('47,11,47.6,11.2')).toEqual([FERRATA_BBOX_TOO_LARGE_MESSAGE]);
expect(messages('47,11,47.2,11.6')).toEqual([FERRATA_BBOX_TOO_LARGE_MESSAGE]);
});
it('takes a 0.500 span whose float difference lands a hair above 0.5', () => {
// 64.001 - 63.501 is 0.5000000000000071 in doubles.
expect(64.001 - 63.501).toBeGreaterThan(0.5);
expect(messages('63.501,11.000,64.001,11.200')).toEqual([]);
expect(ferrataBboxSpanOk({ south: 63.501, west: 11, north: 64.001, east: 11.2 })).toBe(true);
expect(ferrataBboxSpanOk({ south: 63.5, west: 11, north: 64.001, east: 11.2 })).toBe(false);
});
it('refuses an oversized query string before splitting it', () => {
expect(ferratasQuerySchema.safeParse({ bbox: '1,'.repeat(150) }).success).toBe(false);
});
});
describe('ferrataGradeSchema', () => {
it('accepts a parsed grade and an unknown one', () => {
expect(ferrataGradeSchema.safeParse({ raw: '3+', level: 3, plus: '+' }).success).toBe(true);
expect(ferrataGradeSchema.safeParse({ raw: 'C/D', level: null, plus: null }).success).toBe(true);
expect(ferrataGradeSchema.safeParse({ raw: null, level: null, plus: null }).success).toBe(true);
});
it('refuses a level outside 0 to 6', () => {
expect(ferrataGradeSchema.safeParse({ raw: '7', level: 7, plus: null }).success).toBe(false);
});
});
describe('ferratasResponseSchema', () => {
const ferrata = {
id: 'way/123',
name: 'Klettersteig',
grade: { raw: '2', level: 2, plus: null },
routeName: null,
geometry: [
[47.1, 11.2],
[47.2, 11.3],
],
osmUrl: 'https://www.openstreetmap.org/way/123',
};
const base = { truncated: false, source: 'openstreetmap', attribution: FERRATAS_ATTRIBUTION };
it('accepts a well-formed answer', () => {
expect(ferratasResponseSchema.safeParse({ ...base, ferratas: [ferrata] }).success).toBe(true);
});
it('refuses an id that is not a way, and a line with one point', () => {
expect(ferratasResponseSchema.safeParse({ ...base, ferratas: [{ ...ferrata, id: 'node/1' }] }).success).toBe(false);
expect(
ferratasResponseSchema.safeParse({ ...base, ferratas: [{ ...ferrata, geometry: [[47.1, 11.2]] }] }).success,
).toBe(false);
});
it('pins the attribution', () => {
expect(ferratasResponseSchema.safeParse({ ...base, attribution: 'someone', ferratas: [] }).success).toBe(false);
});
});
+112
View File
@@ -0,0 +1,112 @@
import { z } from 'zod';
/**
* Via ferratas around a trip, read from OpenStreetMap through Overpass
* (GET /api/trips/:tripId/ferratas?bbox=south,west,north,east).
*
* Read-only and keyless: the server asks the Overpass mirrors the operator
* configured (OVERPASS_URL) and answers with the ways it found, each with its
* grade as OpenStreetMap tags it. Nothing is stored.
*/
/** The widest box one request may ask for, per side, in degrees (about 55 km north to south). */
export const FERRATA_BBOX_MAX_SPAN_DEG = 0.5;
/** The most ferratas one answer carries; past it the answer says `truncated`. */
export const FERRATAS_RESULT_CAP = 400;
/** ODbL asks for this next to anything drawn from the data. */
export const FERRATAS_ATTRIBUTION = '© OpenStreetMap contributors';
/** The `code` beside `error` when no Overpass mirror answered, so the client can say "try again later". */
export const FERRATAS_UNAVAILABLE_CODE = 'ferratas_unavailable';
export const FERRATA_BBOX_INVALID_MESSAGE = 'A valid bbox (south,west,north,east) is required';
export const FERRATA_BBOX_TOO_LARGE_MESSAGE = `The bbox may span at most ${FERRATA_BBOX_MAX_SPAN_DEG} degrees per side`;
/**
* Room for float noise at the span limit: edges rounded to three decimals can
* put a 0.500 span at 0.5000000000000071 (63.501 to 64.001).
*/
const SPAN_EPSILON = 1e-9;
/**
* Whether neither side of the box is wider than FERRATA_BBOX_MAX_SPAN_DEG. The
* one rule for both ends: the server refuses past it and the client asks the
* user to zoom in instead of sending the box.
*/
export function ferrataBboxSpanOk(b: { south: number; west: number; north: number; east: number }): boolean {
const max = FERRATA_BBOX_MAX_SPAN_DEG + SPAN_EPSILON;
return b.north - b.south <= max && b.east - b.west <= max;
}
const latitude = z.number().min(-90).max(90);
const longitude = z.number().min(-180).max(180);
const ferrataBboxSchema = z
.object({ south: latitude, west: longitude, north: latitude, east: longitude })
.refine((b) => b.south < b.north && b.west < b.east, { message: FERRATA_BBOX_INVALID_MESSAGE })
.refine(ferrataBboxSpanOk, { message: FERRATA_BBOX_TOO_LARGE_MESSAGE });
export type FerrataBbox = z.infer<typeof ferrataBboxSchema>;
/**
* The query: one `bbox` of four comma-separated numbers in south, west, north,
* east order (the order Overpass takes them in). A box that crosses the
* antimeridian is refused rather than split; no via ferrata sits on it.
*/
export const ferratasQuerySchema = z.object({
bbox: z
.string()
.max(200)
.transform((raw, ctx) => {
// Number('') is 0, so an empty edge would quietly become the equator; refuse it first.
const edges = raw.split(',').map((part) => (part.trim() === '' ? Number.NaN : Number(part)));
if (edges.length !== 4 || !edges.every(Number.isFinite)) {
ctx.addIssue({ code: 'custom', message: FERRATA_BBOX_INVALID_MESSAGE });
return z.NEVER;
}
const [south, west, north, east] = edges as [number, number, number, number];
return { south, west, north, east };
})
.pipe(ferrataBboxSchema),
});
export type FerratasQuery = z.output<typeof ferratasQuerySchema>;
/**
* The difficulty as tagged. `via_ferrata_scale` is 0 to 6, sometimes with a
* `+` or `-`; `level` and `plus` are filled only for that form. Anything else
* (a letter scale such as "C/D", free text) keeps its `raw` value and reads as
* unknown: letter scales are not converted, because the mapping between the
* Hüsler, Schall and OSM scales is not one to one.
*/
export const ferrataGradeSchema = z.object({
raw: z.string().max(64).nullable(),
level: z.number().int().min(0).max(6).nullable(),
plus: z.enum(['+', '-']).nullable(),
});
export type FerrataGrade = z.infer<typeof ferrataGradeSchema>;
export const ferrataSchema = z.object({
/** The OSM way, as `way/<id>`. */
id: z.string().regex(/^way\/\d+$/),
name: z.string().max(500).nullable(),
grade: ferrataGradeSchema,
/** The name of a `route=via_ferrata` relation the way belongs to, when it has one. */
routeName: z.string().max(500).nullable(),
/** The line as [lat, lng] pairs, in way order. */
geometry: z
.array(z.tuple([latitude, longitude]))
.min(2)
.max(20000),
osmUrl: z.string().url(),
});
export type Ferrata = z.infer<typeof ferrataSchema>;
export const ferratasResponseSchema = z.object({
ferratas: z.array(ferrataSchema).max(FERRATAS_RESULT_CAP),
/** True when the box held more than FERRATAS_RESULT_CAP ways and the rest were left out. */
truncated: z.boolean(),
source: z.literal('openstreetmap'),
attribution: z.literal(FERRATAS_ATTRIBUTION),
});
export type FerratasResponse = z.infer<typeof ferratasResponseSchema>;
+20
View File
@@ -0,0 +1,20 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'مسارات التسلق الحديدية',
'ferratas.count': '{count} مسار تسلق حديدي',
'ferratas.count.zero': 'لا توجد مسارات تسلق حديدية',
'ferratas.count.one': 'مسار تسلق حديدي واحد',
'ferratas.count.two': 'مساران للتسلق الحديدي',
'ferratas.count.few': '{count} مسارات تسلق حديدية',
'ferratas.count.many': '{count} مسارًا للتسلق الحديدي',
'ferratas.loading': 'جارٍ تحميل مسارات التسلق الحديدية…',
'ferratas.error': 'تعذّر الوصول إلى Overpass',
'ferratas.zoomIn': 'قرّب الخريطة لتحميل مسارات التسلق الحديدية',
'ferratas.offline': 'تحتاج مسارات التسلق الحديدية إلى اتصال',
'ferratas.unknownGrade': 'درجة الصعوبة غير معروفة',
'ferratas.unnamed': 'مسار تسلق حديدي بلا اسم',
'ferratas.viewInOsm': 'عرض في OpenStreetMap',
'ferratas.attribution': 'البيانات © مساهمو OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Via ferrata marşrutları',
'ferratas.count': '{count} via ferrata marşrutu',
'ferratas.count.one': '{count} via ferrata marşrutu',
'ferratas.loading': 'Via ferrata marşrutları yüklənir…',
'ferratas.error': 'Overpass əlçatan deyil',
'ferratas.zoomIn': 'Via ferrata marşrutlarını yükləmək üçün yaxınlaşdırın',
'ferratas.offline': 'Via ferrata marşrutları üçün bağlantı lazımdır',
'ferratas.unknownGrade': 'Çətinlik naməlumdur',
'ferratas.unnamed': 'Adsız via ferrata marşrutu',
'ferratas.viewInOsm': 'OpenStreetMap-də bax',
'ferratas.attribution': 'Məlumatlar © OpenStreetMap iştirakçıları',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Vias ferratas',
'ferratas.count': '{count} vias ferratas',
'ferratas.count.one': '{count} via ferrata',
'ferratas.loading': 'Carregando vias ferratas…',
'ferratas.error': 'Overpass indisponível',
'ferratas.zoomIn': 'Aproxime o mapa para carregar vias ferratas',
'ferratas.offline': 'As vias ferratas precisam de conexão',
'ferratas.unknownGrade': 'Dificuldade desconhecida',
'ferratas.unnamed': 'Via ferrata sem nome',
'ferratas.viewInOsm': 'Ver no OpenStreetMap',
'ferratas.attribution': 'Dados © colaboradores do OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Vies ferrades',
'ferratas.count': '{count} vies ferrades',
'ferratas.count.one': '{count} via ferrada',
'ferratas.loading': "S'estan carregant les vies ferrades…",
'ferratas.error': 'Overpass no està disponible',
'ferratas.zoomIn': 'Apropa el mapa per carregar vies ferrades',
'ferratas.offline': 'Les vies ferrades necessiten connexió',
'ferratas.unknownGrade': 'Dificultat desconeguda',
'ferratas.unnamed': 'Via ferrada sense nom',
'ferratas.viewInOsm': 'Mostra a OpenStreetMap',
'ferratas.attribution': "Dades © col·laboradors d'OpenStreetMap",
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+17
View File
@@ -0,0 +1,17 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Ferraty',
'ferratas.count': '{count} ferrat',
'ferratas.count.one': '{count} ferrata',
'ferratas.count.few': '{count} ferraty',
'ferratas.loading': 'Načítání ferrat…',
'ferratas.error': 'Overpass není dostupný',
'ferratas.zoomIn': 'Přibližte mapu pro načtení ferrat',
'ferratas.offline': 'Ferraty vyžadují připojení',
'ferratas.unknownGrade': 'Obtížnost neznámá',
'ferratas.unnamed': 'Ferrata bez názvu',
'ferratas.viewInOsm': 'Zobrazit v OpenStreetMap',
'ferratas.attribution': 'Data © přispěvatelé OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Klettersteige',
'ferratas.count': '{count} Klettersteige',
'ferratas.count.one': '{count} Klettersteig',
'ferratas.loading': 'Klettersteige werden geladen…',
'ferratas.error': 'Overpass nicht erreichbar',
'ferratas.zoomIn': 'Für Klettersteige näher heranzoomen',
'ferratas.offline': 'Klettersteige brauchen eine Verbindung',
'ferratas.unknownGrade': 'Schwierigkeit unbekannt',
'ferratas.unnamed': 'Klettersteig ohne Namen',
'ferratas.viewInOsm': 'In OpenStreetMap ansehen',
'ferratas.attribution': 'Daten © OpenStreetMap-Mitwirkende',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Via ferratas',
'ferratas.count': '{count} via ferrata routes',
'ferratas.count.one': '{count} via ferrata route',
'ferratas.loading': 'Loading via ferratas…',
'ferratas.error': 'Overpass unreachable',
'ferratas.zoomIn': 'Zoom in to load via ferratas',
'ferratas.offline': 'Via ferratas need a connection',
'ferratas.unknownGrade': 'Grade unknown',
'ferratas.unnamed': 'Unnamed via ferrata',
'ferratas.viewInOsm': 'View in OpenStreetMap',
'ferratas.attribution': 'Data © OpenStreetMap contributors',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Vías ferratas',
'ferratas.count': '{count} vías ferratas',
'ferratas.count.one': '{count} vía ferrata',
'ferratas.loading': 'Cargando vías ferratas…',
'ferratas.error': 'Overpass no disponible',
'ferratas.zoomIn': 'Acerca el mapa para cargar vías ferratas',
'ferratas.offline': 'Las vías ferratas necesitan conexión',
'ferratas.unknownGrade': 'Dificultad desconocida',
'ferratas.unnamed': 'Vía ferrata sin nombre',
'ferratas.viewInOsm': 'Ver en OpenStreetMap',
'ferratas.attribution': 'Datos © colaboradores de OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Via ferrata rajad',
'ferratas.count': '{count} via ferrata rada',
'ferratas.count.one': '{count} via ferrata rada',
'ferratas.loading': 'Via ferrata radade laadimine…',
'ferratas.error': 'Overpass pole kättesaadav',
'ferratas.zoomIn': 'Via ferrata radade laadimiseks suumi lähemale',
'ferratas.offline': 'Via ferrata rajad vajavad ühendust',
'ferratas.unknownGrade': 'Raskusaste teadmata',
'ferratas.unnamed': 'Nimeta via ferrata',
'ferratas.viewInOsm': 'Vaata OpenStreetMapis',
'ferratas.attribution': 'Andmed © OpenStreetMapi kaastöötajad',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Via ferrata',
'ferratas.count': '{count} via ferrata',
'ferratas.count.one': '{count} via ferrata',
'ferratas.loading': 'Chargement des via ferrata…',
'ferratas.error': 'Overpass injoignable',
'ferratas.zoomIn': 'Zoomez pour charger les via ferrata',
'ferratas.offline': 'Les via ferrata nécessitent une connexion',
'ferratas.unknownGrade': 'Difficulté inconnue',
'ferratas.unnamed': 'Via ferrata sans nom',
'ferratas.viewInOsm': 'Voir dans OpenStreetMap',
'ferratas.attribution': 'Données © contributeurs OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Διαδρομές via ferrata',
'ferratas.count': '{count} διαδρομές via ferrata',
'ferratas.count.one': '{count} διαδρομή via ferrata',
'ferratas.loading': 'Φόρτωση διαδρομών via ferrata…',
'ferratas.error': 'Το Overpass δεν είναι διαθέσιμο',
'ferratas.zoomIn': 'Μεγεθύνετε για να φορτωθούν διαδρομές via ferrata',
'ferratas.offline': 'Οι διαδρομές via ferrata χρειάζονται σύνδεση',
'ferratas.unknownGrade': 'Άγνωστη δυσκολία',
'ferratas.unnamed': 'Διαδρομή via ferrata χωρίς όνομα',
'ferratas.viewInOsm': 'Προβολή στο OpenStreetMap',
'ferratas.attribution': 'Δεδομένα © συντελεστές του OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Vasalt utak',
'ferratas.count': '{count} vasalt út',
'ferratas.count.one': '{count} vasalt út',
'ferratas.loading': 'Vasalt utak betöltése…',
'ferratas.error': 'Az Overpass nem érhető el',
'ferratas.zoomIn': 'Nagyíts rá a vasalt utak betöltéséhez',
'ferratas.offline': 'A vasalt utakhoz kapcsolat szükséges',
'ferratas.unknownGrade': 'Ismeretlen nehézség',
'ferratas.unnamed': 'Névtelen vasalt út',
'ferratas.viewInOsm': 'Megtekintés az OpenStreetMapen',
'ferratas.attribution': 'Adatok © OpenStreetMap-közreműködők',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+15
View File
@@ -0,0 +1,15 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Jalur via ferrata',
'ferratas.count': '{count} jalur via ferrata',
'ferratas.loading': 'Memuat jalur via ferrata…',
'ferratas.error': 'Overpass tidak dapat dijangkau',
'ferratas.zoomIn': 'Perbesar peta untuk memuat jalur via ferrata',
'ferratas.offline': 'Jalur via ferrata memerlukan koneksi',
'ferratas.unknownGrade': 'Tingkat kesulitan tidak diketahui',
'ferratas.unnamed': 'Via ferrata tanpa nama',
'ferratas.viewInOsm': 'Lihat di OpenStreetMap',
'ferratas.attribution': 'Data © kontributor OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Vie ferrate',
'ferratas.count': '{count} vie ferrate',
'ferratas.count.one': '{count} via ferrata',
'ferratas.loading': 'Caricamento vie ferrate…',
'ferratas.error': 'Overpass non raggiungibile',
'ferratas.zoomIn': 'Ingrandisci per caricare le vie ferrate',
'ferratas.offline': 'Le vie ferrate richiedono una connessione',
'ferratas.unknownGrade': 'Difficoltà sconosciuta',
'ferratas.unnamed': 'Via ferrata senza nome',
'ferratas.viewInOsm': 'Vedi su OpenStreetMap',
'ferratas.attribution': 'Dati © contributori di OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+15
View File
@@ -0,0 +1,15 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'ヴィア・フェラータ',
'ferratas.count': '{count} 件のヴィア・フェラータ',
'ferratas.loading': 'ヴィア・フェラータを読み込み中…',
'ferratas.error': 'Overpass に接続できません',
'ferratas.zoomIn': 'ヴィア・フェラータを読み込むには拡大してください',
'ferratas.offline': 'ヴィア・フェラータの表示には接続が必要です',
'ferratas.unknownGrade': '難易度不明',
'ferratas.unnamed': '名称未設定のヴィア・フェラータ',
'ferratas.viewInOsm': 'OpenStreetMap で表示',
'ferratas.attribution': 'データ © OpenStreetMap 貢献者',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+15
View File
@@ -0,0 +1,15 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': '비아 페라타',
'ferratas.count': '비아 페라타 {count}곳',
'ferratas.loading': '비아 페라타 불러오는 중…',
'ferratas.error': 'Overpass에 연결할 수 없음',
'ferratas.zoomIn': '비아 페라타를 불러오려면 확대하세요',
'ferratas.offline': '비아 페라타를 보려면 연결이 필요합니다',
'ferratas.unknownGrade': '난이도 알 수 없음',
'ferratas.unnamed': '이름 없는 비아 페라타',
'ferratas.viewInOsm': 'OpenStreetMap에서 보기',
'ferratas.attribution': '데이터 © OpenStreetMap 기여자',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': "Via ferrata's",
'ferratas.count': "{count} via ferrata's",
'ferratas.count.one': '{count} via ferrata',
'ferratas.loading': "Via ferrata's laden…",
'ferratas.error': 'Overpass niet bereikbaar',
'ferratas.zoomIn': "Zoom in om via ferrata's te laden",
'ferratas.offline': "Via ferrata's hebben een verbinding nodig",
'ferratas.unknownGrade': 'Moeilijkheid onbekend',
'ferratas.unnamed': 'Via ferrata zonder naam',
'ferratas.viewInOsm': 'Bekijken in OpenStreetMap',
'ferratas.attribution': 'Gegevens © OpenStreetMap-bijdragers',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+18
View File
@@ -0,0 +1,18 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Ferraty',
'ferratas.count': '{count} ferraty',
'ferratas.count.one': '{count} ferrata',
'ferratas.count.few': '{count} ferraty',
'ferratas.count.many': '{count} ferrat',
'ferratas.loading': 'Ładowanie ferrat…',
'ferratas.error': 'Overpass jest niedostępny',
'ferratas.zoomIn': 'Przybliż mapę, aby wczytać ferraty',
'ferratas.offline': 'Ferraty wymagają połączenia',
'ferratas.unknownGrade': 'Nieznana trudność',
'ferratas.unnamed': 'Ferrata bez nazwy',
'ferratas.viewInOsm': 'Pokaż w OpenStreetMap',
'ferratas.attribution': 'Dane © współtwórcy OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+18
View File
@@ -0,0 +1,18 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Виа ферраты',
'ferratas.count': '{count} виа ферраты',
'ferratas.count.one': '{count} виа феррата',
'ferratas.count.few': '{count} виа ферраты',
'ferratas.count.many': '{count} виа феррат',
'ferratas.loading': 'Загрузка виа феррат…',
'ferratas.error': 'Overpass недоступен',
'ferratas.zoomIn': 'Приблизьте карту, чтобы загрузить виа ферраты',
'ferratas.offline': 'Для виа феррат нужно подключение',
'ferratas.unknownGrade': 'Сложность неизвестна',
'ferratas.unnamed': 'Виа феррата без названия',
'ferratas.viewInOsm': 'Открыть в OpenStreetMap',
'ferratas.attribution': 'Данные © участники OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+17
View File
@@ -0,0 +1,17 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Ferraty',
'ferratas.count': '{count} ferrát',
'ferratas.count.one': '{count} ferrata',
'ferratas.count.few': '{count} ferraty',
'ferratas.loading': 'Načítavajú sa ferraty…',
'ferratas.error': 'Overpass nie je dostupný',
'ferratas.zoomIn': 'Priblížte mapu na načítanie ferrát',
'ferratas.offline': 'Ferraty vyžadujú pripojenie',
'ferratas.unknownGrade': 'Neznáma obťažnosť',
'ferratas.unnamed': 'Ferrata bez názvu',
'ferratas.viewInOsm': 'Zobraziť v OpenStreetMap',
'ferratas.attribution': 'Údaje © prispievatelia OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Via ferrata-leder',
'ferratas.count': '{count} via ferrata-leder',
'ferratas.count.one': '{count} via ferrata-led',
'ferratas.loading': 'Läser in via ferrata-leder…',
'ferratas.error': 'Overpass går inte att nå',
'ferratas.zoomIn': 'Zooma in för att läsa in via ferrata-leder',
'ferratas.offline': 'Via ferrata-leder kräver en anslutning',
'ferratas.unknownGrade': 'Okänd svårighetsgrad',
'ferratas.unnamed': 'Namnlös via ferrata',
'ferratas.viewInOsm': 'Visa i OpenStreetMap',
'ferratas.attribution': 'Data © OpenStreetMap-bidragsgivare',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+15
View File
@@ -0,0 +1,15 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'เส้นทางเวียเฟอร์ราตา',
'ferratas.count': '{count} เส้นทางเวียเฟอร์ราตา',
'ferratas.loading': 'กำลังโหลดเส้นทางเวียเฟอร์ราตา…',
'ferratas.error': 'ไม่สามารถเชื่อมต่อ Overpass ได้',
'ferratas.zoomIn': 'ซูมเข้าเพื่อโหลดเส้นทางเวียเฟอร์ราตา',
'ferratas.offline': 'เส้นทางเวียเฟอร์ราตาต้องใช้การเชื่อมต่อ',
'ferratas.unknownGrade': 'ไม่ทราบระดับความยาก',
'ferratas.unnamed': 'เส้นทางเวียเฟอร์ราตาไม่มีชื่อ',
'ferratas.viewInOsm': 'ดูใน OpenStreetMap',
'ferratas.attribution': 'ข้อมูล © ผู้ร่วมให้ข้อมูล OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+16
View File
@@ -0,0 +1,16 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Via ferrata rotaları',
'ferratas.count': '{count} via ferrata rotası',
'ferratas.count.one': '{count} via ferrata rotası',
'ferratas.loading': 'Via ferrata rotaları yükleniyor…',
'ferratas.error': "Overpass'a ulaşılamıyor",
'ferratas.zoomIn': 'Via ferrata rotalarını yüklemek için yakınlaştırın',
'ferratas.offline': 'Via ferrata rotaları için bağlantı gerekiyor',
'ferratas.unknownGrade': 'Zorluk bilinmiyor',
'ferratas.unnamed': 'Adsız via ferrata',
'ferratas.viewInOsm': "OpenStreetMap'te görüntüle",
'ferratas.attribution': 'Veriler © OpenStreetMap katkıcıları',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+18
View File
@@ -0,0 +1,18 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Віа ферати',
'ferratas.count': '{count} віа ферати',
'ferratas.count.one': '{count} віа ферата',
'ferratas.count.few': '{count} віа ферати',
'ferratas.count.many': '{count} віа ферат',
'ferratas.loading': 'Завантаження віа ферат…',
'ferratas.error': 'Overpass недоступний',
'ferratas.zoomIn': 'Наблизьте мапу, щоб завантажити віа ферати',
'ferratas.offline': 'Для віа ферат потрібне з’єднання',
'ferratas.unknownGrade': 'Складність невідома',
'ferratas.unnamed': 'Віа ферата без назви',
'ferratas.viewInOsm': 'Відкрити в OpenStreetMap',
'ferratas.attribution': 'Дані © учасники OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+15
View File
@@ -0,0 +1,15 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': 'Tuyến via ferrata',
'ferratas.count': '{count} tuyến via ferrata',
'ferratas.loading': 'Đang tải các tuyến via ferrata…',
'ferratas.error': 'Không thể kết nối Overpass',
'ferratas.zoomIn': 'Phóng to để tải các tuyến via ferrata',
'ferratas.offline': 'Các tuyến via ferrata cần kết nối mạng',
'ferratas.unknownGrade': 'Không rõ độ khó',
'ferratas.unnamed': 'Via ferrata chưa có tên',
'ferratas.viewInOsm': 'Xem trên OpenStreetMap',
'ferratas.attribution': 'Dữ liệu © những người đóng góp OpenStreetMap',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+15
View File
@@ -0,0 +1,15 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': '飛拉達',
'ferratas.count': '{count} 條飛拉達',
'ferratas.loading': '正在載入飛拉達…',
'ferratas.error': '無法連線至 Overpass',
'ferratas.zoomIn': '放大地圖以載入飛拉達',
'ferratas.offline': '飛拉達需要網路連線',
'ferratas.unknownGrade': '難度不明',
'ferratas.unnamed': '未命名的飛拉達',
'ferratas.viewInOsm': '在 OpenStreetMap 中檢視',
'ferratas.attribution': '資料 © OpenStreetMap 貢獻者',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+15
View File
@@ -0,0 +1,15 @@
import type { TranslationStrings } from '../types';
const ferratas: TranslationStrings = {
'ferratas.layer': '飞拉达',
'ferratas.count': '{count} 条飞拉达',
'ferratas.loading': '正在加载飞拉达…',
'ferratas.error': '无法连接 Overpass',
'ferratas.zoomIn': '放大地图以加载飞拉达',
'ferratas.offline': '飞拉达需要网络连接',
'ferratas.unknownGrade': '难度未知',
'ferratas.unnamed': '未命名飞拉达',
'ferratas.viewInOsm': '在 OpenStreetMap 中查看',
'ferratas.attribution': '数据 © OpenStreetMap 贡献者',
};
export default ferratas;
+2
View File
@@ -12,6 +12,7 @@ import dawarich from './dawarich';
import day from './day';
import dayplan from './dayplan';
import docsync from './docsync';
import ferratas from './ferratas';
import files from './files';
import help from './help';
import inspector from './inspector';
@@ -106,5 +107,6 @@ const locale = {
...storage,
...dawarich,
...docsync,
...ferratas,
};
export default locale;
+3
View File
@@ -115,3 +115,6 @@ export * from './money/money';
// Trip permission catalog (keys, levels, defaults) and the rule that decides a check.
export * from './permissions/permissions';
// Via ferratas around a trip, from OpenStreetMap through Overpass (GET /api/trips/:tripId/ferratas).
export * from './ferratas/ferratas.schema';