mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
feat(tours): bike tours in the contract and on the server (BIK-01 to BIK-06, RTE-01)
A tour can be a bike tour with a routing profile (touring, road, gravel, mtb), stored per tour by a new migration. The server grades the ways of a mountain bike route from OpenStreetMap (mtb:scale) through one shared Overpass client, offers CyclOSM behind its own admin switch, and exports a bike tour's GPX with the activity of its profile. A hike's route choices are now exactly the three the card names: steep climbs, steps and ferries.
This commit is contained in:
@@ -38,6 +38,7 @@ export class Tours {
|
||||
hiking_preferences?: string | null;
|
||||
taken_over_from_trip_title?: string | null;
|
||||
taken_over_from_tour_name?: string | null;
|
||||
bike_profile?: TToursBikeProfile | null;
|
||||
tourTypeRef!: Ref<TourTypes>;
|
||||
tour_obstacle_reviews_collection = new Collection<TourObstacleReviews>(this);
|
||||
tour_sources: Ref<TourSources> | null = null;
|
||||
@@ -75,6 +76,15 @@ export const ToursPaddleLakeStyle = {
|
||||
|
||||
export type TToursPaddleLakeStyle = (typeof ToursPaddleLakeStyle)[keyof typeof ToursPaddleLakeStyle];
|
||||
|
||||
export const ToursBikeProfile = {
|
||||
TOURING: 'touring',
|
||||
ROAD: 'road',
|
||||
GRAVEL: 'gravel',
|
||||
MTB: 'mtb',
|
||||
} as const;
|
||||
|
||||
export type TToursBikeProfile = (typeof ToursBikeProfile)[keyof typeof ToursBikeProfile];
|
||||
|
||||
export const ToursSchema = defineEntity({
|
||||
class: Tours,
|
||||
repository: () => ToursRepository,
|
||||
@@ -116,6 +126,7 @@ export const ToursSchema = defineEntity({
|
||||
hiking_preferences: p.text().nullable(),
|
||||
taken_over_from_trip_title: p.text().nullable(),
|
||||
taken_over_from_tour_name: p.text().nullable(),
|
||||
bike_profile: p.enum(() => ToursBikeProfile).nullable(),
|
||||
tourTypeRef: () => p.manyToOne(TourTypes).ref().joinColumn('tour_type').hidden().index('idx_tours_tour_type'),
|
||||
tour_obstacle_reviews_collection: () => p.oneToMany(TourObstacleReviews).mappedBy('place').hidden(),
|
||||
tour_sources: () => p.oneToOne(TourSources).ref().mappedBy('place').hidden(),
|
||||
|
||||
@@ -30,4 +30,5 @@ export interface ToursTable {
|
||||
hiking_preferences: InsertOptional<string | null>;
|
||||
taken_over_from_trip_title: InsertOptional<string | null>;
|
||||
taken_over_from_tour_name: InsertOptional<string | null>;
|
||||
bike_profile: InsertOptional<string | null>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
/**
|
||||
* BIK-01: a tour can be a bike tour. The `bike` key has been in `tour_types`,
|
||||
* disabled, since every contract type was seeded; it is switched on here. The
|
||||
* profile it is ridden on (touring, road, gravel, mtb) is a column of the
|
||||
* tour, NULL on every other type, so no existing tour changes its meaning.
|
||||
*/
|
||||
export class Migration20200101045200_a_tour_can_be_ridden extends Migration {
|
||||
override name = 'Migration20200101045200_a_tour_can_be_ridden';
|
||||
|
||||
override up(): void {
|
||||
this.addSql(
|
||||
"ALTER TABLE tours ADD COLUMN bike_profile TEXT CHECK (bike_profile IS NULL OR bike_profile IN ('touring', 'road', 'gravel', 'mtb'))",
|
||||
);
|
||||
this.addSql("UPDATE tour_types SET enabled = 1 WHERE key = 'bike'");
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { type Tours, type TToursOrigin, ToursOrigin } from '../entities/Tours.en
|
||||
import type { DB } from '../kysely/db';
|
||||
import type { AssertRowKeys } from './_shared/rows';
|
||||
import { TrekRepository } from './_shared/trek-repository';
|
||||
import type { PaddleBoatType, PaddleLakeStyle, PaddleWaterContext } from '@trek/shared';
|
||||
import type { BikeProfile, PaddleBoatType, PaddleLakeStyle, PaddleWaterContext } from '@trek/shared';
|
||||
|
||||
/**
|
||||
* A tour as the Tours list and detail read it: the `tours` facet joined with
|
||||
@@ -28,6 +28,8 @@ export interface TourListRow {
|
||||
max_hiking_difficulty: number | null;
|
||||
derived_from_place_id: number | null;
|
||||
origin: string;
|
||||
/** BIK-01: a bike tour's profile; NULL on every other type. */
|
||||
bike_profile: string | null;
|
||||
boat_type: string | null;
|
||||
water_context: string | null;
|
||||
paddle_speed_kmh: number | null;
|
||||
@@ -92,6 +94,7 @@ export interface TourRow {
|
||||
hiking_preferences: string | null;
|
||||
taken_over_from_trip_title: string | null;
|
||||
taken_over_from_tour_name: string | null;
|
||||
bike_profile: string | null;
|
||||
}
|
||||
const _tourRowKeys: AssertRowKeys<TourRow, Tours> = true;
|
||||
|
||||
@@ -107,6 +110,8 @@ export interface TourInsert {
|
||||
break_additional_minutes?: number | null;
|
||||
match_confidence: number | null;
|
||||
max_hiking_difficulty: number | null;
|
||||
/** BIK-01: a bike tour's profile; left out, NULL. */
|
||||
bike_profile?: BikeProfile | null;
|
||||
/** A paddle tour's boat, water and chosen speed; left out, a hike's NULLs. */
|
||||
boat_type?: PaddleBoatType | null;
|
||||
water_context?: PaddleWaterContext | null;
|
||||
@@ -123,6 +128,7 @@ export type TourUpdate = Omit<
|
||||
| 'place_id'
|
||||
| 'planned_duration_minutes'
|
||||
| 'break_additional_minutes'
|
||||
| 'bike_profile'
|
||||
| 'boat_type'
|
||||
| 'water_context'
|
||||
| 'paddle_speed_kmh'
|
||||
@@ -131,6 +137,7 @@ export type TourUpdate = Omit<
|
||||
> & {
|
||||
planned_duration_minutes?: number | null;
|
||||
break_additional_minutes?: number | null;
|
||||
bike_profile: BikeProfile | null;
|
||||
boat_type: PaddleBoatType | null;
|
||||
water_context: PaddleWaterContext | null;
|
||||
paddle_speed_kmh: number | null;
|
||||
@@ -232,6 +239,7 @@ export class ToursRepository extends TrekRepository<Tours> {
|
||||
't.max_hiking_difficulty as max_hiking_difficulty',
|
||||
't.derived_from_place_id as derived_from_place_id',
|
||||
't.origin as origin',
|
||||
't.bike_profile as bike_profile',
|
||||
't.boat_type as boat_type',
|
||||
't.water_context as water_context',
|
||||
't.paddle_speed_kmh as paddle_speed_kmh',
|
||||
@@ -585,6 +593,7 @@ export class ToursRepository extends TrekRepository<Tours> {
|
||||
break_additional_minutes: input.break_additional_minutes ?? null,
|
||||
match_confidence: input.match_confidence,
|
||||
max_hiking_difficulty: input.max_hiking_difficulty,
|
||||
bike_profile: input.bike_profile ?? null,
|
||||
boat_type: input.boat_type ?? null,
|
||||
water_context: input.water_context ?? null,
|
||||
paddle_speed_kmh: input.paddle_speed_kmh ?? null,
|
||||
@@ -669,6 +678,7 @@ export class ToursRepository extends TrekRepository<Tours> {
|
||||
: {}),
|
||||
match_confidence: input.match_confidence,
|
||||
max_hiking_difficulty: input.max_hiking_difficulty,
|
||||
bike_profile: input.bike_profile,
|
||||
boat_type: input.boat_type,
|
||||
water_context: input.water_context,
|
||||
paddle_speed_kmh: input.paddle_speed_kmh,
|
||||
|
||||
@@ -328,6 +328,11 @@ export function applyGlobalMiddleware(
|
||||
// OpenHikingMap, the hiking base map of the Tours planner (MAP-01), loaded only
|
||||
// while a user shows it and the admin allows it; GL reads it through fetch.
|
||||
'https://tile.openmaps.fr',
|
||||
// CyclOSM, the cycling base map of a bike tour in the Tours planner (BIK-06), on the
|
||||
// same terms; its three shards are named, so no wildcard opens the whole domain.
|
||||
'https://a.tile-cyclosm.openstreetmap.fr',
|
||||
'https://b.tile-cyclosm.openstreetmap.fr',
|
||||
'https://c.tile-cyclosm.openstreetmap.fr',
|
||||
// Amap's raster tiles, for an install whose users are in China, for the
|
||||
// same reason as the hosts above (#2180). Road (webrd01..04) and
|
||||
// satellite (webst01..04) are numbered shards of one domain, so the
|
||||
|
||||
@@ -91,6 +91,16 @@ export class AddonsService {
|
||||
return this.writeFlag('tours_hiking_map_enabled', enabled);
|
||||
}
|
||||
|
||||
/** BIK-06: the admin's switch for CyclOSM under Tours, as stored; fail-open like OpenHikingMap's. */
|
||||
async getToursCycleMap() {
|
||||
const value = await readAppSetting(this.appSettings, 'tours_cycle_map_enabled');
|
||||
return { enabled: value !== 'false' };
|
||||
}
|
||||
|
||||
async updateToursCycleMap(enabled: boolean) {
|
||||
return this.writeFlag('tours_cycle_map_enabled', enabled);
|
||||
}
|
||||
|
||||
/** wanderer may be used: Tours is on and the admin allowed it under Tours. */
|
||||
async isWandererAvailable(): Promise<boolean> {
|
||||
return (await this.isAddonEnabled(ADDON_IDS.TOURS)) && (await this.getToursWanderer()).enabled;
|
||||
@@ -177,6 +187,7 @@ export class AddonsService {
|
||||
bagTracking: (await this.getBagTracking()).enabled,
|
||||
toursWanderer: await this.isWandererAvailable(),
|
||||
toursHikingMap: (await this.isAddonEnabled(ADDON_IDS.TOURS)) && (await this.getToursHikingMap()).enabled,
|
||||
toursCycleMap: (await this.isAddonEnabled(ADDON_IDS.TOURS)) && (await this.getToursCycleMap()).enabled,
|
||||
addons: [
|
||||
// The repository row carries every scalar column (description, config,
|
||||
// sort_order included); the legacy statement selected only these five,
|
||||
|
||||
@@ -323,6 +323,25 @@ export class AdminController {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get('tours-cycle-map')
|
||||
@ResponseContract(adminFeatureToggleResponseSchema)
|
||||
getToursCycleMap() {
|
||||
return this.addons.getToursCycleMap();
|
||||
}
|
||||
|
||||
@Put('tours-cycle-map')
|
||||
@ResponseContract(adminFeatureToggleResponseSchema)
|
||||
async updateToursCycleMap(@CurrentUser() user: User, @Body() body: AdminFeatureToggleDto, @Req() req: Request) {
|
||||
const result = await this.addons.updateToursCycleMap(body.enabled);
|
||||
await this.audit.writeAudit({
|
||||
userId: user.id,
|
||||
action: 'admin.tours_cycle_map',
|
||||
ip: getClientIp(req),
|
||||
details: { enabled: result.enabled },
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get('places-photos')
|
||||
getPlacesPhotos() {
|
||||
return this.addons.getPlacesPhotos();
|
||||
|
||||
@@ -112,6 +112,10 @@ export const APP_SETTINGS = {
|
||||
// the installs TREK is made for; an admin whose install is commercial or larger switches it off
|
||||
// until there is an agreement with the operator (openmaps.fr/tile-usage-policy.html).
|
||||
tours_hiking_map_enabled: { schema: flag, default: 'true' },
|
||||
// BIK-06: CyclOSM as a base map of a bike tour in the Tours planner, on the same terms: its
|
||||
// tiles are OpenStreetMap France's, free for moderate use with a Referer; a heavy or commercial
|
||||
// install switches it off (or reaches an agreement) under the operator's usage policy.
|
||||
tours_cycle_map_enabled: { schema: flag, default: 'true' },
|
||||
route_usage_enabled: { schema: flag, default: 'false' },
|
||||
// PAD-11C: how TREK gets the water data of a place a paddler plans in (auto, ask, off) and the largest extract it loads by itself.
|
||||
paddle_auto_load: { schema: z.enum(['auto', 'ask', 'off']), default: 'auto' },
|
||||
|
||||
@@ -202,11 +202,6 @@ type WayElement = z.infer<typeof wayElementSchema>;
|
||||
type RelationElement = z.infer<typeof relationElementSchema>;
|
||||
|
||||
/** What an Overpass mirror answers with, at the boundary. */
|
||||
export const overpassFerrataAnswerSchema = z.object({
|
||||
elements: z.array(z.unknown()).max(50000),
|
||||
remark: z.string().optional(),
|
||||
});
|
||||
|
||||
function cleanName(value: string | undefined): string | null {
|
||||
const name = value?.trim() ?? '';
|
||||
return name === '' ? null : name.slice(0, NAME_MAX);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FerrataTiles } from '../../db/entities/FerrataTiles.entity';
|
||||
import { OverpassModule } from '../overpass/overpass.module';
|
||||
import { PermissionsModule } from '../permissions/permissions.module';
|
||||
import { FerratasOverpassClient } from './ferratas-overpass.client';
|
||||
import { FerratasController } from './ferratas.controller';
|
||||
import { FerratasService } from './ferratas.service';
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
@@ -8,14 +8,14 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* Via ferratas around a trip, read from OpenStreetMap through Overpass.
|
||||
* PermissionsModule brings TripAccessGuard; RuntimeEnvService (the Overpass
|
||||
* endpoints and deadline) comes from the global AppConfigModule. The answers
|
||||
* PermissionsModule brings TripAccessGuard; OverpassModule the client that asks
|
||||
* the operator's Overpass mirrors. The answers
|
||||
* are kept per grid cell in `ferrata_tiles`; UnitOfWork and MikroORM come from
|
||||
* the global OrmModule.
|
||||
*/
|
||||
@Module({
|
||||
imports: [PermissionsModule, MikroOrmModule.forFeature([FerrataTiles])],
|
||||
imports: [PermissionsModule, OverpassModule, MikroOrmModule.forFeature([FerrataTiles])],
|
||||
controllers: [FerratasController],
|
||||
providers: [FerratasService, FerratasOverpassClient],
|
||||
providers: [FerratasService],
|
||||
})
|
||||
export class FerratasModule {}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { logWarn } from '../audit/audit-log.logger';
|
||||
import { badRequest, DomainError } from '../common/domain-error';
|
||||
import { withRequestContext } from '../database/request-context';
|
||||
import { UnitOfWork } from '../database/unit-of-work';
|
||||
import { FerratasOverpassClient } from './ferratas-overpass.client';
|
||||
import { OverpassElementsClient } from '../overpass/overpass-elements.client';
|
||||
import {
|
||||
buildFerrataQuery,
|
||||
ferrataTileBlocks,
|
||||
@@ -98,7 +98,7 @@ export class FerratasService {
|
||||
private readonly waiting: Array<() => void> = [];
|
||||
|
||||
constructor(
|
||||
private readonly overpass: FerratasOverpassClient,
|
||||
private readonly overpass: OverpassElementsClient,
|
||||
@InjectRepository(FerrataTiles) private readonly tiles: FerrataTilesRepository,
|
||||
private readonly uow: UnitOfWork,
|
||||
private readonly orm: MikroORM,
|
||||
@@ -242,7 +242,7 @@ export class FerratasService {
|
||||
private async queryOverpass(keys: readonly string[]): Promise<unknown[]> {
|
||||
await this.turn();
|
||||
try {
|
||||
return await this.overpass.query(buildFerrataQuery(ferrataTilesBbox(keys)));
|
||||
return await this.overpass.query(buildFerrataQuery(ferrataTilesBbox(keys)), 'Ferratas');
|
||||
} catch {
|
||||
// The client already logged why each mirror failed.
|
||||
throw new DomainError(502, FERRATAS_UNAVAILABLE_MESSAGE, {
|
||||
|
||||
@@ -367,8 +367,8 @@ export const CATEGORY_OSM_FILTERS: Record<string, string[]> = {
|
||||
charging: ['amenity=charging_station'],
|
||||
rest_area: ['highway=rest_area', 'highway=services'],
|
||||
campsite: ['tourism=camp_site', 'tourism=caravan_site'],
|
||||
// What a hike or a paddle tour stops for along its line (PLN-02). Asked for by the Tours
|
||||
// search along a route, never offered by the explore pill.
|
||||
// What a hike, a ride or a paddle tour stops for along its line (PLN-02). Asked for by the
|
||||
// Tours search along a route, never offered by the explore pill.
|
||||
viewpoint: ['tourism=viewpoint'],
|
||||
peak: ['natural=peak'],
|
||||
shelter: ['amenity=shelter', 'tourism=wilderness_hut', 'tourism=alpine_hut'],
|
||||
@@ -377,6 +377,7 @@ export const CATEGORY_OSM_FILTERS: Record<string, string[]> = {
|
||||
picnic: ['tourism=picnic_site', 'leisure=picnic_table'],
|
||||
slipway: ['leisure=slipway', 'canoe=put_in'],
|
||||
boat_rental: ['amenity=boat_rental'],
|
||||
bike_repair: ['amenity=bicycle_repair_station', 'shop=bicycle'],
|
||||
};
|
||||
|
||||
export const POI_CATEGORY_KEYS = Object.keys(CATEGORY_OSM_FILTERS);
|
||||
|
||||
+16
-7
@@ -3,12 +3,14 @@ import { readCappedJson } from '../../utils/cappedFetch';
|
||||
import { RuntimeEnvService } from '../app-config/runtime-env.service';
|
||||
import { logWarn } from '../audit/audit-log.logger';
|
||||
import { buildUserAgent, parseOverpassEndpoints } from '../maps/overpass.constants';
|
||||
import { overpassFerrataAnswerSchema } from './ferratas.helpers';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* The outbound half of the via ferrata lookup: one Overpass query raced across
|
||||
* the mirrors the operator configured.
|
||||
* One Overpass query raced across the mirrors the operator configured, for the
|
||||
* domains that read OpenStreetMap elements themselves: the via ferratas around
|
||||
* a trip, the trail grades along a bike tour (BIK-05).
|
||||
*
|
||||
* It reads the same settings the maps domain's Overpass client reads
|
||||
* (OVERPASS_URL, a comma-separated list that replaces the public mirrors, and
|
||||
@@ -22,6 +24,12 @@ import { Injectable } from '@nestjs/common';
|
||||
// megabytes means the mirror is misbehaving, not that the mountains got bigger.
|
||||
const MAX_ANSWER_BYTES = 8 * 1024 * 1024;
|
||||
|
||||
/** An Overpass answer: its elements, and a remark when Overpass gave up on the query. */
|
||||
export const overpassAnswerSchema = z.object({
|
||||
elements: z.array(z.unknown()).max(50000),
|
||||
remark: z.string().optional(),
|
||||
});
|
||||
|
||||
/** Why every mirror failed, one reason per mirror, for the log line. */
|
||||
function reasons(err: unknown): string {
|
||||
const all = err instanceof AggregateError ? err.errors : [err];
|
||||
@@ -29,7 +37,7 @@ function reasons(err: unknown): string {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FerratasOverpassClient {
|
||||
export class OverpassElementsClient {
|
||||
constructor(private readonly env: RuntimeEnvService) {}
|
||||
|
||||
/**
|
||||
@@ -37,8 +45,9 @@ export class FerratasOverpassClient {
|
||||
* document. A `remark` (Overpass' own timeout or runtime error, sent with a
|
||||
* 200) counts as a failure, so a fast but empty answer cannot win the race.
|
||||
* Throws once every mirror failed; the caller decides what the client sees.
|
||||
* `caller` names the domain in the log line.
|
||||
*/
|
||||
async query(query: string): Promise<unknown[]> {
|
||||
async query(query: string, caller: string): Promise<unknown[]> {
|
||||
const { overpassUrl, overpassTimeoutMs } = this.env.env().integrations;
|
||||
const endpoints = parseOverpassEndpoints(overpassUrl);
|
||||
const body = `data=${encodeURIComponent(query)}`;
|
||||
@@ -52,7 +61,7 @@ export class FerratasOverpassClient {
|
||||
await res.body?.cancel().catch(() => {});
|
||||
throw new Error(`Overpass ${res.status} @ ${url}`);
|
||||
}
|
||||
const answer = overpassFerrataAnswerSchema.safeParse(await readCappedJson<unknown>(res, MAX_ANSWER_BYTES));
|
||||
const answer = overpassAnswerSchema.safeParse(await readCappedJson<unknown>(res, MAX_ANSWER_BYTES));
|
||||
if (!answer.success) throw new Error(`Overpass non-OSM or oversized body @ ${url}`);
|
||||
if (answer.data.remark) throw new Error(`Overpass remark @ ${url}: ${answer.data.remark}`);
|
||||
return answer.data.elements;
|
||||
@@ -61,7 +70,7 @@ export class FerratasOverpassClient {
|
||||
try {
|
||||
return await Promise.any(endpoints.map(attempt));
|
||||
} catch (err) {
|
||||
logWarn(`[Ferratas] all ${endpoints.length} Overpass endpoint(s) failed: ${reasons(err)}`);
|
||||
logWarn(`[${caller}] all ${endpoints.length} Overpass endpoint(s) failed: ${reasons(err)}`);
|
||||
throw new Error('Could not reach any Overpass endpoint', { cause: err });
|
||||
} finally {
|
||||
// The losers are cancelled once one mirror answered (or all gave up).
|
||||
@@ -0,0 +1,13 @@
|
||||
import { OverpassElementsClient } from './overpass-elements.client';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* The Overpass client shared by the domains that read OpenStreetMap elements
|
||||
* themselves. RuntimeEnvService (the mirrors and the deadline) comes from the
|
||||
* global AppConfigModule.
|
||||
*/
|
||||
@Module({
|
||||
providers: [OverpassElementsClient],
|
||||
exports: [OverpassElementsClient],
|
||||
})
|
||||
export class OverpassModule {}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { tourTrailGradeRequestSchema } from '@trek/shared';
|
||||
|
||||
import { createZodDto } from 'nestjs-zod';
|
||||
|
||||
export class TourTrailGradeDto extends createZodDto(tourTrailGradeRequestSchema) {}
|
||||
@@ -9,7 +9,7 @@ import { toRowId } from '../common/row-id';
|
||||
import { PlaceImportService } from '../place-import/place-import.service';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import type { PaddleBoatType } from '@trek/shared';
|
||||
import type { BikeProfile, PaddleBoatType } from '@trek/shared';
|
||||
|
||||
/** What GPX calls the activity in a track's <type>; a paddle tour says what it is paddled in. */
|
||||
const PADDLE_ACTIVITY: Record<PaddleBoatType, string> = {
|
||||
@@ -19,8 +19,18 @@ const PADDLE_ACTIVITY: Record<PaddleBoatType, string> = {
|
||||
other: 'paddling',
|
||||
};
|
||||
|
||||
function gpxActivityOf(type: string, boat: string | null): string {
|
||||
/** A bike tour says what it is ridden on, in the words Garmin and Strava read. */
|
||||
const BIKE_ACTIVITY: Record<BikeProfile, string> = {
|
||||
touring: 'cycling',
|
||||
road: 'road_biking',
|
||||
gravel: 'gravel_cycling',
|
||||
mtb: 'mountain_biking',
|
||||
};
|
||||
|
||||
function gpxActivityOf(tour: { tour_type: string; boat_type?: string | null; bike_profile?: string | null }): string {
|
||||
const { tour_type: type, boat_type: boat, bike_profile: bike } = tour;
|
||||
if (type === 'kayak') return PADDLE_ACTIVITY[(boat ?? 'other') as PaddleBoatType] ?? PADDLE_ACTIVITY.other;
|
||||
if (type === 'bike') return BIKE_ACTIVITY[(bike ?? 'touring') as BikeProfile] ?? BIKE_ACTIVITY.touring;
|
||||
return type === 'hike' ? 'hiking' : type;
|
||||
}
|
||||
|
||||
@@ -51,7 +61,7 @@ export class TourGpxExportService {
|
||||
name: tour.name,
|
||||
description: tour.description ?? null,
|
||||
website: tour.website ?? null,
|
||||
activity: gpxActivityOf(tour.tour_type, tour.boat_type),
|
||||
activity: gpxActivityOf(tour),
|
||||
route_geometry: place.route_geometry ?? null,
|
||||
stops,
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ const MAPS_CATEGORIES: Record<TourPoiCategory, readonly string[]> = {
|
||||
sights: ['sights'],
|
||||
slipway: ['slipway'],
|
||||
boat_rental: ['boat_rental'],
|
||||
bike_repair: ['bike_repair'],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { AddonGuard } from '../addons/addon.guard';
|
||||
import { RequireAddon } from '../addons/require-addon.decorator';
|
||||
import { JwtAuthGuard } from '../auth-core/jwt-auth.guard';
|
||||
import { ResponseContract } from '../common/response-contract';
|
||||
import { TripAccessGuard } from '../permissions/trip-access.guard';
|
||||
import { TourTrailGradeDto } from './dto/tour-trail-grade.dto';
|
||||
import { TourTrailGradeService } from './tour-trail-grade.service';
|
||||
import { Body, Controller, HttpCode, Post, UseGuards } from '@nestjs/common';
|
||||
import { tourTrailGradeResponseSchema } from '@trek/shared';
|
||||
|
||||
/**
|
||||
* BIK-05 POST /api/trips/:tripId/tours/trail-grades: the trail grades of the
|
||||
* ways a mountain bike tour runs on. A read, so anyone who sees the trip may
|
||||
* ask; the ways come in the body because the draft may not be saved yet.
|
||||
*/
|
||||
@Controller('api/trips/:tripId/tours')
|
||||
@UseGuards(AddonGuard, JwtAuthGuard, TripAccessGuard)
|
||||
@RequireAddon(ADDON_IDS.TOURS, 'Tours')
|
||||
export class TourTrailGradeController {
|
||||
constructor(private readonly trailGrades: TourTrailGradeService) {}
|
||||
|
||||
@Post('trail-grades')
|
||||
@HttpCode(200)
|
||||
@ResponseContract(tourTrailGradeResponseSchema)
|
||||
async grades(@Body() body: TourTrailGradeDto) {
|
||||
return await this.trailGrades.grades(body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { OverpassElementsClient } from '../overpass/overpass-elements.client';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { TourTrailGrade, TourTrailGradeRequest, TourTrailGradeResponse } from '@trek/shared';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/** Ways per Overpass question: a query names them all, and a shorter one answers sooner. */
|
||||
const WAYS_PER_QUERY = 400;
|
||||
/** How long a way's grades are kept: OpenStreetMap changes slowly, the mirrors are shared. */
|
||||
const KEPT_MS = 24 * 60 * 60 * 1000;
|
||||
/** The most ways kept at once; the oldest go first. */
|
||||
const MAX_KEPT = 50_000;
|
||||
|
||||
const wayElementSchema = z.object({
|
||||
type: z.literal('way'),
|
||||
id: z.number().int().positive(),
|
||||
tags: z.record(z.string(), z.string()).optional(),
|
||||
});
|
||||
|
||||
/** A tag's value as a step of its scale (`S2` and ` 2 ` read as 2), or null when it is none. */
|
||||
function step(value: string | undefined, top: number, signed: boolean): string | null {
|
||||
const match = /^\s*S?([0-9])([+-])?\s*$/i.exec(value ?? '');
|
||||
if (!match || Number(match[1]) > top) return null;
|
||||
return signed ? `${match[1]}${match[2] ?? ''}` : (match[1] ?? null);
|
||||
}
|
||||
|
||||
function gradeOf(id: number, tags: Record<string, string> = {}): TourTrailGrade | null {
|
||||
const grade = {
|
||||
id,
|
||||
mtb_scale: step(tags['mtb:scale'], 6, true),
|
||||
mtb_scale_uphill: step(tags['mtb:scale:uphill'], 5, true),
|
||||
mtb_scale_imba: step(tags['mtb:scale:imba'], 4, false),
|
||||
};
|
||||
return grade.mtb_scale || grade.mtb_scale_uphill || grade.mtb_scale_imba ? grade : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* BIK-05: the trail grades of the ways a mountain bike tour runs on, read from
|
||||
* OpenStreetMap through the operator's Overpass mirrors. What was read is kept
|
||||
* a day, so the same tour asked about again costs the mirrors nothing; ways a
|
||||
* mirror could not answer are said, never guessed.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TourTrailGradeService {
|
||||
private readonly kept = new Map<number, { grade: TourTrailGrade | null; at: number }>();
|
||||
|
||||
constructor(private readonly overpass: OverpassElementsClient) {}
|
||||
|
||||
async grades(request: TourTrailGradeRequest): Promise<TourTrailGradeResponse> {
|
||||
const now = Date.now();
|
||||
const ids = [...new Set(request.way_ids)];
|
||||
const unknown = ids.filter((id) => {
|
||||
const known = this.kept.get(id);
|
||||
return !known || now - known.at > KEPT_MS;
|
||||
});
|
||||
const parts = Array.from({ length: Math.ceil(unknown.length / WAYS_PER_QUERY) }, (_, index) =>
|
||||
unknown.slice(index * WAYS_PER_QUERY, (index + 1) * WAYS_PER_QUERY),
|
||||
);
|
||||
// One question after another, so a long tour does not flood the shared mirrors.
|
||||
const unanswered = await parts.reduce<Promise<number[]>>(async (done, part) => {
|
||||
const failed = await done;
|
||||
return (await this.ask(part, now)) ? failed : [...failed, ...part];
|
||||
}, Promise.resolve([]));
|
||||
const ways = ids.flatMap((id) => {
|
||||
const grade = this.kept.get(id)?.grade;
|
||||
return grade ? [grade] : [];
|
||||
});
|
||||
return { ways, unanswered };
|
||||
}
|
||||
|
||||
/** Reads one part's grades into what is kept; false when no mirror answered. */
|
||||
private async ask(ids: number[], now: number): Promise<boolean> {
|
||||
let elements: unknown[];
|
||||
try {
|
||||
elements = await this.overpass.query(`[out:json][timeout:25];way(id:${ids.join(',')});out tags;`, 'Tours');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
const tagged = new Map<number, Record<string, string> | undefined>();
|
||||
for (const element of elements) {
|
||||
const way = wayElementSchema.safeParse(element);
|
||||
if (way.success) tagged.set(way.data.id, way.data.tags);
|
||||
}
|
||||
// A way the answer leaves out has no tags worth naming, or no longer exists: no grade either way.
|
||||
for (const id of ids) this.keep(id, gradeOf(id, tagged.get(id)), now);
|
||||
return true;
|
||||
}
|
||||
|
||||
private keep(id: number, grade: TourTrailGrade | null, at: number): void {
|
||||
this.kept.delete(id);
|
||||
this.kept.set(id, { grade, at });
|
||||
if (this.kept.size > MAX_KEPT) {
|
||||
const oldest = this.kept.keys().next().value;
|
||||
if (oldest !== undefined) this.kept.delete(oldest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export class ToursMcp {
|
||||
@Tool({
|
||||
name: 'list_tours',
|
||||
description:
|
||||
'List the saved tours of a trip (hikes and paddle tours planned or imported in the Tours addon): name, type (hike, or kayak for a paddle tour), where it came from (origin: trek, gpx or wanderer), distance in km, ascent and descent in metres, the stored routing limit of a hike (max_hiking_difficulty, SAC T1 to T6) and its route choices (hiking_preferences: avoid_steps, avoid_ferries, prefer_paths, shortest; weightings, never a grade of the path), the boat, water and chosen speed of a paddle tour (boat_type, water_context, paddle_speed_kmh in km/h, null while the suggested speed of the boat applies), the check bench of a paddle tour (paddle_check: how far the OpenStreetMap obstacle data covers its line and how many obstacles are open, to check again or decided by the travellers; a planning status, never a safety or legal clearance), the revision (raised by every saved change of the tour), and the days of the trip each tour is on.',
|
||||
'List the saved tours of a trip (hikes, bike tours and paddle tours planned or imported in the Tours addon): name, type (hike, bike, or kayak for a paddle tour), where it came from (origin: trek, gpx or wanderer), distance in km, ascent and descent in metres, the stored routing limit of a hike (max_hiking_difficulty, SAC T1 to T6) and its route choices (hiking_preferences: avoid_climbs for gentler ways, avoid_steps, avoid_ferries; weightings, never a grade of the path), the profile a bike tour is routed for (bike_profile: touring for paved cycle paths and quiet roads, road for asphalt, gravel for gravel and farm tracks, mtb for rough tracks; a routing preference, never a statement that a way is rideable or safe), the boat, water and chosen speed of a paddle tour (boat_type, water_context, paddle_speed_kmh in km/h, null while the suggested speed of the boat applies), the check bench of a paddle tour (paddle_check: how far the OpenStreetMap obstacle data covers its line and how many obstacles are open, to check again or decided by the travellers; a planning status, never a safety or legal clearance), the revision (raised by every saved change of the tour), and the days of the trip each tour is on.',
|
||||
inputSchema: { tripId: idSchema },
|
||||
annotations: TOOL_ANNOTATIONS_READONLY,
|
||||
when: addonGate(ADDON_IDS.TOURS),
|
||||
@@ -130,7 +130,7 @@ export class ToursMcp {
|
||||
@Tool({
|
||||
name: 'search_tour_pois',
|
||||
description:
|
||||
'Search places along the saved line of a tour: what a hike or a paddle tour stops for (viewpoint, peak, shelter, drinking_water, food, toilets, picnic, campsite, sights, slipway, boat_rental), within corridor_m metres of the line. Answers each place with its source, kind, address, website and phone where known, opening hours as the source has them (a hint to check, never a promise), distance_m (straight across to the line, not a detour) and along_m (from the start along the line), nearest the start first, up to 60. partial says some stretches could not be searched, capped that only the first part of a long line was. Nothing is added to the tour or the trip.',
|
||||
'Search places along the saved line of a tour: what a hike, a bike tour or a paddle tour stops for (viewpoint, peak, shelter, drinking_water, food, toilets, picnic, campsite, sights, slipway, boat_rental, bike_repair for repair stations and bike shops), within corridor_m metres of the line. Answers each place with its source, kind, address, website and phone where known, opening hours as the source has them (a hint to check, never a promise), distance_m (straight across to the line, not a detour) and along_m (from the start along the line), nearest the start first, up to 60. partial says some stretches could not be searched, capped that only the first part of a long line was. Nothing is added to the tour or the trip.',
|
||||
inputSchema: {
|
||||
tripId: idSchema,
|
||||
placeId: idSchema.describe('The place id of the tour, as list_tours gives it'),
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Tours } from '../../db/entities/Tours.entity';
|
||||
import { AddonsModule } from '../addons/addons.module';
|
||||
import { MapsModule } from '../maps/maps.module';
|
||||
import { McpSharedModule } from '../mcp-shared/mcp-shared.module';
|
||||
import { OverpassModule } from '../overpass/overpass.module';
|
||||
import { PaddleObstaclesModule } from '../paddle-obstacles/paddle-obstacles.module';
|
||||
import { PermissionsModule } from '../permissions/permissions.module';
|
||||
import { PlaceImportModule } from '../place-import/place-import.module';
|
||||
@@ -23,6 +24,8 @@ import { TourPaddleCheckService } from './tour-paddle-check.service';
|
||||
import { TourPaddlePlanService } from './tour-paddle-plan.service';
|
||||
import { TourPoiSearchController } from './tour-poi-search.controller';
|
||||
import { TourPoiSearchService } from './tour-poi-search.service';
|
||||
import { TourTrailGradeController } from './tour-trail-grade.controller';
|
||||
import { TourTrailGradeService } from './tour-trail-grade.service';
|
||||
import { ToursImportController } from './tours-import.controller';
|
||||
import { ToursWandererController } from './tours-wanderer.controller';
|
||||
import { ToursWandererService } from './tours-wanderer.service';
|
||||
@@ -49,7 +52,8 @@ import { Module } from '@nestjs/common';
|
||||
McpSharedModule,
|
||||
// PLN-02 searches places along a tour through the map search.
|
||||
MapsModule,
|
||||
|
||||
// BIK-05 reads the trail grades along a mountain bike tour from OpenStreetMap.
|
||||
OverpassModule,
|
||||
AddonsModule,
|
||||
// The wanderer import reads trails through the caller's own connection.
|
||||
WandererModule,
|
||||
@@ -60,6 +64,7 @@ import { Module } from '@nestjs/common';
|
||||
controllers: [
|
||||
TourTakeOverController,
|
||||
TourPoiSearchController,
|
||||
TourTrailGradeController,
|
||||
ToursController,
|
||||
ToursImportController,
|
||||
ToursWandererController,
|
||||
@@ -75,6 +80,7 @@ import { Module } from '@nestjs/common';
|
||||
ToursWandererService,
|
||||
TourLibraryService,
|
||||
TourPoiSearchService,
|
||||
TourTrailGradeService,
|
||||
ToursMcp,
|
||||
TourLibraryMcp,
|
||||
],
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
MAX_PLANNED_TOUR_DURATION_MINUTES,
|
||||
plannedTourDurationMinutesSchema,
|
||||
tourWebsiteSchema,
|
||||
type BikeProfile,
|
||||
type PaddleBoatType,
|
||||
type PaddleLakeStyle,
|
||||
type PaddleWaterContext,
|
||||
@@ -108,6 +109,7 @@ export class ToursService {
|
||||
max_hiking_difficulty: r.max_hiking_difficulty,
|
||||
derived_from_place_id: r.derived_from_place_id,
|
||||
origin: r.origin as TourOrigin,
|
||||
bike_profile: r.bike_profile as BikeProfile | null,
|
||||
boat_type: r.boat_type as PaddleBoatType | null,
|
||||
water_context: r.water_context as PaddleWaterContext | null,
|
||||
paddle_speed_kmh: r.paddle_speed_kmh,
|
||||
@@ -143,6 +145,7 @@ export class ToursService {
|
||||
: {}),
|
||||
match_confidence: 1,
|
||||
max_hiking_difficulty: input.max_hiking_difficulty,
|
||||
bike_profile: input.tour_type === 'bike' ? (input.bike_profile ?? null) : null,
|
||||
boat_type: input.boat_type ?? null,
|
||||
water_context: input.water_context ?? null,
|
||||
paddle_speed_kmh: input.paddle_speed_kmh ?? null,
|
||||
@@ -381,6 +384,7 @@ export class ToursService {
|
||||
row.wanderer_ref && row.tour_type === 'hike'
|
||||
? wandererDifficultyToSac(row.difficulty as WandererDifficulty | null)
|
||||
: null,
|
||||
bike_profile: row.bike_profile as BikeProfile | null,
|
||||
boat_type: row.boat_type as PaddleBoatType | null,
|
||||
water_context: row.water_context as PaddleWaterContext | null,
|
||||
paddle_speed_kmh: row.paddle_speed_kmh,
|
||||
@@ -486,6 +490,7 @@ export class ToursService {
|
||||
break_additional_minutes: row.break_additional_minutes,
|
||||
match_confidence: row.match_confidence,
|
||||
max_hiking_difficulty: row.max_hiking_difficulty,
|
||||
bike_profile: row.bike_profile as BikeProfile | null,
|
||||
boat_type: row.boat_type as PaddleBoatType | null,
|
||||
water_context: row.water_context as PaddleWaterContext | null,
|
||||
paddle_speed_kmh: row.paddle_speed_kmh,
|
||||
@@ -647,7 +652,9 @@ export class ToursService {
|
||||
const rows = this.readGpxTracks(fileBuffer, fileName);
|
||||
if (rows.length === 0) return null;
|
||||
await this.assertTourTypeEnabled(fields.tour_type);
|
||||
const paddle = {
|
||||
// What the type adds to each imported tour: a bike tour its profile, a paddle tour its boat and water.
|
||||
const typeFields = {
|
||||
bike_profile: fields.bike_profile ?? null,
|
||||
boat_type: fields.boat_type ?? null,
|
||||
water_context: fields.water_context ?? null,
|
||||
paddle_speed_kmh: null,
|
||||
@@ -663,7 +670,7 @@ export class ToursService {
|
||||
await this.toursRepo.insertTour({
|
||||
place_id: place.id,
|
||||
tour_type: fields.tour_type,
|
||||
...paddle,
|
||||
...typeFields,
|
||||
distance: metrics.distanceKm,
|
||||
elevation_gain: metrics.elevationGainM,
|
||||
elevation_loss: metrics.elevationLossM,
|
||||
@@ -682,7 +689,7 @@ export class ToursService {
|
||||
description: place.description ?? null,
|
||||
website: place.website ?? null,
|
||||
tour_type: fields.tour_type,
|
||||
...paddle,
|
||||
...typeFields,
|
||||
distance: metrics.distanceKm,
|
||||
elevation_gain: metrics.elevationGainM,
|
||||
elevation_loss: metrics.elevationLossM,
|
||||
|
||||
@@ -137,6 +137,7 @@ describe('GET /api/addons e2e (real auth guard + temp SQLite)', () => {
|
||||
// Tours is off in this database, so wanderer is not offered either.
|
||||
toursWanderer: false,
|
||||
toursHikingMap: false,
|
||||
toursCycleMap: false,
|
||||
addons: [
|
||||
{ id: 'packing', name: 'Packing', type: 'trip', icon: 'Backpack', enabled: true },
|
||||
{ id: 'journey', name: 'Journey', type: 'global', icon: 'Compass', enabled: true },
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* Tours e2e for bike tours (BIK-01 to BIK-05): a bike tour saved, read, copied,
|
||||
* imported and exported with its profile, through the real JwtAuthGuard,
|
||||
* AddonGuard and TripAccessGuard against a migrated and seeded temp SQLite db.
|
||||
* The permission check is a container spy and the broadcast goes to a
|
||||
* FakeRealtimeService, as in tours.e2e.test.ts.
|
||||
*
|
||||
* Fixtures: user 1 owns trips 5 and 6, user 2 is a member of trip 5 whom the
|
||||
* permission spy denies 'place_edit', user 3 has no access to either trip.
|
||||
*/
|
||||
import { DayAssignments } from '../../src/db/entities/DayAssignments.entity';
|
||||
import { Places } from '../../src/db/entities/Places.entity';
|
||||
import { Tours } from '../../src/db/entities/Tours.entity';
|
||||
import { Trips } from '../../src/db/entities/Trips.entity';
|
||||
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
||||
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
|
||||
import { JourneyDomainService } from '../../src/nest/journey/journey-domain.service';
|
||||
import { MapsService } from '../../src/nest/maps/maps.service';
|
||||
import { OverpassElementsClient } from '../../src/nest/overpass/overpass-elements.client';
|
||||
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
|
||||
import { RealtimeModule } from '../../src/nest/realtime/realtime.module';
|
||||
import { RealtimeService } from '../../src/nest/realtime/realtime.service';
|
||||
import { ToursModule } from '../../src/nest/tours/tours.module';
|
||||
import { createSnapshotTestDb } from '../helpers/db-mock';
|
||||
import { makePlace } from '../helpers/factories/places';
|
||||
import { countRows, deleteRows, findRow, insertRows } from '../helpers/factories/rows';
|
||||
import { setAddonEnabled } from '../helpers/factories/settings';
|
||||
import { makeTour } from '../helpers/factories/tours';
|
||||
import { addTripMember } from '../helpers/factories/trips';
|
||||
import { makeUser } from '../helpers/factories/users';
|
||||
import { FakeRealtimeService } from '../helpers/fake-realtime';
|
||||
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
||||
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
||||
import { sessionCookie } from './harness';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import cookieParser from 'cookie-parser';
|
||||
import type { Server } from 'http';
|
||||
import request from 'supertest';
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type MockInstance } from 'vitest';
|
||||
|
||||
vi.mock('../../src/nest/audit/audit-log.logger', () => ({
|
||||
LOG_LEVEL: 'error',
|
||||
logInfo: vi.fn(),
|
||||
logDebug: vi.fn(),
|
||||
logError: vi.fn(),
|
||||
logWarn: vi.fn(),
|
||||
}));
|
||||
|
||||
const { journeyHooks } = vi.hoisted(() => ({
|
||||
journeyHooks: {
|
||||
reconcileTripSkeletons: vi.fn().mockResolvedValue(undefined),
|
||||
onPlaceCreated: vi.fn().mockResolvedValue(undefined),
|
||||
onPlaceUpdated: vi.fn().mockResolvedValue(undefined),
|
||||
onPlaceDeleted: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
}));
|
||||
|
||||
const db = createSnapshotTestDb();
|
||||
const mapsPois = vi.fn();
|
||||
// The trail grades (BIK-05) read OpenStreetMap through Overpass: no network in a test.
|
||||
const overpassQuery = vi.fn();
|
||||
const realtime = new FakeRealtimeService();
|
||||
|
||||
let orm: TestOrm;
|
||||
let checkPermission: MockInstance;
|
||||
|
||||
const OWNER = 1;
|
||||
const VIEWER = 2;
|
||||
const OUTSIDER = 3;
|
||||
|
||||
const GEOMETRY = [
|
||||
[47, 11, 600],
|
||||
[47.01, 11.01, 700],
|
||||
[47.02, 11.02, 650],
|
||||
];
|
||||
const bikeBody = (overrides: Record<string, unknown> = {}) => ({
|
||||
name: 'Lakeside ride',
|
||||
tour_type: 'bike',
|
||||
bike_profile: 'gravel',
|
||||
route_geometry: GEOMETRY,
|
||||
waypoints: [
|
||||
{ lat: 47, lng: 11, role: 'start', sequence: 0 },
|
||||
{ lat: 47.02, lng: 11.02, role: 'end', sequence: 1 },
|
||||
],
|
||||
duration_seconds: 1800,
|
||||
...overrides,
|
||||
});
|
||||
const GPX = `<?xml version="1.0"?>
|
||||
<gpx version="1.1" creator="e2e"><trk><name>Gravel loop</name><trkseg>
|
||||
<trkpt lat="47" lon="11"><ele>600</ele></trkpt>
|
||||
<trkpt lat="47.01" lon="11.01"><ele>640</ele></trkpt>
|
||||
</trkseg></trk></gpx>`;
|
||||
|
||||
describe('Bike tours e2e (real guards + temp SQLite)', () => {
|
||||
let server: Server;
|
||||
let app: Awaited<ReturnType<typeof build>>;
|
||||
|
||||
async function build() {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [
|
||||
await TestUnitOfWorkModule.forRoot(db),
|
||||
await createTestMikroOrmModule(db),
|
||||
RealtimeModule,
|
||||
ToursModule,
|
||||
],
|
||||
})
|
||||
.overrideProvider(RealtimeService)
|
||||
.useValue(realtime)
|
||||
.overrideProvider(JourneyDomainService)
|
||||
.useValue(journeyHooks)
|
||||
.overrideProvider(MapsService)
|
||||
.useValue({ pois: mapsPois })
|
||||
.overrideProvider(OverpassElementsClient)
|
||||
.useValue({ query: overpassQuery })
|
||||
.compile();
|
||||
const nest = moduleRef.createNestApplication();
|
||||
nest.use(cookieParser());
|
||||
nest.useGlobalPipes(new ZodValidationPipe());
|
||||
nest.useGlobalFilters(new TrekExceptionFilter());
|
||||
await nest.init();
|
||||
return nest;
|
||||
}
|
||||
|
||||
const createViaApi = async (tripId = 5, body: Record<string, unknown> = bikeBody()) => {
|
||||
const res = await request(server).post(`/api/trips/${tripId}/tours`).set('Cookie', sessionCookie(OWNER)).send(body);
|
||||
expect(res.status).toBe(201);
|
||||
return res.body.tour.place_id as number;
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
orm = await createTestOrm(db);
|
||||
// Pinned ids: the session cookies sign for exactly these users.
|
||||
await makeUser(orm, { id: 1, username: 'owner', email: 'owner@example.test', role: 'user', password_version: 0 });
|
||||
await makeUser(orm, { id: 2, username: 'viewer', email: 'viewer@example.test', role: 'user', password_version: 0 });
|
||||
await makeUser(orm, {
|
||||
id: 3,
|
||||
username: 'outsider',
|
||||
email: 'outsider@example.test',
|
||||
role: 'user',
|
||||
password_version: 0,
|
||||
});
|
||||
await insertRows(orm, Trips, [
|
||||
{ id: 5, user: 1, title: 'Alps' },
|
||||
{ id: 6, user: 1, title: 'Other' },
|
||||
]);
|
||||
await addTripMember(orm, 5, 2);
|
||||
app = await build();
|
||||
checkPermission = vi.spyOn(app.get(PermissionsService), 'checkPermission');
|
||||
server = app.getHttpServer();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
checkPermission.mockImplementation(
|
||||
(action: string, _role: string, _owner: number, userId: number) =>
|
||||
!(action === 'place_edit' && userId === VIEWER),
|
||||
);
|
||||
await setAddonEnabled(orm, 'tours', true);
|
||||
realtime.broadcastMock.mockClear();
|
||||
await deleteRows(orm, DayAssignments);
|
||||
await deleteRows(orm, Places);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
await orm.close();
|
||||
});
|
||||
|
||||
const send = (method: 'post' | 'put', path: string, body: Record<string, unknown>, userId = OWNER) =>
|
||||
request(server)[method](`/api/trips/5/tours${path}`).set('Cookie', sessionCookie(userId)).send(body);
|
||||
|
||||
const gpxTypeOf = async (placeId: number) => {
|
||||
const res = await request(server)
|
||||
.get(`/api/trips/5/tours/${placeId}/export.gpx`)
|
||||
.set('Cookie', sessionCookie(OWNER))
|
||||
.buffer(true)
|
||||
.parse((res, done) => {
|
||||
let text = '';
|
||||
res.setEncoding('utf8');
|
||||
res.on('data', (chunk: string) => (text += chunk));
|
||||
res.on('end', () => done(null, text));
|
||||
});
|
||||
return /<type>([^<]+)<\/type>/.exec(String(res.body))?.[1] ?? '';
|
||||
};
|
||||
|
||||
describe('a bike tour and its profile (BIK-01 to BIK-04)', () => {
|
||||
it('saves a bike tour with its profile, lists it so and broadcasts the change', async () => {
|
||||
const placeId = await createViaApi();
|
||||
const row = await findRow(orm, Tours, { place: placeId });
|
||||
expect(row).toMatchObject({ bike_profile: 'gravel', max_hiking_difficulty: null, boat_type: null });
|
||||
const list = await request(server).get('/api/trips/5/tours').set('Cookie', sessionCookie(VIEWER));
|
||||
expect(list.status).toBe(200);
|
||||
const tour = list.body.tours.find((item: { place_id: number }) => item.place_id === placeId);
|
||||
expect(tour).toMatchObject({ tour_type: 'bike', bike_profile: 'gravel', duration: 30 });
|
||||
expect(tour.elevation_gain).toBe(100);
|
||||
expect(realtime.broadcastMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('refuses a bike tour without a profile, with a hiking limit, or over a via ferrata', async () => {
|
||||
const ferrata = { kind: 'ferrata', ref: 'way/1', name: null, geometry: [GEOMETRY[0], GEOMETRY[2]] };
|
||||
const bodies = [
|
||||
bikeBody({ bike_profile: null }),
|
||||
bikeBody({ bike_profile: 'ebike' }),
|
||||
bikeBody({ max_hiking_difficulty: 3 }),
|
||||
bikeBody({
|
||||
waypoints: [
|
||||
{ lat: 47, lng: 11, role: 'start', sequence: 0, fixed_leg: ferrata },
|
||||
{ lat: 47.02, lng: 11.02, role: 'end', sequence: 1 },
|
||||
],
|
||||
}),
|
||||
];
|
||||
const statuses = await bodies.reduce<Promise<number[]>>(
|
||||
async (done, body) => [...(await done), (await send('post', '', body)).status],
|
||||
Promise.resolve([]),
|
||||
);
|
||||
expect(statuses).toEqual([400, 400, 400, 400]);
|
||||
expect(await countRows(orm, Tours)).toBe(0);
|
||||
});
|
||||
|
||||
it('changes the profile with an edit, and a hike made of it keeps none', async () => {
|
||||
const placeId = await createViaApi();
|
||||
const road = await send('put', `/${placeId}`, bikeBody({ bike_profile: 'road', expected_revision: 1 }));
|
||||
expect(road.status).toBe(200);
|
||||
expect(road.body.tour).toMatchObject({ bike_profile: 'road', revision: 2 });
|
||||
const hike = await send(
|
||||
'put',
|
||||
`/${placeId}`,
|
||||
bikeBody({ tour_type: 'hike', bike_profile: undefined, max_hiking_difficulty: 2, expected_revision: 2 }),
|
||||
);
|
||||
expect(hike.status).toBe(200);
|
||||
expect(await findRow(orm, Tours, { place: placeId })).toMatchObject({
|
||||
tour_type: 'hike',
|
||||
bike_profile: null,
|
||||
max_hiking_difficulty: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('a member who may only read cannot save one', async () => {
|
||||
expect((await send('post', '', bikeBody(), VIEWER)).status).toBe(403);
|
||||
});
|
||||
|
||||
it('a copy keeps the profile', async () => {
|
||||
const placeId = await createViaApi(5, bikeBody({ bike_profile: 'mtb' }));
|
||||
const copy = await send('post', `/${placeId}/copy`, { name: 'Again' });
|
||||
expect(copy.status).toBe(201);
|
||||
expect(copy.body.tour).toMatchObject({ tour_type: 'bike', bike_profile: 'mtb' });
|
||||
});
|
||||
|
||||
it('exports a bike tour as the activity its profile names', async () => {
|
||||
const kinds: Record<string, string> = {
|
||||
touring: 'cycling',
|
||||
road: 'road_biking',
|
||||
gravel: 'gravel_cycling',
|
||||
mtb: 'mountain_biking',
|
||||
};
|
||||
const types = await Object.keys(kinds).reduce<Promise<string[]>>(
|
||||
async (done, profile) => [
|
||||
...(await done),
|
||||
await gpxTypeOf(await createViaApi(5, bikeBody({ bike_profile: profile }))),
|
||||
],
|
||||
Promise.resolve([]),
|
||||
);
|
||||
expect(types).toEqual(Object.values(kinds));
|
||||
});
|
||||
|
||||
it('imports a GPX file as a bike tour only with its profile', async () => {
|
||||
const importGpx = (fields: Record<string, string>) => {
|
||||
let req = request(server).post('/api/trips/5/tours/import/gpx').set('Cookie', sessionCookie(OWNER));
|
||||
for (const [key, value] of Object.entries(fields)) req = req.field(key, value);
|
||||
return req.attach('file', Buffer.from(GPX), { filename: 'loop.gpx', contentType: 'application/gpx+xml' });
|
||||
};
|
||||
expect((await importGpx({ tour_type: 'bike' })).status).toBe(400);
|
||||
const res = await importGpx({ tour_type: 'bike', bike_profile: 'road' });
|
||||
expect(res.status).toBe(201);
|
||||
expect(res.body.tours[0]).toMatchObject({ tour_type: 'bike', bike_profile: 'road', origin: 'gpx' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST trail-grades (BIK-05)', () => {
|
||||
const ask = (body: unknown, userId?: number, tripId = 5) => {
|
||||
const req = request(server).post(`/api/trips/${tripId}/tours/trail-grades`);
|
||||
return (userId ? req.set('Cookie', sessionCookie(userId)) : req).send(body as object);
|
||||
};
|
||||
|
||||
it('answers the grades of the ways asked about to anyone who sees the trip', async () => {
|
||||
overpassQuery.mockResolvedValueOnce([
|
||||
{ type: 'way', id: 11, tags: { 'mtb:scale': '2', 'mtb:scale:uphill': '1' } },
|
||||
]);
|
||||
const res = await ask({ way_ids: [11, 12] }, VIEWER);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
ways: [{ id: 11, mtb_scale: '2', mtb_scale_uphill: '1', mtb_scale_imba: null }],
|
||||
unanswered: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses an outsider, a body it does not know, and no session', async () => {
|
||||
expect((await ask({ way_ids: [1] }, OUTSIDER)).status).toBe(404);
|
||||
const statuses = await [{ way_ids: [] }, { way_ids: [0] }, { way_ids: [1], extra: 1 }].reduce<Promise<number[]>>(
|
||||
async (done, body) => [...(await done), (await ask(body, OWNER)).status],
|
||||
Promise.resolve([]),
|
||||
);
|
||||
expect(statuses).toEqual([400, 400, 400]);
|
||||
expect((await ask({ way_ids: [1] })).status).toBe(401);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -435,15 +435,15 @@ describe('Tours planned e2e (real guards + temp SQLite)', () => {
|
||||
it('a hike keeps the preferences switched on, reads them back, and a copy carries them', async () => {
|
||||
const placeId = await createViaApi(
|
||||
5,
|
||||
tourBody({ hiking_preferences: { avoid_steps: true, prefer_paths: true, shortest: false } }),
|
||||
tourBody({ hiking_preferences: { avoid_climbs: true, avoid_steps: true, avoid_ferries: false } }),
|
||||
);
|
||||
const detail = await request(server).get(`/api/trips/5/tours/${placeId}`).set('Cookie', sessionCookie(OWNER));
|
||||
expect(detail.body.tour.hiking_preferences).toEqual({ avoid_steps: true, prefer_paths: true });
|
||||
expect(detail.body.tour.hiking_preferences).toEqual({ avoid_climbs: true, avoid_steps: true });
|
||||
const copy = await request(server)
|
||||
.post(`/api/trips/5/tours/${placeId}/copy`)
|
||||
.set('Cookie', sessionCookie(OWNER))
|
||||
.send({ name: 'Copy' });
|
||||
expect(copy.body.tour.hiking_preferences).toEqual({ avoid_steps: true, prefer_paths: true });
|
||||
expect(copy.body.tour.hiking_preferences).toEqual({ avoid_climbs: true, avoid_steps: true });
|
||||
|
||||
// An edit that switches them all off keeps none; one without the field keeps none too.
|
||||
const off = await request(server)
|
||||
|
||||
@@ -63,6 +63,7 @@ const UNNUMBERED = [
|
||||
'Migration20200101044900_a_tour_waypoint_can_be_a_place_of_the_trip',
|
||||
'Migration20200101045000_a_hike_keeps_its_routing_preferences',
|
||||
'Migration20200101045100_a_tour_remembers_the_trip_it_was_taken_from',
|
||||
'Migration20200101045200_a_tour_can_be_ridden',
|
||||
];
|
||||
// The last step the positional runner ever had; planLegacyBaseline refuses a
|
||||
// schema_version past it.
|
||||
|
||||
@@ -101,6 +101,7 @@ describe('ToursRepository reads', () => {
|
||||
has_recorded_track: 0,
|
||||
revision: 1,
|
||||
hiking_preferences: null,
|
||||
bike_profile: null,
|
||||
},
|
||||
{
|
||||
place_id: older.id,
|
||||
@@ -131,6 +132,7 @@ describe('ToursRepository reads', () => {
|
||||
has_recorded_track: 0,
|
||||
revision: 1,
|
||||
hiking_preferences: null,
|
||||
bike_profile: null,
|
||||
},
|
||||
]);
|
||||
expect((await repo.listForTrip(trip.id)).map((r) => r.place_id)).not.toContain(plain.id);
|
||||
@@ -217,6 +219,7 @@ describe('ToursRepository writes', () => {
|
||||
has_recorded_track: 0,
|
||||
revision: 1,
|
||||
hiking_preferences: null,
|
||||
bike_profile: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -297,6 +300,7 @@ describe('ToursRepository writes', () => {
|
||||
track_marks: null,
|
||||
revision: 1,
|
||||
hiking_preferences: null,
|
||||
bike_profile: null,
|
||||
taken_over_from_trip_title: null,
|
||||
taken_over_from_tour_name: null,
|
||||
},
|
||||
|
||||
@@ -60,6 +60,12 @@ describe('global CSP: the other shipped raster presets (#2180)', () => {
|
||||
expect(await connectSrcSources()).toContain('https://tile.openmaps.fr');
|
||||
});
|
||||
|
||||
it('allows the CyclOSM shards the Tours planner offers as a cycling base map (BIK-06), and nothing wider', async () => {
|
||||
const sources = await connectSrcSources();
|
||||
for (const shard of ['a', 'b', 'c']) expect(sources).toContain(`https://${shard}.tile-cyclosm.openstreetmap.fr`);
|
||||
expect(sources).not.toContain('https://*.openstreetmap.fr');
|
||||
});
|
||||
|
||||
it('keeps the routing host, which is a different host and covers nothing here', async () => {
|
||||
// routing.openstreetmap.de was on the list all along and looks close enough
|
||||
// to hide the gap: a CSP source matches a host, not a suffix of one.
|
||||
|
||||
@@ -439,6 +439,22 @@ describe('AddonsService addon/feature flags', () => {
|
||||
expect((await svc.list()).toursHikingMap).toBe(false);
|
||||
});
|
||||
|
||||
it('CyclOSM under Tours is on until an admin switches it off, and offered only with Tours (BIK-06)', async () => {
|
||||
expect(await svc.getToursCycleMap()).toEqual({ enabled: true }); // absent row → ON
|
||||
expect((await svc.list()).toursCycleMap).toBe(false); // Tours is off
|
||||
await insertAddon({ id: 'tours', name: 'Tours', enabled: 1 });
|
||||
expect((await svc.list()).toursCycleMap).toBe(true);
|
||||
|
||||
expect(await svc.updateToursCycleMap(false)).toEqual({ enabled: false });
|
||||
expect(await storedAppSetting('tours_cycle_map_enabled')).toEqual({
|
||||
key: 'tours_cycle_map_enabled',
|
||||
value: 'false',
|
||||
});
|
||||
expect((await svc.list()).toursCycleMap).toBe(false);
|
||||
// The two maps are switched apart.
|
||||
expect((await svc.list()).toursHikingMap).toBe(true);
|
||||
});
|
||||
|
||||
it('getCollabFeatures is opt-out: absent rows default ON, only false disables', async () => {
|
||||
await setAppSetting('collab_chat_enabled', 'false');
|
||||
await setAppSetting('collab_polls_enabled', 'true');
|
||||
|
||||
@@ -48,6 +48,8 @@ const addonsStub = () =>
|
||||
updateToursWanderer: vi.fn((enabled: boolean) => ({ enabled })),
|
||||
getToursHikingMap: vi.fn(() => ({ enabled: true })),
|
||||
updateToursHikingMap: vi.fn((enabled: boolean) => ({ enabled })),
|
||||
getToursCycleMap: vi.fn(() => ({ enabled: true })),
|
||||
updateToursCycleMap: vi.fn((enabled: boolean) => ({ enabled })),
|
||||
getPlacesPhotos: vi.fn(() => ({ enabled: false })),
|
||||
updatePlacesPhotos: vi.fn((enabled: boolean) => ({ enabled })),
|
||||
getPlacesAutocomplete: vi.fn(() => ({ enabled: false })),
|
||||
@@ -495,6 +497,7 @@ describe('AdminController feature toggles', () => {
|
||||
[() => c.updateBagTracking(user, { enabled: true }, req), 'admin.bag_tracking', 'updateBagTracking'],
|
||||
[() => c.updateToursWanderer(user, { enabled: true }, req), 'admin.tours_wanderer', 'updateToursWanderer'],
|
||||
[() => c.updateToursHikingMap(user, { enabled: true }, req), 'admin.tours_hiking_map', 'updateToursHikingMap'],
|
||||
[() => c.updateToursCycleMap(user, { enabled: true }, req), 'admin.tours_cycle_map', 'updateToursCycleMap'],
|
||||
[() => c.updatePlacesPhotos(user, { enabled: true }, req), 'admin.places_photos', 'updatePlacesPhotos'],
|
||||
[
|
||||
() => c.updatePlacesAutocomplete(user, { enabled: true }, req),
|
||||
@@ -517,6 +520,7 @@ describe('AdminController feature toggles', () => {
|
||||
expect(await c.getBagTracking()).toEqual({ enabled: false });
|
||||
expect(await c.getToursWanderer()).toEqual({ enabled: false });
|
||||
expect(await c.getToursHikingMap()).toEqual({ enabled: true });
|
||||
expect(await c.getToursCycleMap()).toEqual({ enabled: true });
|
||||
expect(await c.getPlacesPhotos()).toEqual({ enabled: false });
|
||||
expect(await c.getPlacesAutocomplete()).toEqual({ enabled: false });
|
||||
expect(await c.getPlacesDetails()).toEqual({ enabled: false });
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
ferrataTileBlocks,
|
||||
ferrataTileKeysFor,
|
||||
ferrataTilesBbox,
|
||||
overpassFerrataAnswerSchema,
|
||||
parseFerrataGrade,
|
||||
snapFerrataBbox,
|
||||
splitFerratasByTile,
|
||||
@@ -211,18 +210,6 @@ describe('buildFerrataQuery', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('overpassFerrataAnswerSchema', () => {
|
||||
it('accepts an element list with or without a remark', () => {
|
||||
expect(overpassFerrataAnswerSchema.safeParse({ elements: [] }).success).toBe(true);
|
||||
expect(overpassFerrataAnswerSchema.safeParse({ elements: [], remark: 'runtime error' }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses a body without elements', () => {
|
||||
expect(overpassFerrataAnswerSchema.safeParse({ error: 'x' }).success).toBe(false);
|
||||
expect(overpassFerrataAnswerSchema.safeParse(undefined).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toFerratas', () => {
|
||||
it('maps a way to the wire row', () => {
|
||||
const { ferratas, truncated } = toFerratas([way(42, { name: ' Pisciadù ', via_ferrata_scale: '3+' })]);
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
import { FerrataTiles } from '../../../src/db/entities/FerrataTiles.entity';
|
||||
import { FerrataTilesRepository } from '../../../src/db/repositories/FerrataTiles.repository';
|
||||
import { DomainError } from '../../../src/nest/common/domain-error';
|
||||
import { FerratasOverpassClient } from '../../../src/nest/ferratas/ferratas-overpass.client';
|
||||
import { FerratasModule } from '../../../src/nest/ferratas/ferratas.module';
|
||||
import {
|
||||
FERRATA_TILE_STALE_MS,
|
||||
FERRATAS_UNAVAILABLE_MESSAGE,
|
||||
FerratasService,
|
||||
} from '../../../src/nest/ferratas/ferratas.service';
|
||||
import { OverpassElementsClient } from '../../../src/nest/overpass/overpass-elements.client';
|
||||
import { createSnapshotTestDb } from '../../helpers/db-mock';
|
||||
import { countRows, deleteRows, findRow, findRows, insertRows } from '../../helpers/factories/rows';
|
||||
import { resetTestDb } from '../../helpers/test-db';
|
||||
@@ -73,7 +73,7 @@ async function boot(): Promise<void> {
|
||||
t = await createTestModule({
|
||||
db,
|
||||
imports: [FerratasModule],
|
||||
overrides: [{ provide: FerratasOverpassClient, useValue: { query } }],
|
||||
overrides: [{ provide: OverpassElementsClient, useValue: { query } }],
|
||||
});
|
||||
service = t.get(FerratasService);
|
||||
}
|
||||
|
||||
+22
-10
@@ -1,20 +1,20 @@
|
||||
import { RuntimeEnvService } from '../../../src/nest/app-config/runtime-env.service';
|
||||
import { FerratasOverpassClient } from '../../../src/nest/ferratas/ferratas-overpass.client';
|
||||
import { parseOverpassEndpoints } from '../../../src/nest/maps/overpass.constants';
|
||||
import { OverpassElementsClient, overpassAnswerSchema } from '../../../src/nest/overpass/overpass-elements.client';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const integrations = { overpassUrl: undefined as string | undefined, overpassTimeoutMs: 5000 };
|
||||
let client: FerratasOverpassClient;
|
||||
let client: OverpassElementsClient;
|
||||
|
||||
beforeEach(async () => {
|
||||
integrations.overpassUrl = 'https://a.example/api/interpreter,https://b.example/api/interpreter';
|
||||
integrations.overpassTimeoutMs = 5000;
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
providers: [FerratasOverpassClient, { provide: RuntimeEnvService, useValue: { env: () => ({ integrations }) } }],
|
||||
providers: [OverpassElementsClient, { provide: RuntimeEnvService, useValue: { env: () => ({ integrations }) } }],
|
||||
}).compile();
|
||||
client = moduleRef.get(FerratasOverpassClient);
|
||||
client = moduleRef.get(OverpassElementsClient);
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
@@ -46,12 +46,12 @@ describe('parseOverpassEndpoints', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('FerratasOverpassClient.query', () => {
|
||||
describe('OverpassElementsClient.query', () => {
|
||||
it('POSTs the query with a user agent and a deadline, and answers the elements', async () => {
|
||||
const fetcher = vi.fn(async (_url: string, _init: RequestInit) => json({ elements: [{ type: 'way', id: 1 }] }));
|
||||
vi.stubGlobal('fetch', fetcher);
|
||||
integrations.overpassUrl = 'https://a.example/api/interpreter';
|
||||
const elements = await client.query('[out:json];way(1);out;');
|
||||
const elements = await client.query('[out:json];way(1);out;', 'Ferratas');
|
||||
expect(elements).toEqual([{ type: 'way', id: 1 }]);
|
||||
const [url, init] = fetcher.mock.calls[0] ?? [];
|
||||
expect(url).toBe('https://a.example/api/interpreter');
|
||||
@@ -71,7 +71,7 @@ describe('FerratasOverpassClient.query', () => {
|
||||
return json({ elements: [{ type: 'way', id: 2 }] });
|
||||
}),
|
||||
);
|
||||
expect(await client.query('q')).toEqual([{ type: 'way', id: 2 }]);
|
||||
expect(await client.query('q', 'Ferratas')).toEqual([{ type: 'way', id: 2 }]);
|
||||
expect(signals.every((s) => s.aborted)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('FerratasOverpassClient.query', () => {
|
||||
'fetch',
|
||||
vi.fn(async (url: string) => (url.startsWith('https://a.') ? json({}, 504) : json({ elements: 'nope' }))),
|
||||
);
|
||||
await expect(client.query('q')).rejects.toThrow('Could not reach any Overpass endpoint');
|
||||
await expect(client.query('q', 'Ferratas')).rejects.toThrow('Could not reach any Overpass endpoint');
|
||||
});
|
||||
|
||||
it('throws when a mirror cannot be reached at all', async () => {
|
||||
@@ -90,7 +90,7 @@ describe('FerratasOverpassClient.query', () => {
|
||||
throw new TypeError('fetch failed');
|
||||
}),
|
||||
);
|
||||
await expect(client.query('q')).rejects.toThrow('Could not reach any Overpass endpoint');
|
||||
await expect(client.query('q', 'Ferratas')).rejects.toThrow('Could not reach any Overpass endpoint');
|
||||
});
|
||||
|
||||
it('refuses an answer larger than the cap', async () => {
|
||||
@@ -101,6 +101,18 @@ describe('FerratasOverpassClient.query', () => {
|
||||
new Response('{"elements":[]}', { status: 200, headers: { 'content-length': String(9 * 1024 * 1024) } }),
|
||||
),
|
||||
);
|
||||
await expect(client.query('q')).rejects.toThrow('Could not reach any Overpass endpoint');
|
||||
await expect(client.query('q', 'Ferratas')).rejects.toThrow('Could not reach any Overpass endpoint');
|
||||
});
|
||||
});
|
||||
|
||||
describe('overpassAnswerSchema', () => {
|
||||
it('accepts an element list with or without a remark', () => {
|
||||
expect(overpassAnswerSchema.safeParse({ elements: [] }).success).toBe(true);
|
||||
expect(overpassAnswerSchema.safeParse({ elements: [], remark: 'runtime error' }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('refuses a body without elements', () => {
|
||||
expect(overpassAnswerSchema.safeParse({ error: 'x' }).success).toBe(false);
|
||||
expect(overpassAnswerSchema.safeParse(undefined).success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { OverpassElementsClient } from '../../../src/nest/overpass/overpass-elements.client';
|
||||
import { TourTrailGradeService } from '../../../src/nest/tours/tour-trail-grade.service';
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// TOUR-GRADE-001 to TOUR-GRADE-005
|
||||
|
||||
const query = vi.fn<(q: string, caller: string) => Promise<unknown[]>>();
|
||||
let service: TourTrailGradeService;
|
||||
|
||||
beforeEach(async () => {
|
||||
query.mockReset();
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
providers: [TourTrailGradeService, { provide: OverpassElementsClient, useValue: { query } }],
|
||||
}).compile();
|
||||
service = moduleRef.get(TourTrailGradeService);
|
||||
});
|
||||
|
||||
const way = (id: number, tags: Record<string, string>) => ({ type: 'way', id, tags });
|
||||
|
||||
describe('TourTrailGradeService (BIK-05)', () => {
|
||||
it('TOUR-GRADE-001: reads each scale to its steps and leaves a way without one out', async () => {
|
||||
query.mockResolvedValue([
|
||||
way(1, { 'mtb:scale': '2+', 'mtb:scale:uphill': '3' }),
|
||||
way(2, { 'mtb:scale': 'S1', 'mtb:scale:imba': '2' }),
|
||||
way(3, { highway: 'track' }),
|
||||
way(4, { 'mtb:scale': '9', 'mtb:scale:imba': 'hard' }),
|
||||
]);
|
||||
const answer = await service.grades({ way_ids: [1, 2, 3, 4, 5] });
|
||||
expect(answer).toEqual({
|
||||
ways: [
|
||||
{ id: 1, mtb_scale: '2+', mtb_scale_uphill: '3', mtb_scale_imba: null },
|
||||
{ id: 2, mtb_scale: '1', mtb_scale_uphill: null, mtb_scale_imba: '2' },
|
||||
],
|
||||
unanswered: [],
|
||||
});
|
||||
expect(query).toHaveBeenCalledWith('[out:json][timeout:25];way(id:1,2,3,4,5);out tags;', 'Tours');
|
||||
});
|
||||
|
||||
it('TOUR-GRADE-002: names the ways it could not ask about, never guesses them', async () => {
|
||||
query.mockRejectedValue(new Error('all mirrors down'));
|
||||
expect(await service.grades({ way_ids: [7, 8] })).toEqual({ ways: [], unanswered: [7, 8] });
|
||||
});
|
||||
|
||||
it('TOUR-GRADE-003: asks a long tour in parts, one after another, and keeps what one part answered', async () => {
|
||||
const ids = Array.from({ length: 900 }, (_, index) => index + 1);
|
||||
query
|
||||
.mockResolvedValueOnce([way(1, { 'mtb:scale': '0' })])
|
||||
.mockRejectedValueOnce(new Error('timeout'))
|
||||
.mockResolvedValueOnce([way(900, { 'mtb:scale': '5' })]);
|
||||
const answer = await service.grades({ way_ids: ids });
|
||||
expect(query).toHaveBeenCalledTimes(3);
|
||||
expect(answer.ways.map((grade) => grade.id)).toEqual([1, 900]);
|
||||
expect(answer.unanswered).toHaveLength(400);
|
||||
expect(answer.unanswered[0]).toBe(401);
|
||||
});
|
||||
|
||||
it('TOUR-GRADE-004: asks a way it already read only again a day later', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
query.mockResolvedValue([way(1, { 'mtb:scale': '3' })]);
|
||||
await service.grades({ way_ids: [1, 1] });
|
||||
await service.grades({ way_ids: [1] });
|
||||
expect(query).toHaveBeenCalledTimes(1);
|
||||
vi.advanceTimersByTime(24 * 60 * 60 * 1000 + 1);
|
||||
await service.grades({ way_ids: [1] });
|
||||
expect(query).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('TOUR-GRADE-005: skips what is no way in the answer', async () => {
|
||||
query.mockResolvedValue([{ type: 'node', id: 1, tags: { 'mtb:scale': '1' } }, { nonsense: true }]);
|
||||
expect(await service.grades({ way_ids: [1] })).toEqual({ ways: [], unanswered: [] });
|
||||
});
|
||||
});
|
||||
@@ -188,6 +188,7 @@ describe('ToursService planner contracts (mock-only; SQLite rollback semantics a
|
||||
portage_speed_kmh: null,
|
||||
paddle_lake_style: null,
|
||||
hiking_preferences: null,
|
||||
bike_profile: null,
|
||||
});
|
||||
expect((setup.toursRepo.insertTour.mock.calls[0][0] as { distance: number }).distance).toBeGreaterThan(0);
|
||||
expect(setup.waypointsRepo.insertForPlace).toHaveBeenCalledExactlyOnceWith(42, request.waypoints);
|
||||
@@ -305,6 +306,7 @@ describe('ToursService planner contracts (mock-only; SQLite rollback semantics a
|
||||
portage_speed_kmh: null,
|
||||
paddle_lake_style: null,
|
||||
hiking_preferences: null,
|
||||
bike_profile: null,
|
||||
});
|
||||
expect(setup.waypointsRepo.deleteForPlace).toHaveBeenCalledExactlyOnceWith(42);
|
||||
expect(setup.waypointsRepo.insertForPlace).toHaveBeenCalledExactlyOnceWith(42, update.waypoints);
|
||||
|
||||
@@ -250,7 +250,7 @@ describe('ToursService planner creation', () => {
|
||||
it('TOURS-SVC-010: a type the catalogue holds but has not enabled is a 400 without a write', async () => {
|
||||
// The contract only lets `hike` through today; this pins what happens the
|
||||
// day it lets more keys in before the planner offers them.
|
||||
const bike = { ...request, tour_type: 'bike' } as unknown as typeof request;
|
||||
const bike = { ...request, tour_type: 'run' } as unknown as typeof request;
|
||||
|
||||
await expect(service.createTour(tripId, bike)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
@@ -262,7 +262,7 @@ describe('ToursService planner creation', () => {
|
||||
it('TOURS-SVC-011: an edit to a disabled type is a 400 and leaves the saved tour alone', async () => {
|
||||
const created = await service.createTour(tripId, request);
|
||||
broadcast.mockReset();
|
||||
const bike = { ...request, name: 'Renamed', tour_type: 'bike' } as unknown as typeof request;
|
||||
const bike = { ...request, name: 'Renamed', tour_type: 'run' } as unknown as typeof request;
|
||||
|
||||
await expect(service.updateTour(tripId, String(created.tour.place_id), bike)).rejects.toBeInstanceOf(
|
||||
BadRequestException,
|
||||
|
||||
@@ -135,6 +135,9 @@ export default defineConfig({
|
||||
'src/nest/notifications/**/*.ts': { statements: 83, branches: 72, functions: 85, lines: 87 },
|
||||
'src/nest/oauth/**/*.ts': { statements: 96, branches: 95, functions: 97, lines: 97 },
|
||||
'src/nest/oidc/**/*.ts': { statements: 88, branches: 83, functions: 91, lines: 92 },
|
||||
// The Overpass client the ferratas and the tours share, moved out of ferratas in this
|
||||
// change with its tests: pinned at the 80 floor until a full run measures it.
|
||||
'src/nest/overpass/**/*.ts': { statements: 80, branches: 80, functions: 80, lines: 80 },
|
||||
'src/nest/packing/**/*.ts': { statements: 92, branches: 83, functions: 99, lines: 97 },
|
||||
'src/nest/paddle-obstacles/**/*.ts': { statements: 96, branches: 92, functions: 94, lines: 97 },
|
||||
// PAD-11, new in this change: measured by its unit and e2e suites, one point of slack.
|
||||
|
||||
@@ -720,6 +720,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'خريطة المشي (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'يعرض OpenHikingMap كخريطة أساسية في مخطط الجولات. مجاني للتثبيتات غير التجارية حتى 500,000 مربع شهريًا؛ أما التثبيت التجاري أو الأكبر فيحتاج أولًا إلى اتفاق مع openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'خريطة الدراجات (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'يعرض CyclOSM كخريطة أساس لجولات الدراجات في مخطط الجولات. تقدمها OpenStreetMap France للاستخدام المعتدل؛ والتثبيت ذو الاستخدام الكثيف أو التجاري يوقفها أو يتفق مع المشغّل أولًا',
|
||||
'admin.addons.noAddons': 'لا توجد إضافات متاحة',
|
||||
'admin.weather.title': 'بيانات الطقس',
|
||||
'admin.weather.badge': 'منذ 24 مارس 2026',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'قمر صناعي',
|
||||
'map.baseLayer.hiking': 'المشي',
|
||||
'map.baseLayer.hikingDown': 'لا يستجيب OpenHikingMap حاليًا. انتقل إلى خريطة أخرى، وتبقى جولتك كما هي.',
|
||||
'map.baseLayer.cycle': 'دراجات',
|
||||
'map.baseLayer.cycleDown': 'لا يستجيب CyclOSM الآن. انتقل إلى خريطة أخرى؛ تبقى جولتك كما هي.',
|
||||
'map.baseLayer.switchToSatellite': 'التبديل إلى عرض القمر الصناعي',
|
||||
'map.baseLayer.switchToDefault': 'التبديل إلى عرض الخريطة',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -107,6 +107,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'تفتح الملاحة نقطة بداية الدرب، ولا توفّر إرشادًا على طول مسار الجولة.',
|
||||
'tours.planner.title': 'تخطيط جولة',
|
||||
'tours.planner.newTour': 'تخطيط جولة جديدة',
|
||||
'tours.planner.addTour': 'إضافة جولة',
|
||||
'tours.planner.addTourHint': 'خطط لجولة جديدة أو استورد ملفات GPX أو انقل جولة من رحلة أخرى',
|
||||
'tours.planner.neutralTitle': 'تخطيط جولة',
|
||||
'tours.planner.neutralBody': 'ابدأ جولة جديدة وانقر على الخريطة لتحديد نقاط المسار.',
|
||||
'tours.planner.newTitle': 'جولة جديدة',
|
||||
@@ -122,6 +124,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'توسيع مخطط الارتفاع',
|
||||
'tours.planner.name': 'اسم الجولة',
|
||||
'tours.planner.namePlaceholder': 'جولة مشي بلا اسم',
|
||||
'tours.planner.namePlaceholder.bike': 'جولة دراجة بلا اسم',
|
||||
'tours.planner.namePlaceholder.kayak': 'جولة تجديف بلا اسم',
|
||||
'tours.planner.mapSearch': 'ابحث عن مكان أو عنوان',
|
||||
'tours.planner.placePoints': 'تعيين النقاط',
|
||||
'tours.planner.pointPlaced': 'تم تعيين النقطة',
|
||||
@@ -188,6 +192,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'بانتظار الحساب',
|
||||
'tours.planner.status.routing': 'جارٍ حساب مسار المشي…',
|
||||
'tours.planner.status.routingFailed': 'فشل حساب المسار. لا يزال آخر مسار حُسب بنجاح معروضًا.',
|
||||
'tours.planner.status.unreachable': 'لا يمكن الوصول إلى النقطة {point}',
|
||||
'tours.planner.unreachable.hike':
|
||||
'لا يصل أي مسار مشي إلى النقطة {point}: أقرب مسار يعرفه الموجّه على بعد {distance}. انقل النقطة إلى مسار أو ارسم المقطع إليها يدويًا.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'لا يصل أي طريق للدراجات إلى النقطة {point}: أقرب طريق يعرفه الموجّه على بعد {distance}. انقل النقطة إلى طريق أو ارسم المقطع إليها يدويًا.',
|
||||
'tours.planner.unreachable.show': 'عرض النقطة {point}',
|
||||
'tours.planner.status.elevation': 'جارٍ تحميل الارتفاع…',
|
||||
'tours.planner.status.elevationFailed': 'بيانات الارتفاع غير متاحة. لا يزال المسار المحسوب معروضًا.',
|
||||
'tours.planner.status.saving': 'جارٍ الحفظ…',
|
||||
@@ -349,6 +359,62 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'معالم',
|
||||
'tours.poi.cat.slipway': 'نقاط إنزال القوارب',
|
||||
'tours.poi.cat.boat_rental': 'تأجير القوارب',
|
||||
'tours.poi.cat.bike_repair': 'إصلاح الدراجات',
|
||||
'tours.planner.tourTypeHint.hike': 'سيرًا على الأقدام، ضمن صعوبة تحددها',
|
||||
'tours.planner.tourTypeHint.bike': 'بالدراجة، مسار مناسب للتجوال أو الطريق أو الحصى أو الجبلية',
|
||||
'tours.planner.tourTypeHint.kayak': 'على الماء، بقارب الكاياك أو الكانو أو لوح التجديف',
|
||||
'tours.bike.profile': 'الملف',
|
||||
'tours.bike.profile.touring': 'تجوال',
|
||||
'tours.bike.profile.road': 'طريق',
|
||||
'tours.bike.profile.gravel': 'حصى',
|
||||
'tours.bike.profile.mtb': 'جبلية',
|
||||
'tours.bike.profileHint.touring': 'يفضل مسارات الدراجات والطرق الهادئة، مرصوفة حيثما أمكن.',
|
||||
'tours.bike.profileHint.road': 'يلتزم بالأسفلت؛ وتُعرض المقاطع غير المرصوفة المعروفة.',
|
||||
'tours.bike.profileHint.gravel': 'يسلك طرق الحصى والطرق الزراعية، لا المسارات الفردية التقنية.',
|
||||
'tours.bike.profileHint.mtb': 'يسلك الطرق الوعرة كما هي؛ وتُعرض درجات المسارات حيث تُرسم.',
|
||||
'tours.bike.timeEstimated': 'وقت الركوب (تقديري)',
|
||||
'tours.bike.timeEstimatedShort': 'وقت الركوب',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'كيف يُحسب وقت الركوب',
|
||||
'tours.bike.timeEstimatedInfo': 'وقت الموجّه للملف المختار مع سرعته والصعود. قيمة تخطيطية دون الاستراحات.',
|
||||
'tours.bike.ways.title': 'السطح والطرق',
|
||||
'tours.bike.ways.surface': 'السطح',
|
||||
'tours.bike.ways.surface.paved': 'أسفلت',
|
||||
'tours.bike.ways.surface.rough': 'رصف خشن',
|
||||
'tours.bike.ways.surface.compacted': 'مدكوك',
|
||||
'tours.bike.ways.surface.gravel': 'حصى',
|
||||
'tours.bike.ways.surface.ground': 'أرض طبيعية',
|
||||
'tours.bike.ways.surface.unknown': 'غير موجود في البيانات',
|
||||
'tours.bike.ways.way': 'نوع الطريق',
|
||||
'tours.bike.ways.way.cyclePath': 'مسار دراجات',
|
||||
'tours.bike.ways.way.cycleLane': 'حارة دراجات بجانب السير',
|
||||
'tours.bike.ways.way.quiet': 'شارع هادئ',
|
||||
'tours.bike.ways.way.road': 'طريق فيه حركة مرور',
|
||||
'tours.bike.ways.way.track': 'طريق زراعي أو حرجي',
|
||||
'tours.bike.ways.way.path': 'ممر',
|
||||
'tours.bike.ways.way.ferry': 'عبّارة',
|
||||
'tours.bike.ways.way.other': 'أخرى',
|
||||
'tours.bike.ways.loading': 'جارٍ قراءة الطرق على طول المسار…',
|
||||
'tours.bike.ways.offline': 'يتطلب السطح ودرجات المسارات اتصالًا.',
|
||||
'tours.bike.ways.failed': 'تعذرت قراءة الطرق على طول المسار. المسار نفسه لم يتأثر.',
|
||||
'tours.bike.ways.source':
|
||||
'من OpenStreetMap كما يقرأه الموجّه. حيث لا تذكر البيانات شيئًا يكون السطح مجهولًا، لا جيدًا.',
|
||||
'tours.bike.ways.showOnMap': 'عرض على الخريطة',
|
||||
'tours.bike.ways.unpavedBadge': '{share} غير مرصوف',
|
||||
'tours.bike.ways.unpavedNotice': '{length} من المسار معروف بأنه غير مرصوف.',
|
||||
'tours.bike.trail.title': 'درجة المسار (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0، بلا صعوبة خاصة',
|
||||
'tours.bike.trail.s1': 'S1، عوائق صغيرة',
|
||||
'tours.bike.trail.s2': 'S2، جذور ودرجات وأرض رخوة',
|
||||
'tours.bike.trail.s3': 'S3، صخور وانحدار ومنعطفات ضيقة',
|
||||
'tours.bike.trail.s4': 'S4 فأعلى، أرض للخبراء',
|
||||
'tours.bike.trail.none': 'لا توجد درجة في البيانات',
|
||||
'tours.bike.trail.unasked': 'تعذر الاستعلام هذه المرة',
|
||||
'tours.bike.trail.uphillTitle': 'درجة الصعود (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'صعود {step}',
|
||||
'tours.bike.trail.note':
|
||||
'الدرجات كما وضعها مساهمو OpenStreetMap. الطريق بلا درجة مجهول وليس سهلًا. ولا تقول شيئًا عن ملاءمة المسار لك.',
|
||||
'tours.bike.trail.loading': 'جارٍ قراءة درجات المسارات…',
|
||||
'tours.bike.trail.failed': 'تعذرت قراءة درجات المسارات هذه المرة.',
|
||||
'tours.copy.action': 'حفظ كجولة جديدة',
|
||||
'tours.copy.hint': 'ينسخ هذه الجولة كجولة جديدة مستقلة دون أيامها. يبقى الأصل كما هو.',
|
||||
'tours.copy.name': '{name} (نسخة)',
|
||||
@@ -361,6 +427,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'يتطلب تنزيل ملف GPX اتصالًا بالخادم.',
|
||||
'tours.gpxExport.noRoute': 'لا يوجد لهذه الجولة مسار محفوظ للتصدير.',
|
||||
'tours.gpxExport.failed': 'تعذّر إنشاء ملف GPX.',
|
||||
'tours.gpxExport.share': 'مشاركة GPX',
|
||||
'tours.gpxExport.shareShort': 'مشاركة',
|
||||
'tours.gpxExport.shareSaveFirst': 'احفظ الجولة أولًا لمشاركتها بصيغة GPX',
|
||||
'tours.gpxExport.shareReady': 'ملف GPX جاهز. اضغط على مشاركة مرة أخرى لفتح نافذة المشاركة.',
|
||||
'tours.gpxExport.shareFailed': 'لم تنجح المشاركة. لا يزال بإمكانك تنزيل ملف GPX.',
|
||||
'tours.revision.remoteTitle': 'حُفظت هذه الجولة في مكان آخر',
|
||||
'tours.revision.remoteBody': 'حفظها شخص ما، أو أنت على جهاز آخر، أثناء تعديلك. تبقى تغييراتك حتى تقرّر.',
|
||||
'tours.revision.loadNewer': 'تحميل الأحدث',
|
||||
@@ -389,14 +460,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'ما الذي يفعله اختيار المسار',
|
||||
'tours.preferences.info':
|
||||
'تؤثر هذه الخيارات في الطريق الذي يختاره الموجّه. حيث لا يوجد طريق آخر، يمر المسار هناك رغم ذلك. ولا تقول شيئًا عن صعوبة الطريق؛ فحد الصعوبة أعلاه يحدد ما يمكن للمسار استخدامه.',
|
||||
'tours.preferences.avoid_climbs': 'تجنّب المنحدرات الشديدة',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'طرق أقل انحدارًا حيث توجد، حتى لو كانت أطول. حيث لا يوجد طريق بديل، يبقى المسار شديد الانحدار.',
|
||||
'tours.preferences.avoid_steps': 'تجنّب الدرج',
|
||||
'tours.preferences.avoid_stepsHint': 'الدرج فقط حيث لا يوجد طريق بديل.',
|
||||
'tours.preferences.avoid_ferries': 'تجنّب العبّارات',
|
||||
'tours.preferences.avoid_ferriesHint': 'عبر البر حيث يوجد طريق بديل، حتى لو كان أطول.',
|
||||
'tours.preferences.prefer_paths': 'تفضيل ممرات المشاة',
|
||||
'tours.preferences.prefer_pathsHint': 'مسارات مستقلة بدل الطرق وأرصفتها حيث توجد.',
|
||||
'tours.preferences.shortest': 'الأقصر بدل الأسرع',
|
||||
'tours.preferences.shortestHint': 'يُختار الطريق بطوله لا بزمنه؛ غالبًا أقصر قليلًا، وليس دائمًا أسرع.',
|
||||
'tours.planner.tourType': 'نوع الجولة',
|
||||
'tours.paddle.boat': 'القارب',
|
||||
'tours.paddle.boat.kayak': 'كاياك',
|
||||
|
||||
@@ -753,6 +753,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Gəzinti xəritəsi (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'OpenHikingMap-i tur planlayıcısında əsas xəritə kimi təklif edir. Qeyri-kommersiya quraşdırmalar üçün ayda 500.000 plitəyə qədər pulsuzdur; kommersiya və ya daha böyük quraşdırma əvvəlcə openmaps.fr ilə razılaşma tələb edir',
|
||||
'admin.toursCycleMap.title': 'Velosiped xəritəsi (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Tur planlayıcısında velosiped turları üçün CyclOSM-i əsas xəritə kimi təklif edir. OpenStreetMap France onu orta istifadə üçün təqdim edir; ağır və ya kommersiya quraşdırması onu söndürür və ya əvvəlcə operatorla razılaşır',
|
||||
'admin.addons.noAddons': 'Heç bir əlavə mövcud deyil',
|
||||
'admin.weather.title': 'Hava məlumatları',
|
||||
'admin.weather.badge': '24 mart 2026-cı ildən',
|
||||
|
||||
@@ -23,6 +23,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Peyk',
|
||||
'map.baseLayer.hiking': 'Gəzinti',
|
||||
'map.baseLayer.hikingDown': 'OpenHikingMap hazırda cavab vermir. Başqa xəritəyə keçin; turunuz olduğu kimi qalır.',
|
||||
'map.baseLayer.cycle': 'Velosiped',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM hazırda cavab vermir. Başqa xəritəyə keç; turun olduğu kimi qalır.',
|
||||
'map.baseLayer.switchToSatellite': 'Peyk görünüşünə keç',
|
||||
'map.baseLayer.switchToDefault': 'Xəritə görünüşünə keç',
|
||||
|
||||
|
||||
@@ -93,6 +93,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Naviqasiya cığırın başlanğıcını açır; marşrut boyunca istiqamət göstərmir.',
|
||||
'tours.planner.title': 'Tur planlaşdır',
|
||||
'tours.planner.newTour': 'Yeni tur planlaşdır',
|
||||
'tours.planner.addTour': 'Tur əlavə et',
|
||||
'tours.planner.addTourHint': 'Yeni tur planla, GPX fayllarını idxal et və ya başqa səfərdən tur götür',
|
||||
'tours.planner.neutralTitle': 'Tur planlaşdır',
|
||||
'tours.planner.neutralBody': 'Yeni tur başladın və marşrut nöqtələrini qoymaq üçün xəritəyə klikləyin.',
|
||||
'tours.planner.newTitle': 'Yeni tur',
|
||||
@@ -109,6 +111,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Yüksəklik profilini aç',
|
||||
'tours.planner.name': 'Turun adı',
|
||||
'tours.planner.namePlaceholder': 'Adsız yürüş',
|
||||
'tours.planner.namePlaceholder.bike': 'Adsız velosiped turu',
|
||||
'tours.planner.namePlaceholder.kayak': 'Adsız avarçəkmə turu',
|
||||
'tours.planner.mapSearch': 'Yer və ya ünvan axtar',
|
||||
'tours.planner.placePoints': 'Nöqtə qoy',
|
||||
'tours.planner.pointPlaced': 'Nöqtə qoyuldu',
|
||||
@@ -172,6 +176,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Hesablama gözlənilir',
|
||||
'tours.planner.status.routing': 'Piyada marşrutu hesablanır…',
|
||||
'tours.planner.status.routingFailed': 'Marşrut hesablanmadı. Son uğurlu marşrut hələ göstərilir.',
|
||||
'tours.planner.status.unreachable': '{point} nöqtəsinə çatmaq olmur',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Heç bir piyada yolu {point} nöqtəsinə çatmır: marşrutlaşdırıcının bildiyi ən yaxın yol {distance} uzaqdadır. Nöqtəni yola köçür və ya ora hissəni əllə çək.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Heç bir velosiped yolu {point} nöqtəsinə çatmır: marşrutlaşdırıcının bildiyi ən yaxın yol {distance} uzaqdadır. Nöqtəni yola köçür və ya ora hissəni əllə çək.',
|
||||
'tours.planner.unreachable.show': '{point} nöqtəsini göstər',
|
||||
'tours.planner.status.elevation': 'Yüksəklik yüklənir…',
|
||||
'tours.planner.status.elevationFailed': 'Yüksəklik əlçatan deyil. Hesablanmış marşrut hələ göstərilir.',
|
||||
'tours.planner.status.saving': 'Saxlanılır…',
|
||||
@@ -340,6 +350,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Görməli yerlər',
|
||||
'tours.poi.cat.slipway': 'Suya buraxılma yerləri',
|
||||
'tours.poi.cat.boat_rental': 'Qayıq icarəsi',
|
||||
'tours.poi.cat.bike_repair': 'Velosiped təmiri',
|
||||
'tours.planner.tourTypeHint.hike': 'Piyada, təyin etdiyin çətinlik daxilində',
|
||||
'tours.planner.tourTypeHint.bike': 'Velosipedlə, turinq, yol, qravel və ya MTB üçün marşrut',
|
||||
'tours.planner.tourTypeHint.kayak': 'Suda, kayak, kanoe və ya SUP ilə',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Turinq',
|
||||
'tours.bike.profile.road': 'Yol',
|
||||
'tours.bike.profile.gravel': 'Qravel',
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Velosiped yollarına və sakit küçələrə üstünlük verir, mümkün olduqda asfaltlı.',
|
||||
'tours.bike.profileHint.road': 'Asfaltda qalır; məlum asfaltsız hissələr göstərilir.',
|
||||
'tours.bike.profileHint.gravel': 'Çınqıl və təsərrüfat yollarından keçir, texniki tək cığırlardan yox.',
|
||||
'tours.bike.profileHint.mtb': 'Kobud yollardan olduğu kimi keçir; cığır çətinlikləri qeyd olunduğu yerdə göstərilir.',
|
||||
'tours.bike.timeEstimated': 'Sürüş vaxtı (təxmini)',
|
||||
'tours.bike.timeEstimatedShort': 'Sürüş vaxtı',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Sürüş vaxtı necə hesablanır',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Seçilmiş profil üçün marşrutlaşdırıcının vaxtı, sürəti və yoxuşlar nəzərə alınmaqla. Fasilələrsiz planlaşdırma dəyəri.',
|
||||
'tours.bike.ways.title': 'Səth və yollar',
|
||||
'tours.bike.ways.surface': 'Səth',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Kobud örtük',
|
||||
'tours.bike.ways.surface.compacted': 'Sıxlaşdırılmış',
|
||||
'tours.bike.ways.surface.gravel': 'Çınqıl',
|
||||
'tours.bike.ways.surface.ground': 'Təbii torpaq',
|
||||
'tours.bike.ways.surface.unknown': 'Məlumatda yoxdur',
|
||||
'tours.bike.ways.way': 'Yol növü',
|
||||
'tours.bike.ways.way.cyclePath': 'Velosiped yolu',
|
||||
'tours.bike.ways.way.cycleLane': 'Nəqliyyat yanında velosiped zolağı',
|
||||
'tours.bike.ways.way.quiet': 'Sakit küçə',
|
||||
'tours.bike.ways.way.road': 'Nəqliyyatlı yol',
|
||||
'tours.bike.ways.way.track': 'Təsərrüfat və ya meşə yolu',
|
||||
'tours.bike.ways.way.path': 'Cığır',
|
||||
'tours.bike.ways.way.ferry': 'Bərə',
|
||||
'tours.bike.ways.way.other': 'Digər',
|
||||
'tours.bike.ways.loading': 'Marşrut boyunca yollar oxunur…',
|
||||
'tours.bike.ways.offline': 'Səth və cığır çətinlikləri üçün bağlantı lazımdır.',
|
||||
'tours.bike.ways.failed': 'Marşrut boyunca yollar oxunmadı. Marşrutun özü dəyişmir.',
|
||||
'tours.bike.ways.source':
|
||||
'Marşrutlaşdırıcının oxuduğu kimi OpenStreetMap-dən. Məlumat heç nə demirsə, səth naməlumdur, yaxşı deyil.',
|
||||
'tours.bike.ways.showOnMap': 'Xəritədə göstər',
|
||||
'tours.bike.ways.unpavedBadge': '{share} asfaltsız',
|
||||
'tours.bike.ways.unpavedNotice': 'Marşrutun {length} hissəsinin asfaltsız olduğu məlumdur.',
|
||||
'tours.bike.trail.title': 'Cığır çətinliyi (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, xüsusi çətinlik yoxdur',
|
||||
'tours.bike.trail.s1': 'S1, kiçik maneələr',
|
||||
'tours.bike.trail.s2': 'S2, köklər, pillələr, boş torpaq',
|
||||
'tours.bike.trail.s3': 'S3, daş bloklar, dik, dar döngələr',
|
||||
'tours.bike.trail.s4': 'S4 və yuxarı, ekspert ərazisi',
|
||||
'tours.bike.trail.none': 'Məlumatda dərəcə yoxdur',
|
||||
'tours.bike.trail.unasked': 'Bu dəfə soruşmaq mümkün olmadı',
|
||||
'tours.bike.trail.uphillTitle': 'Yoxuş çətinliyi (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Yoxuş {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Dərəcələr OpenStreetMap redaktorlarının verdiyi kimidir. Dərəcəsiz yol asan deyil, naməlumdur. Cığırın sənə uyğun olub-olmadığını demir.',
|
||||
'tours.bike.trail.loading': 'Cığır çətinlikləri oxunur…',
|
||||
'tours.bike.trail.failed': 'Cığır çətinlikləri bu dəfə oxunmadı.',
|
||||
'tours.copy.action': 'Yeni tur kimi saxla',
|
||||
'tours.copy.hint': 'Bu turu günləri olmadan yeni, müstəqil tur kimi kopyalayır. Orijinal olduğu kimi qalır.',
|
||||
'tours.copy.name': '{name} (surət)',
|
||||
@@ -352,6 +419,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'GPX faylını endirmək üçün serverə bağlantı lazımdır.',
|
||||
'tours.gpxExport.noRoute': 'Bu turun ixrac ediləcək saxlanmış marşrutu yoxdur.',
|
||||
'tours.gpxExport.failed': 'GPX faylını yaratmaq olmadı.',
|
||||
'tours.gpxExport.share': 'GPX paylaş',
|
||||
'tours.gpxExport.shareShort': 'Paylaş',
|
||||
'tours.gpxExport.shareSaveFirst': 'GPX kimi paylaşmaq üçün əvvəlcə turu yadda saxla',
|
||||
'tours.gpxExport.shareReady': 'GPX faylı hazırdır. Paylaşma pəncərəsini açmaq üçün Paylaş-a bir daha toxun.',
|
||||
'tours.gpxExport.shareFailed': 'Paylaşma alınmadı. GPX faylını yenə də yükləmək olar.',
|
||||
'tours.revision.remoteTitle': 'Bu tur başqa yerdə saxlanıb',
|
||||
'tours.revision.remoteBody':
|
||||
'Siz redaktə edərkən kimsə və ya başqa cihazda siz özünüz onu saxladı. Dəyişiklikləriniz qərar verənə qədər qalır.',
|
||||
@@ -382,15 +454,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Marşrut seçimi nə edir',
|
||||
'tours.preferences.info':
|
||||
'Bu seçimlər marşrutlaşdırıcının hansı yolu seçəcəyinə təsir edir. Başqa yol yoxdursa, marşrut yenə oradan keçir. Yolun çətinliyi barədə heç nə demir; marşrutun nədən istifadə edə biləcəyini yuxarıdakı çətinlik həddi müəyyən edir.',
|
||||
'tours.preferences.avoid_climbs': 'Dik yoxuşlardan qaç',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Mövcud olduqda daha yumşaq yollar, daha uzun olsalar belə. Dolama yol olmayan yerdə dik qalır.',
|
||||
'tours.preferences.avoid_steps': 'Pilləkənlərdən qaç',
|
||||
'tours.preferences.avoid_stepsHint': 'Pilləkən yalnız dolama yol olmayan yerdə.',
|
||||
'tours.preferences.avoid_ferries': 'Bərələrdən qaç',
|
||||
'tours.preferences.avoid_ferriesHint': 'Dolama yol varsa, daha uzun olsa da quru ilə.',
|
||||
'tours.preferences.prefer_paths': 'Piyada yollarına üstünlük ver',
|
||||
'tours.preferences.prefer_pathsHint': 'Olan yerdə yollar və səkilər əvəzinə ayrıca cığırlar.',
|
||||
'tours.preferences.shortest': 'Daha sürətli yox, daha qısa',
|
||||
'tours.preferences.shortestHint':
|
||||
'Yol vaxta görə yox, uzunluğa görə seçilir; çox vaxt bir az qısa, həmişə daha sürətli deyil.',
|
||||
'tours.planner.tourType': 'Tur növü',
|
||||
'tours.paddle.boat': 'Qayıq',
|
||||
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
|
||||
|
||||
@@ -661,6 +661,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Mapa de trilhas (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Oferece o OpenHikingMap como mapa base no planejador de passeios. Gratuito para instalações não comerciais até 500.000 blocos por mês; uma instalação comercial ou maior precisa antes de um acordo com o openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Mapa de ciclismo (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Oferece o CyclOSM como mapa base para passeios de bicicleta no planejador. O OpenStreetMap France o fornece para uso moderado; uma instalação de uso intenso ou comercial o desativa ou faz antes um acordo com o operador',
|
||||
'admin.addons.noAddons': 'Nenhum complemento disponível',
|
||||
'admin.addons.integrationHint': 'Serviços de backend e integrações de API sem página dedicada',
|
||||
'admin.weather.title': 'Dados meteorológicos',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Trilhas',
|
||||
'map.baseLayer.hikingDown':
|
||||
'O OpenHikingMap não está respondendo agora. Mude para outro mapa; seu passeio continua como está.',
|
||||
'map.baseLayer.cycle': 'Bicicleta',
|
||||
'map.baseLayer.cycleDown': 'O CyclOSM não está respondendo agora. Mude para outro mapa; seu passeio continua igual.',
|
||||
'map.baseLayer.switchToSatellite': 'Mudar para vista de satélite',
|
||||
'map.baseLayer.switchToDefault': 'Mudar para vista de mapa',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -97,6 +97,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'A navegação abre o início da trilha; ela não orienta ao longo da rota.',
|
||||
'tours.planner.title': 'Planejar um passeio',
|
||||
'tours.planner.newTour': 'Planejar novo passeio',
|
||||
'tours.planner.addTour': 'Adicionar passeio',
|
||||
'tours.planner.addTourHint': 'Planeje um novo passeio, importe arquivos GPX ou traga um passeio de outra viagem',
|
||||
'tours.planner.neutralTitle': 'Planejar um passeio',
|
||||
'tours.planner.neutralBody': 'Comece um novo passeio e clique no mapa para marcar os pontos do percurso.',
|
||||
'tours.planner.newTitle': 'Novo passeio',
|
||||
@@ -113,6 +115,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Expandir perfil de elevação',
|
||||
'tours.planner.name': 'Nome do passeio',
|
||||
'tours.planner.namePlaceholder': 'Trilha sem título',
|
||||
'tours.planner.namePlaceholder.bike': 'Passeio de bicicleta sem nome',
|
||||
'tours.planner.namePlaceholder.kayak': 'Passeio de remo sem nome',
|
||||
'tours.planner.mapSearch': 'Buscar lugar ou endereço',
|
||||
'tours.planner.placePoints': 'Marcar pontos',
|
||||
'tours.planner.pointPlaced': 'Ponto marcado',
|
||||
@@ -176,6 +180,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Aguardando cálculo',
|
||||
'tours.planner.status.routing': 'Calculando rota a pé…',
|
||||
'tours.planner.status.routingFailed': 'O cálculo da rota falhou. A última rota válida continua sendo exibida.',
|
||||
'tours.planner.status.unreachable': 'O ponto {point} não pode ser alcançado',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Nenhuma trilha de caminhada chega ao ponto {point}: a mais próxima que o roteador conhece fica a {distance}. Mova o ponto para uma trilha ou desenhe o trecho até ele à mão.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Nenhuma via para bicicletas chega ao ponto {point}: a mais próxima que o roteador conhece fica a {distance}. Mova o ponto para uma via ou desenhe o trecho até ele à mão.',
|
||||
'tours.planner.unreachable.show': 'Mostrar o ponto {point}',
|
||||
'tours.planner.status.elevation': 'Carregando elevação…',
|
||||
'tours.planner.status.elevationFailed': 'A elevação não está disponível. A rota calculada continua sendo exibida.',
|
||||
'tours.planner.status.saving': 'Salvando…',
|
||||
@@ -345,6 +355,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Atrações',
|
||||
'tours.poi.cat.slipway': 'Pontos de entrada na água',
|
||||
'tours.poi.cat.boat_rental': 'Aluguel de barcos',
|
||||
'tours.poi.cat.bike_repair': 'Conserto de bicicleta',
|
||||
'tours.planner.tourTypeHint.hike': 'A pé, dentro de uma dificuldade que você define',
|
||||
'tours.planner.tourTypeHint.bike': 'De bicicleta, com rota para cicloturismo, estrada, gravel ou MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Na água, de caiaque, canoa ou SUP',
|
||||
'tours.bike.profile': 'Perfil',
|
||||
'tours.bike.profile.touring': 'Cicloturismo',
|
||||
'tours.bike.profile.road': 'Estrada',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Prefere ciclovias e ruas tranquilas, pavimentadas quando possível.',
|
||||
'tours.bike.profileHint.road': 'Fica no asfalto; trechos sem pavimento conhecidos são mostrados.',
|
||||
'tours.bike.profileHint.gravel': 'Usa cascalho e estradas rurais, não trilhas técnicas.',
|
||||
'tours.bike.profileHint.mtb': 'Usa trilhas brutas como vierem; graus de trilha aparecem onde mapeados.',
|
||||
'tours.bike.timeEstimated': 'Tempo pedalando (estimado)',
|
||||
'tours.bike.timeEstimatedShort': 'Tempo pedalando',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Como o tempo pedalando é calculado',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'O tempo do roteador para o perfil escolhido, com sua velocidade e as subidas. Um valor de planejamento, sem pausas.',
|
||||
'tours.bike.ways.title': 'Piso e vias',
|
||||
'tours.bike.ways.surface': 'Piso',
|
||||
'tours.bike.ways.surface.paved': 'Asfalto',
|
||||
'tours.bike.ways.surface.rough': 'Pavimento irregular',
|
||||
'tours.bike.ways.surface.compacted': 'Compactado',
|
||||
'tours.bike.ways.surface.gravel': 'Cascalho',
|
||||
'tours.bike.ways.surface.ground': 'Terra',
|
||||
'tours.bike.ways.surface.unknown': 'Fora dos dados',
|
||||
'tours.bike.ways.way': 'Tipo de via',
|
||||
'tours.bike.ways.way.cyclePath': 'Ciclovia',
|
||||
'tours.bike.ways.way.cycleLane': 'Ciclofaixa ao lado do trânsito',
|
||||
'tours.bike.ways.way.quiet': 'Rua tranquila',
|
||||
'tours.bike.ways.way.road': 'Estrada com trânsito',
|
||||
'tours.bike.ways.way.track': 'Estrada rural ou florestal',
|
||||
'tours.bike.ways.way.path': 'Trilha',
|
||||
'tours.bike.ways.way.ferry': 'Balsa',
|
||||
'tours.bike.ways.way.other': 'Outros',
|
||||
'tours.bike.ways.loading': 'Lendo as vias ao longo da rota…',
|
||||
'tours.bike.ways.offline': 'Pisos e graus de trilha precisam de conexão.',
|
||||
'tours.bike.ways.failed': 'Não foi possível ler as vias da rota. A rota em si não muda.',
|
||||
'tours.bike.ways.source':
|
||||
'Do OpenStreetMap, como o roteador lê. Onde os dados não dizem nada, o piso é desconhecido, não bom.',
|
||||
'tours.bike.ways.showOnMap': 'Mostrar no mapa',
|
||||
'tours.bike.ways.unpavedBadge': '{share} sem pavimento',
|
||||
'tours.bike.ways.unpavedNotice': '{length} da rota sabidamente sem pavimento.',
|
||||
'tours.bike.trail.title': 'Grau da trilha (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, sem dificuldade especial',
|
||||
'tours.bike.trail.s1': 'S1, pequenos obstáculos',
|
||||
'tours.bike.trail.s2': 'S2, raízes, degraus, chão solto',
|
||||
'tours.bike.trail.s3': 'S3, blocos, íngreme, curvas fechadas',
|
||||
'tours.bike.trail.s4': 'S4 ou mais, terreno para especialistas',
|
||||
'tours.bike.trail.none': 'Sem grau nos dados',
|
||||
'tours.bike.trail.unasked': 'Não foi possível consultar agora',
|
||||
'tours.bike.trail.uphillTitle': 'Grau de subida (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Subida {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Graus como os mapeadores do OpenStreetMap os deram. Uma via sem grau é desconhecida, não fácil. Eles não dizem se a trilha combina com você.',
|
||||
'tours.bike.trail.loading': 'Lendo os graus das trilhas…',
|
||||
'tours.bike.trail.failed': 'Não foi possível ler os graus das trilhas agora.',
|
||||
'tours.copy.action': 'Salvar como novo passeio',
|
||||
'tours.copy.hint':
|
||||
'Copia este passeio como um novo passeio independente, sem os seus dias. O original fica como está.',
|
||||
@@ -358,6 +425,12 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Baixar um arquivo GPX exige conexão com o servidor.',
|
||||
'tours.gpxExport.noRoute': 'Este passeio não tem uma rota salva para exportar.',
|
||||
'tours.gpxExport.failed': 'Não foi possível criar o arquivo GPX.',
|
||||
'tours.gpxExport.share': 'Compartilhar GPX',
|
||||
'tours.gpxExport.shareShort': 'Compartilhar',
|
||||
'tours.gpxExport.shareSaveFirst': 'Salve o passeio primeiro para compartilhá-lo como GPX',
|
||||
'tours.gpxExport.shareReady':
|
||||
'O arquivo GPX está pronto. Toque em Compartilhar mais uma vez para abrir o menu de compartilhamento.',
|
||||
'tours.gpxExport.shareFailed': 'Não foi possível compartilhar. O arquivo GPX ainda pode ser baixado.',
|
||||
'tours.revision.remoteTitle': 'Este passeio foi salvo em outro lugar',
|
||||
'tours.revision.remoteBody':
|
||||
'Alguém, ou você em outro aparelho, salvou enquanto você editava. Suas alterações ficam até você decidir.',
|
||||
@@ -388,15 +461,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'O que a escolha da rota faz',
|
||||
'tours.preferences.info':
|
||||
'Estas escolhas pesam o caminho que o roteador escolhe. Onde não há outro caminho, a rota passa por ali mesmo assim. Não dizem nada sobre a dificuldade de um caminho; o limite de dificuldade acima define o que a rota pode usar.',
|
||||
'tours.preferences.avoid_climbs': 'Evitar subidas íngremes',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Caminhos mais suaves onde houver, mesmo que mais longos. Onde não há como contornar, continua íngreme.',
|
||||
'tours.preferences.avoid_steps': 'Evitar escadas',
|
||||
'tours.preferences.avoid_stepsHint': 'Escadas só onde não há caminho alternativo.',
|
||||
'tours.preferences.avoid_ferries': 'Evitar balsas',
|
||||
'tours.preferences.avoid_ferriesHint': 'Por terra onde houver caminho alternativo, mesmo mais longo.',
|
||||
'tours.preferences.prefer_paths': 'Preferir trilhas a pé',
|
||||
'tours.preferences.prefer_pathsHint': 'Caminhos próprios em vez de estradas e calçadas, onde houver.',
|
||||
'tours.preferences.shortest': 'Mais curto em vez de mais rápido',
|
||||
'tours.preferences.shortestHint':
|
||||
'O caminho escolhido pela distância, não pelo tempo; em geral um pouco mais curto, nem sempre mais rápido.',
|
||||
'tours.planner.tourType': 'Tipo de passeio',
|
||||
'tours.paddle.boat': 'Embarcação',
|
||||
'tours.paddle.boat.kayak': 'Caiaque',
|
||||
|
||||
@@ -270,6 +270,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': "Mapa d'excursionisme (OpenHikingMap)",
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Ofereix OpenHikingMap com a mapa base al planificador de rutes. Gratuït per a instal·lacions no comercials fins a 500.000 tessel·les al mes; una instal·lació comercial o més gran necessita abans un acord amb openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Mapa ciclista (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
"Ofereix CyclOSM com a mapa base per a rutes en bici al planificador. OpenStreetMap France el serveix per a un ús moderat; una instal·lació d'ús intens o comercial el desactiva o primer arriba a un acord amb l'operador",
|
||||
'admin.addons.noAddons': 'No hi ha complements disponibles',
|
||||
'admin.weather.title': 'Dades meteorològiques',
|
||||
'admin.weather.badge': 'Des del 24 de març de 2026',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Excursionisme',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap no respon ara mateix. Canvia a un altre mapa; la teva ruta es queda tal com és.',
|
||||
'map.baseLayer.cycle': 'Bicicleta',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM no respon ara mateix. Canvia a un altre mapa; la teva ruta es queda igual.',
|
||||
'map.baseLayer.switchToSatellite': 'Canvia a vista de satèl·lit',
|
||||
'map.baseLayer.switchToDefault': 'Canvia a vista de mapa',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -97,6 +97,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': "La navegació obre el punt d'inici del sender; no guia al llarg de la ruta.",
|
||||
'tours.planner.title': 'Planifica una excursió',
|
||||
'tours.planner.newTour': 'Planifica una nova excursió',
|
||||
'tours.planner.addTour': 'Afegeix una ruta',
|
||||
'tours.planner.addTourHint': "Planifica una ruta nova, importa fitxers GPX o porta una ruta d'un altre viatge",
|
||||
'tours.planner.neutralTitle': 'Planifica una excursió',
|
||||
'tours.planner.neutralBody': 'Comença una ruta nova i fes clic al mapa per marcar-ne els punts.',
|
||||
'tours.planner.newTitle': 'Nova excursió',
|
||||
@@ -113,6 +115,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': "Desplega el perfil d'altitud",
|
||||
'tours.planner.name': "Nom de l'excursió",
|
||||
'tours.planner.namePlaceholder': 'Ruta de senderisme sense títol',
|
||||
'tours.planner.namePlaceholder.bike': 'Ruta en bici sense nom',
|
||||
'tours.planner.namePlaceholder.kayak': 'Sortida en caiac sense nom',
|
||||
'tours.planner.mapSearch': 'Cerca un lloc o una adreça',
|
||||
'tours.planner.placePoints': 'Posar punts',
|
||||
'tours.planner.pointPlaced': 'Punt posat',
|
||||
@@ -176,6 +180,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Pendent de càlcul',
|
||||
'tours.planner.status.routing': 'Calculant ruta a peu…',
|
||||
'tours.planner.status.routingFailed': "El càlcul de la ruta ha fallat. Es continua mostrant l'última ruta vàlida.",
|
||||
'tours.planner.status.unreachable': 'No es pot arribar al punt {point}',
|
||||
'tours.planner.unreachable.hike':
|
||||
"Cap camí de senderisme arriba al punt {point}: el més proper que coneix l'enrutador és a {distance}. Mou el punt a un camí o dibuixa el tram fins allà a mà.",
|
||||
'tours.planner.unreachable.bike':
|
||||
"Cap camí per a bicicletes arriba al punt {point}: el més proper que coneix l'enrutador és a {distance}. Mou el punt a un camí o dibuixa el tram fins allà a mà.",
|
||||
'tours.planner.unreachable.show': 'Mostra el punt {point}',
|
||||
'tours.planner.status.elevation': 'Carregant altitud…',
|
||||
'tours.planner.status.elevationFailed': "L'altitud no està disponible. Es continua mostrant la ruta calculada.",
|
||||
'tours.planner.status.saving': 'Desant…',
|
||||
@@ -346,6 +356,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': "Llocs d'interès",
|
||||
'tours.poi.cat.slipway': "Punts d'embarcament",
|
||||
'tours.poi.cat.boat_rental': 'Lloguer de barques',
|
||||
'tours.poi.cat.bike_repair': 'Reparació de bicicletes',
|
||||
'tours.planner.tourTypeHint.hike': "A peu, dins d'una dificultat que tu tries",
|
||||
'tours.planner.tourTypeHint.bike': 'En bicicleta, amb ruta per a cicloturisme, carretera, gravel o BTT',
|
||||
'tours.planner.tourTypeHint.kayak': "A l'aigua, amb caiac, canoa o SUP",
|
||||
'tours.bike.profile': 'Perfil',
|
||||
'tours.bike.profile.touring': 'Cicloturisme',
|
||||
'tours.bike.profile.road': 'Carretera',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'BTT',
|
||||
'tours.bike.profileHint.touring': 'Prefereix carrils bici i carrers tranquils, pavimentats quan es pot.',
|
||||
'tours.bike.profileHint.road': "Es manté a l'asfalt; es mostren els trams sense pavimentar coneguts.",
|
||||
'tours.bike.profileHint.gravel': 'Pren grava i camins agrícoles, no corriols tècnics.',
|
||||
'tours.bike.profileHint.mtb': 'Pren camins durs tal com vénen; es mostren els graus on estan cartografiats.',
|
||||
'tours.bike.timeEstimated': 'Temps en bici (estimat)',
|
||||
'tours.bike.timeEstimatedShort': 'Temps en bici',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Com es calcula el temps en bici',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
"El temps de l'enrutador per al perfil triat, amb la seva velocitat i les pujades. Un valor de planificació, sense pauses.",
|
||||
'tours.bike.ways.title': 'Ferm i camins',
|
||||
'tours.bike.ways.surface': 'Ferm',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Paviment irregular',
|
||||
'tours.bike.ways.surface.compacted': 'Compactat',
|
||||
'tours.bike.ways.surface.gravel': 'Grava',
|
||||
'tours.bike.ways.surface.ground': 'Terra natural',
|
||||
'tours.bike.ways.surface.unknown': 'No consta a les dades',
|
||||
'tours.bike.ways.way': 'Tipus de camí',
|
||||
'tours.bike.ways.way.cyclePath': 'Carril bici',
|
||||
'tours.bike.ways.way.cycleLane': 'Carril bici al costat del trànsit',
|
||||
'tours.bike.ways.way.quiet': 'Carrer tranquil',
|
||||
'tours.bike.ways.way.road': 'Carretera amb trànsit',
|
||||
'tours.bike.ways.way.track': 'Camí agrícola o forestal',
|
||||
'tours.bike.ways.way.path': 'Sender',
|
||||
'tours.bike.ways.way.ferry': 'Transbordador',
|
||||
'tours.bike.ways.way.other': 'Altres',
|
||||
'tours.bike.ways.loading': "S'estan llegint els camins de la ruta…",
|
||||
'tours.bike.ways.offline': 'El ferm i els graus dels corriols necessiten connexió.',
|
||||
'tours.bike.ways.failed': "No s'han pogut llegir els camins de la ruta. La ruta no canvia.",
|
||||
'tours.bike.ways.source':
|
||||
"D'OpenStreetMap, tal com ho llegeix l'enrutador. On les dades no diuen res, el ferm és desconegut, no bo.",
|
||||
'tours.bike.ways.showOnMap': 'Mostra al mapa',
|
||||
'tours.bike.ways.unpavedBadge': '{share} sense pavimentar',
|
||||
'tours.bike.ways.unpavedNotice': 'Se sap que {length} de la ruta no estan pavimentats.',
|
||||
'tours.bike.trail.title': 'Grau del corriol (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, sense dificultat especial',
|
||||
'tours.bike.trail.s1': 'S1, obstacles petits',
|
||||
'tours.bike.trail.s2': 'S2, arrels, graons, terra solta',
|
||||
'tours.bike.trail.s3': 'S3, blocs, pendent fort, revolts tancats',
|
||||
'tours.bike.trail.s4': "S4 o més, terreny d'experts",
|
||||
'tours.bike.trail.none': 'Sense grau a les dades',
|
||||
'tours.bike.trail.unasked': "No s'ha pogut consultar aquesta vegada",
|
||||
'tours.bike.trail.uphillTitle': 'Grau de pujada (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Pujada {step}',
|
||||
'tours.bike.trail.note':
|
||||
"Graus tal com els han posat els mapadors d'OpenStreetMap. Un camí sense grau és desconegut, no fàcil. No diuen res de si un corriol et convé.",
|
||||
'tours.bike.trail.loading': "S'estan llegint els graus dels corriols…",
|
||||
'tours.bike.trail.failed': "Aquesta vegada no s'han pogut llegir els graus dels corriols.",
|
||||
'tours.copy.action': 'Desa com a excursió nova',
|
||||
'tours.copy.hint':
|
||||
'Copia aquesta excursió com una excursió nova i independent, sense els seus dies. L’original es queda com està.',
|
||||
@@ -359,6 +426,12 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Per baixar un fitxer GPX cal connexió amb el servidor.',
|
||||
'tours.gpxExport.noRoute': 'Aquesta excursió no té cap ruta desada per exportar.',
|
||||
'tours.gpxExport.failed': 'No s’ha pogut crear el fitxer GPX.',
|
||||
'tours.gpxExport.share': 'Comparteix el GPX',
|
||||
'tours.gpxExport.shareShort': 'Comparteix',
|
||||
'tours.gpxExport.shareSaveFirst': 'Desa primer la ruta per compartir-la com a GPX',
|
||||
'tours.gpxExport.shareReady':
|
||||
'El fitxer GPX és a punt. Toca Comparteix una altra vegada per obrir el diàleg de compartir.',
|
||||
'tours.gpxExport.shareFailed': "No s'ha pogut compartir. El fitxer GPX encara es pot baixar.",
|
||||
'tours.revision.remoteTitle': 'Aquesta excursió s’ha desat en un altre lloc',
|
||||
'tours.revision.remoteBody':
|
||||
'Algú, o tu en un altre dispositiu, l’ha desada mentre l’editaves. Els teus canvis es conserven fins que decideixis.',
|
||||
@@ -389,15 +462,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Què fa l’elecció de ruta',
|
||||
'tours.preferences.info':
|
||||
'Aquestes opcions ponderen el camí que tria el càlcul de ruta. On no hi ha cap altre camí, la ruta hi passa igualment. No diuen res de la dificultat d’un camí; el límit de dificultat de dalt fixa què pot fer servir la ruta.',
|
||||
'tours.preferences.avoid_climbs': 'Evitar pujades fortes',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
"Camins més suaus on n'hi hagi, encara que siguin més llargs. On no hi ha volta possible, continua sent costerut.",
|
||||
'tours.preferences.avoid_steps': 'Evita escales',
|
||||
'tours.preferences.avoid_stepsHint': 'Escales només on no hi ha cap volta possible.',
|
||||
'tours.preferences.avoid_ferries': 'Evita transbordadors',
|
||||
'tours.preferences.avoid_ferriesHint': 'Per terra on hi hagi una volta, encara que sigui més llarga.',
|
||||
'tours.preferences.prefer_paths': 'Prefereix camins de vianants',
|
||||
'tours.preferences.prefer_pathsHint': 'Camins propis en lloc de carreteres i voreres, on n’hi hagi.',
|
||||
'tours.preferences.shortest': 'Més curt que ràpid',
|
||||
'tours.preferences.shortestHint':
|
||||
'El camí triat per la llargada, no pel temps; sovint una mica més curt, no sempre més ràpid.',
|
||||
'tours.planner.tourType': 'Tipus de ruta',
|
||||
'tours.paddle.boat': 'Embarcació',
|
||||
'tours.paddle.boat.kayak': 'Caiac',
|
||||
|
||||
@@ -664,6 +664,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Turistická mapa (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Nabízí OpenHikingMap jako podkladovou mapu v plánovači túr. Zdarma pro nekomerční instalace do 500 000 dlaždic měsíčně; komerční nebo větší instalace potřebuje nejdřív dohodu s openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Cyklomapa (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Nabízí CyclOSM jako podkladovou mapu pro cyklotúry v plánovači. OpenStreetMap France ji poskytuje pro přiměřené používání; silně využívaná nebo komerční instalace ji vypne, nebo se nejprve dohodne s provozovatelem',
|
||||
'admin.addons.noAddons': 'Žádné doplňky nejsou k dispozici',
|
||||
'admin.addons.catalog.mcp.name': 'MCP',
|
||||
'admin.addons.catalog.mcp.description': 'Model Context Protocol pro integraci AI asistentů',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Satelit',
|
||||
'map.baseLayer.hiking': 'Turistika',
|
||||
'map.baseLayer.hikingDown': 'OpenHikingMap teď neodpovídá. Přepni na jinou mapu, tvoje túra zůstane beze změny.',
|
||||
'map.baseLayer.cycle': 'Cyklo',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM teď neodpovídá. Přepni na jinou mapu, tvoje túra zůstává beze změny.',
|
||||
'map.baseLayer.switchToSatellite': 'Přepnout na satelitní zobrazení',
|
||||
'map.baseLayer.switchToDefault': 'Přepnout na mapové zobrazení',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -97,6 +97,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Navigace otevře výchozí bod túry; nevede po její trase.',
|
||||
'tours.planner.title': 'Naplánovat výlet',
|
||||
'tours.planner.newTour': 'Naplánovat nový výlet',
|
||||
'tours.planner.addTour': 'Přidat túru',
|
||||
'tours.planner.addTourHint': 'Naplánuj novou túru, importuj soubory GPX nebo převezmi túru z jiné cesty',
|
||||
'tours.planner.neutralTitle': 'Naplánovat výlet',
|
||||
'tours.planner.neutralBody': 'Začněte novou túru a kliknutím do mapy umístěte její body.',
|
||||
'tours.planner.newTitle': 'Nový výlet',
|
||||
@@ -112,6 +114,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Rozbalit výškový profil',
|
||||
'tours.planner.name': 'Název výletu',
|
||||
'tours.planner.namePlaceholder': 'Túra bez názvu',
|
||||
'tours.planner.namePlaceholder.bike': 'Cyklotúra bez názvu',
|
||||
'tours.planner.namePlaceholder.kayak': 'Vodácký výlet bez názvu',
|
||||
'tours.planner.mapSearch': 'Hledat místo nebo adresu',
|
||||
'tours.planner.placePoints': 'Přidávat body',
|
||||
'tours.planner.pointPlaced': 'Bod přidán',
|
||||
@@ -175,6 +179,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Čeká na výpočet',
|
||||
'tours.planner.status.routing': 'Výpočet pěší trasy…',
|
||||
'tours.planner.status.routingFailed': 'Výpočet trasy selhal. Stále se zobrazuje poslední platná trasa.',
|
||||
'tours.planner.status.unreachable': 'Bod {point} je nedosažitelný',
|
||||
'tours.planner.unreachable.hike':
|
||||
'K bodu {point} nevede žádná pěší cesta: nejbližší, kterou router zná, je {distance} daleko. Přesuň bod na cestu, nebo úsek k němu nakresli ručně.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'K bodu {point} nevede žádná cesta pro kola: nejbližší, kterou router zná, je {distance} daleko. Přesuň bod na cestu, nebo úsek k němu nakresli ručně.',
|
||||
'tours.planner.unreachable.show': 'Ukázat bod {point}',
|
||||
'tours.planner.status.elevation': 'Načítání výškových údajů…',
|
||||
'tours.planner.status.elevationFailed': 'Výškové údaje nejsou dostupné. Vypočtená trasa se stále zobrazuje.',
|
||||
'tours.planner.status.saving': 'Ukládání…',
|
||||
@@ -339,6 +349,62 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Pamětihodnosti',
|
||||
'tours.poi.cat.slipway': 'Nástupní místa',
|
||||
'tours.poi.cat.boat_rental': 'Půjčovna lodí',
|
||||
'tours.poi.cat.bike_repair': 'Oprava kol',
|
||||
'tours.planner.tourTypeHint.hike': 'Pěšky, do obtížnosti, kterou nastavíš',
|
||||
'tours.planner.tourTypeHint.bike': 'Na kole, trasa pro treking, silnici, gravel nebo MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Na vodě, na kajaku, kánoi nebo SUP',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Treking',
|
||||
'tours.bike.profile.road': 'Silnice',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Upřednostňuje cyklostezky a klidné silnice, kde to jde zpevněné.',
|
||||
'tours.bike.profileHint.road': 'Drží se asfaltu; známé nezpevněné úseky se zobrazí.',
|
||||
'tours.bike.profileHint.gravel': 'Jezdí po štěrku a polních cestách, ne po technických singletrailech.',
|
||||
'tours.bike.profileHint.mtb': 'Bere drsné cesty, jak přijdou; obtížnost traťí se ukáže, kde je zmapovaná.',
|
||||
'tours.bike.timeEstimated': 'Doba jízdy (odhad)',
|
||||
'tours.bike.timeEstimatedShort': 'Doba jízdy',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Jak se počítá doba jízdy',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Čas routeru pro zvolený profil, s jeho rychlostí a stoupáním. Plánovací hodnota bez přestávek.',
|
||||
'tours.bike.ways.title': 'Povrch a cesty',
|
||||
'tours.bike.ways.surface': 'Povrch',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Hrubá dlažba',
|
||||
'tours.bike.ways.surface.compacted': 'Zhutněný',
|
||||
'tours.bike.ways.surface.gravel': 'Štěrk',
|
||||
'tours.bike.ways.surface.ground': 'Přírodní povrch',
|
||||
'tours.bike.ways.surface.unknown': 'Není v datech',
|
||||
'tours.bike.ways.way': 'Druh cesty',
|
||||
'tours.bike.ways.way.cyclePath': 'Cyklostezka',
|
||||
'tours.bike.ways.way.cycleLane': 'Cyklopruh vedle provozu',
|
||||
'tours.bike.ways.way.quiet': 'Klidná ulice',
|
||||
'tours.bike.ways.way.road': 'Silnice s provozem',
|
||||
'tours.bike.ways.way.track': 'Polní nebo lesní cesta',
|
||||
'tours.bike.ways.way.path': 'Stezka',
|
||||
'tours.bike.ways.way.ferry': 'Trajekt',
|
||||
'tours.bike.ways.way.other': 'Ostatní',
|
||||
'tours.bike.ways.loading': 'Načítají se cesty podél trasy…',
|
||||
'tours.bike.ways.offline': 'Povrch a obtížnost tratí potřebují připojení.',
|
||||
'tours.bike.ways.failed': 'Cesty podél trasy se nepodařilo načíst. Samotná trasa zůstává beze změny.',
|
||||
'tours.bike.ways.source': 'Z OpenStreetMap, jak je čte router. Kde data nic neříkají, je povrch neznámý, ne dobrý.',
|
||||
'tours.bike.ways.showOnMap': 'Ukázat na mapě',
|
||||
'tours.bike.ways.unpavedBadge': '{share} nezpevněno',
|
||||
'tours.bike.ways.unpavedNotice': '{length} trasy je podle dat nezpevněno.',
|
||||
'tours.bike.trail.title': 'Obtížnost trati (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, bez zvláštních obtíží',
|
||||
'tours.bike.trail.s1': 'S1, malé překážky',
|
||||
'tours.bike.trail.s2': 'S2, kořeny, schody, sypký povrch',
|
||||
'tours.bike.trail.s3': 'S3, bloky, prudké, úzké zatáčky',
|
||||
'tours.bike.trail.s4': 'S4 a více, terén pro experty',
|
||||
'tours.bike.trail.none': 'Bez stupně v datech',
|
||||
'tours.bike.trail.unasked': 'Tentokrát nešlo zjistit',
|
||||
'tours.bike.trail.uphillTitle': 'Obtížnost do kopce (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Do kopce {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Stupně, jak je zadali mapovači OpenStreetMap. Cesta bez stupně je neznámá, ne snadná. Neříkají nic o tom, zda se trať hodí pro tebe.',
|
||||
'tours.bike.trail.loading': 'Načítá se obtížnost tratí…',
|
||||
'tours.bike.trail.failed': 'Obtížnost tratí se tentokrát nepodařilo načíst.',
|
||||
'tours.copy.action': 'Uložit jako novou túru',
|
||||
'tours.copy.hint': 'Zkopíruje tuto túru jako novou, samostatnou túru bez jejích dnů. Originál zůstane, jak je.',
|
||||
'tours.copy.name': '{name} (kopie)',
|
||||
@@ -351,6 +417,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Ke stažení souboru GPX je potřeba připojení k serveru.',
|
||||
'tours.gpxExport.noRoute': 'Tato túra nemá uloženou trasu k exportu.',
|
||||
'tours.gpxExport.failed': 'Soubor GPX se nepodařilo vytvořit.',
|
||||
'tours.gpxExport.share': 'Sdílet GPX',
|
||||
'tours.gpxExport.shareShort': 'Sdílet',
|
||||
'tours.gpxExport.shareSaveFirst': 'Nejdřív túru ulož, pak ji můžeš sdílet jako GPX',
|
||||
'tours.gpxExport.shareReady': 'Soubor GPX je připravený. Klepni ještě jednou na Sdílet a otevře se nabídka sdílení.',
|
||||
'tours.gpxExport.shareFailed': 'Sdílení se nepovedlo. Soubor GPX si můžeš dál stáhnout.',
|
||||
'tours.revision.remoteTitle': 'Tato túra byla uložena jinde',
|
||||
'tours.revision.remoteBody':
|
||||
'Někdo, nebo ty na jiném zařízení, ji uložil, zatímco ji upravuješ. Tvoje změny zůstanou, dokud se nerozhodneš.',
|
||||
@@ -381,14 +452,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Co volba trasy dělá',
|
||||
'tours.preferences.info':
|
||||
'Tato volba ovlivňuje, kterou cestu plánovač vybere. Kde jiná cesta není, vede trasa tudy i tak. O obtížnosti cesty neříká nic; co smí trasa použít, určuje limit obtížnosti výše.',
|
||||
'tours.preferences.avoid_climbs': 'Vyhnout se prudkým stoupáním',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Mírnější cesty, kde existují, i když jsou delší. Kde se nedá obejít, zůstává to strmé.',
|
||||
'tours.preferences.avoid_steps': 'Vyhnout se schodům',
|
||||
'tours.preferences.avoid_stepsHint': 'Schody jen tam, kde není cesta kolem.',
|
||||
'tours.preferences.avoid_ferries': 'Vyhnout se přívozům',
|
||||
'tours.preferences.avoid_ferriesHint': 'Po souši, kde vede cesta kolem, i když je delší.',
|
||||
'tours.preferences.prefer_paths': 'Upřednostnit pěšiny',
|
||||
'tours.preferences.prefer_pathsHint': 'Samostatné cesty místo silnic a jejich chodníků, kde jsou.',
|
||||
'tours.preferences.shortest': 'Kratší místo rychlejší',
|
||||
'tours.preferences.shortestHint': 'Cesta vybraná podle délky, ne podle času; často o něco kratší, ne vždy rychlejší.',
|
||||
'tours.planner.tourType': 'Typ túry',
|
||||
'tours.paddle.boat': 'Loď',
|
||||
'tours.paddle.boat.kayak': 'Kajak',
|
||||
|
||||
@@ -669,6 +669,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Wanderkarte (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Bietet OpenHikingMap als Grundkarte im Tourplaner an. Kostenlos für nichtkommerzielle Installationen bis 500.000 Kacheln im Monat; eine kommerzielle oder größere Installation braucht vorher eine Vereinbarung mit openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Fahrradkarte (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Bietet CyclOSM als Grundkarte für Radtouren im Tourplaner an. OpenStreetMap France stellt sie für moderate Nutzung bereit; eine stark genutzte oder kommerzielle Installation schaltet sie ab oder trifft vorher eine Vereinbarung mit dem Betreiber',
|
||||
'admin.addons.noAddons': 'Keine Addons verfügbar',
|
||||
'admin.weather.title': 'Wetterdaten',
|
||||
'admin.weather.badge': 'Seit 24. März 2026',
|
||||
|
||||
@@ -21,6 +21,9 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Wandern',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap antwortet gerade nicht. Wechsle auf eine andere Karte, deine Tour bleibt unverändert.',
|
||||
'map.baseLayer.cycle': 'Rad',
|
||||
'map.baseLayer.cycleDown':
|
||||
'CyclOSM antwortet gerade nicht. Wechsle auf eine andere Karte, deine Tour bleibt unverändert.',
|
||||
'map.baseLayer.switchToSatellite': 'Zur Satellitenansicht wechseln',
|
||||
'map.baseLayer.switchToDefault': 'Zur Kartenansicht wechseln',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -97,6 +97,9 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Die Navigation führt zum Startpunkt, nicht entlang der Tour.',
|
||||
'tours.planner.title': 'Tour planen',
|
||||
'tours.planner.newTour': 'Neue Tour planen',
|
||||
'tours.planner.addTour': 'Tour hinzufügen',
|
||||
'tours.planner.addTourHint':
|
||||
'Neue Tour planen, GPX-Dateien importieren oder eine Tour aus einer anderen Reise übernehmen',
|
||||
'tours.planner.neutralTitle': 'Tour planen',
|
||||
'tours.planner.neutralBody': 'Starte eine neue Tour und klicke auf die Karte, um die Wegpunkte zu setzen.',
|
||||
'tours.planner.newTitle': 'Neue Tour',
|
||||
@@ -113,6 +116,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Höhenprofil ausklappen',
|
||||
'tours.planner.name': 'Name der Tour',
|
||||
'tours.planner.namePlaceholder': 'Wanderung ohne Titel',
|
||||
'tours.planner.namePlaceholder.bike': 'Radtour ohne Namen',
|
||||
'tours.planner.namePlaceholder.kayak': 'Paddeltour ohne Namen',
|
||||
'tours.planner.mapSearch': 'Ort oder Adresse suchen',
|
||||
'tours.planner.placePoints': 'Punkte setzen',
|
||||
'tours.planner.pointPlaced': 'Punkt gesetzt',
|
||||
@@ -177,6 +182,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.routing': 'Wanderroute wird berechnet...',
|
||||
'tours.planner.status.routingFailed':
|
||||
'Route konnte nicht berechnet werden. Die letzte gültige Route bleibt sichtbar.',
|
||||
'tours.planner.status.unreachable': 'Punkt {point} ist nicht erreichbar',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Kein Wanderweg erreicht Punkt {point}: der nächste, den der Router kennt, liegt {distance} entfernt. Setz den Punkt auf einen Weg oder zeichne den Abschnitt dorthin von Hand.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Kein Weg für Räder erreicht Punkt {point}: der nächste, den der Router kennt, liegt {distance} entfernt. Setz den Punkt auf einen Weg oder zeichne den Abschnitt dorthin von Hand.',
|
||||
'tours.planner.unreachable.show': 'Punkt {point} zeigen',
|
||||
'tours.planner.status.elevation': 'Höhendaten werden geladen...',
|
||||
'tours.planner.status.elevationFailed': 'Höhendaten sind nicht verfügbar. Die berechnete Route bleibt sichtbar.',
|
||||
'tours.planner.status.saving': 'Tour wird gespeichert...',
|
||||
@@ -348,6 +359,64 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Sehenswertes',
|
||||
'tours.poi.cat.slipway': 'Einsetzstellen',
|
||||
'tours.poi.cat.boat_rental': 'Bootsverleih',
|
||||
'tours.poi.cat.bike_repair': 'Radwerkstatt',
|
||||
'tours.planner.tourTypeHint.hike': 'Zu Fuß, bis zu einer Schwierigkeit, die du festlegst',
|
||||
'tours.planner.tourTypeHint.bike': 'Mit dem Rad, geroutet für Touring, Rennrad, Gravel oder MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Auf dem Wasser, mit Kajak, Kanadier oder SUP',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Touring', // same-as-en
|
||||
'tours.bike.profile.road': 'Rennrad',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Bevorzugt Radwege und ruhige Straßen, wo es geht befestigt.',
|
||||
'tours.bike.profileHint.road': 'Bleibt auf Asphalt; bekannte unbefestigte Abschnitte werden angezeigt.',
|
||||
'tours.bike.profileHint.gravel': 'Nimmt Schotter und Wirtschaftswege, keine technischen Singletrails.',
|
||||
'tours.bike.profileHint.mtb': 'Nimmt raue Wege, wie sie kommen; Trail-Schwierigkeiten werden gezeigt, wo erfasst.',
|
||||
'tours.bike.timeEstimated': 'Fahrzeit (geschätzt)',
|
||||
'tours.bike.timeEstimatedShort': 'Fahrzeit',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Wie die Fahrzeit berechnet wird',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Die Zeit des Routers für das gewählte Profil, mit seinem Tempo und den Anstiegen. Ein Planungswert, ohne Pausen.',
|
||||
'tours.bike.ways.title': 'Belag und Wege',
|
||||
'tours.bike.ways.surface': 'Belag',
|
||||
'tours.bike.ways.surface.paved': 'Asphalt', // same-as-en
|
||||
'tours.bike.ways.surface.rough': 'Grobes Pflaster',
|
||||
'tours.bike.ways.surface.compacted': 'Verdichtet',
|
||||
'tours.bike.ways.surface.gravel': 'Schotter',
|
||||
'tours.bike.ways.surface.ground': 'Naturboden',
|
||||
'tours.bike.ways.surface.unknown': 'Nicht in den Daten',
|
||||
'tours.bike.ways.way': 'Wegart',
|
||||
'tours.bike.ways.way.cyclePath': 'Radweg',
|
||||
'tours.bike.ways.way.cycleLane': 'Radspur neben dem Verkehr',
|
||||
'tours.bike.ways.way.quiet': 'Ruhige Straße',
|
||||
'tours.bike.ways.way.road': 'Straße mit Verkehr',
|
||||
'tours.bike.ways.way.track': 'Wirtschaftsweg',
|
||||
'tours.bike.ways.way.path': 'Pfad',
|
||||
'tours.bike.ways.way.ferry': 'Fähre',
|
||||
'tours.bike.ways.way.other': 'Sonstige',
|
||||
'tours.bike.ways.loading': 'Wege entlang der Route werden gelesen…',
|
||||
'tours.bike.ways.offline': 'Belag und Trail-Schwierigkeit brauchen eine Verbindung.',
|
||||
'tours.bike.ways.failed':
|
||||
'Die Wege entlang der Route konnten nicht gelesen werden. Die Route selbst bleibt unverändert.',
|
||||
'tours.bike.ways.source':
|
||||
'Aus OpenStreetMap, wie der Router es liest. Wo die Daten nichts sagen, ist der Belag unbekannt, nicht gut.',
|
||||
'tours.bike.ways.showOnMap': 'Auf der Karte zeigen',
|
||||
'tours.bike.ways.unpavedBadge': '{share} unbefestigt',
|
||||
'tours.bike.ways.unpavedNotice': '{length} der Route sind bekannt unbefestigt.',
|
||||
'tours.bike.trail.title': 'Trail-Schwierigkeit (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, keine besonderen Schwierigkeiten',
|
||||
'tours.bike.trail.s1': 'S1, kleine Hindernisse',
|
||||
'tours.bike.trail.s2': 'S2, Wurzeln, Stufen, loser Boden',
|
||||
'tours.bike.trail.s3': 'S3, Blöcke, steil, enge Kehren',
|
||||
'tours.bike.trail.s4': 'S4 und mehr, Expertengelände',
|
||||
'tours.bike.trail.none': 'Keine Angabe in den Daten',
|
||||
'tours.bike.trail.unasked': 'Diesmal nicht abfragbar',
|
||||
'tours.bike.trail.uphillTitle': 'Bergauf-Schwierigkeit (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Bergauf {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Schwierigkeiten, wie OpenStreetMap-Mapper sie vergeben haben. Ein Weg ohne Angabe ist unbekannt, nicht leicht. Sie sagen nichts darüber, ob ein Trail zu dir passt.',
|
||||
'tours.bike.trail.loading': 'Trail-Schwierigkeiten werden gelesen…',
|
||||
'tours.bike.trail.failed': 'Die Trail-Schwierigkeiten konnten diesmal nicht gelesen werden.',
|
||||
'tours.copy.action': 'Als neue Tour speichern',
|
||||
'tours.copy.hint': 'Kopiert diese Tour als neue, eigenständige Tour ohne ihre Tage. Das Original bleibt, wie es ist.',
|
||||
'tours.copy.name': '{name} (Kopie)',
|
||||
@@ -360,6 +429,12 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Für den GPX-Download braucht es eine Verbindung zum Server.',
|
||||
'tours.gpxExport.noRoute': 'Diese Tour hat keine gespeicherte Route zum Exportieren.',
|
||||
'tours.gpxExport.failed': 'Die GPX-Datei konnte nicht erstellt werden.',
|
||||
'tours.gpxExport.share': 'GPX teilen',
|
||||
'tours.gpxExport.shareShort': 'Teilen',
|
||||
'tours.gpxExport.shareSaveFirst': 'Erst speichern, dann als GPX teilen',
|
||||
'tours.gpxExport.shareReady':
|
||||
'Die GPX-Datei ist bereit. Tippe noch einmal auf Teilen, um den Teilen-Dialog zu öffnen.',
|
||||
'tours.gpxExport.shareFailed': 'Teilen hat nicht geklappt. Die GPX-Datei lässt sich weiterhin herunterladen.',
|
||||
'tours.revision.remoteTitle': 'Diese Tour wurde woanders gespeichert',
|
||||
'tours.revision.remoteBody':
|
||||
'Jemand, oder du auf einem anderen Gerät, hat sie gespeichert, während du sie bearbeitest. Deine Änderungen bleiben, bis du entscheidest.',
|
||||
@@ -390,14 +465,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Was die Routenwahl bewirkt',
|
||||
'tours.preferences.info':
|
||||
'Diese Wahl gewichtet, welchen Weg der Router nimmt. Gibt es keinen anderen Weg, führt die Route trotzdem dort entlang. Über die Schwierigkeit eines Weges sagt sie nichts; was die Route nutzen darf, legt das Schwierigkeitslimit darüber fest.',
|
||||
'tours.preferences.avoid_climbs': 'Steigungen meiden',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Flachere Wege, wo es sie gibt, auch wenn sie länger sind. Wo kein Weg herumführt, bleibt es steil.',
|
||||
'tours.preferences.avoid_steps': 'Treppen meiden',
|
||||
'tours.preferences.avoid_stepsHint': 'Treppen nur, wo es keinen Weg außen herum gibt.',
|
||||
'tours.preferences.avoid_ferries': 'Fähren meiden',
|
||||
'tours.preferences.avoid_ferriesHint': 'Über Land, wo es einen Weg außen herum gibt, auch wenn er länger ist.',
|
||||
'tours.preferences.prefer_paths': 'Fußwege bevorzugen',
|
||||
'tours.preferences.prefer_pathsHint': 'Eigene Wege statt Straßen und ihrer Gehsteige, wo es sie gibt.',
|
||||
'tours.preferences.shortest': 'Kürzer statt schneller',
|
||||
'tours.preferences.shortestHint': 'Der Weg nach Länge statt nach Gehzeit; oft etwas kürzer, nicht immer schneller.',
|
||||
'tours.planner.tourType': 'Tourtyp',
|
||||
'tours.paddle.boat': 'Boot',
|
||||
'tours.paddle.boat.kayak': 'Kajak',
|
||||
|
||||
@@ -713,6 +713,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Hiking map (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Offers OpenHikingMap as a base map in the tour planner. Free for non-commercial installs up to 500,000 tiles a month; a commercial or larger install needs an agreement with openmaps.fr first',
|
||||
'admin.toursCycleMap.title': 'Cycling map (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Offers CyclOSM as a base map for bike tours in the tour planner. OpenStreetMap France serves it for moderate use; a heavy or commercial install switches it off or reaches an agreement with the operator first',
|
||||
'admin.addons.noAddons': 'No addons available',
|
||||
'admin.weather.title': 'Weather Data',
|
||||
'admin.weather.badge': 'Since March 24, 2026',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Hiking',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap is not answering right now. Switch to another map; your tour stays as it is.',
|
||||
'map.baseLayer.cycle': 'Cycling',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM is not answering right now. Switch to another map; your tour stays as it is.',
|
||||
'map.baseLayer.switchToSatellite': 'Switch to satellite view',
|
||||
'map.baseLayer.switchToDefault': 'Switch to map view',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -93,6 +93,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Navigation opens the trailhead; it does not navigate along the route.',
|
||||
'tours.planner.title': 'Plan a tour',
|
||||
'tours.planner.newTour': 'Plan new tour',
|
||||
'tours.planner.addTour': 'Add tour',
|
||||
'tours.planner.addTourHint': 'Plan a new tour, import GPX files or take a tour over from another trip',
|
||||
'tours.planner.neutralTitle': 'Plan a tour',
|
||||
'tours.planner.neutralBody': 'Start a new tour and click the map to place its waypoints.',
|
||||
'tours.planner.newTitle': 'New tour',
|
||||
@@ -109,6 +111,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Expand elevation profile',
|
||||
'tours.planner.name': 'Tour name',
|
||||
'tours.planner.namePlaceholder': 'Untitled hike',
|
||||
'tours.planner.namePlaceholder.bike': 'Untitled bike tour',
|
||||
'tours.planner.namePlaceholder.kayak': 'Untitled paddle tour',
|
||||
'tours.planner.mapSearch': 'Search place or address',
|
||||
'tours.planner.placePoints': 'Set points',
|
||||
'tours.planner.pointPlaced': 'Point set',
|
||||
@@ -171,6 +175,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Waiting to calculate',
|
||||
'tours.planner.status.routing': 'Calculating walking route…',
|
||||
'tours.planner.status.routingFailed': 'Route calculation failed. The last good route is still shown.',
|
||||
'tours.planner.status.unreachable': 'Point {point} cannot be reached',
|
||||
'tours.planner.unreachable.hike':
|
||||
'No path for hikes reaches point {point}: the nearest the router knows is {distance} away. Move the point onto a path, or draw the leg to it by hand.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'No way for bikes reaches point {point}: the nearest the router knows is {distance} away. Move the point onto a way, or draw the leg to it by hand.',
|
||||
'tours.planner.unreachable.show': 'Show point {point}',
|
||||
'tours.planner.status.elevation': 'Loading elevation…',
|
||||
'tours.planner.status.elevationFailed': 'Elevation is unavailable. The calculated route is still shown.',
|
||||
'tours.planner.status.saving': 'Saving…',
|
||||
@@ -339,6 +349,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Sights',
|
||||
'tours.poi.cat.slipway': 'Put-ins',
|
||||
'tours.poi.cat.boat_rental': 'Boat rental',
|
||||
'tours.poi.cat.bike_repair': 'Bike repair',
|
||||
'tours.planner.tourTypeHint.hike': 'On foot, within a difficulty you set',
|
||||
'tours.planner.tourTypeHint.bike': 'By bike, routed for touring, road, gravel or MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'On the water, by kayak, canoe or SUP',
|
||||
'tours.bike.profile': 'Profile',
|
||||
'tours.bike.profile.touring': 'Touring',
|
||||
'tours.bike.profile.road': 'Road',
|
||||
'tours.bike.profile.gravel': 'Gravel',
|
||||
'tours.bike.profile.mtb': 'MTB',
|
||||
'tours.bike.profileHint.touring': 'Prefers cycle paths and quiet roads, paved where it can.',
|
||||
'tours.bike.profileHint.road': 'Keeps to asphalt; known unpaved stretches are shown.',
|
||||
'tours.bike.profileHint.gravel': 'Takes gravel and farm tracks, not technical singletrails.',
|
||||
'tours.bike.profileHint.mtb': 'Takes rough tracks as they come; trail grades are shown where mapped.',
|
||||
'tours.bike.timeEstimated': 'Riding time (estimated)',
|
||||
'tours.bike.timeEstimatedShort': 'Riding time',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'How the riding time is worked out',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
"The router's time for the profile chosen, its speed and the climbs weighed in. A planning value, breaks not included.",
|
||||
'tours.bike.ways.title': 'Surface and ways',
|
||||
'tours.bike.ways.surface': 'Surface',
|
||||
'tours.bike.ways.surface.paved': 'Asphalt',
|
||||
'tours.bike.ways.surface.rough': 'Rough paving',
|
||||
'tours.bike.ways.surface.compacted': 'Compacted',
|
||||
'tours.bike.ways.surface.gravel': 'Gravel',
|
||||
'tours.bike.ways.surface.ground': 'Natural ground',
|
||||
'tours.bike.ways.surface.unknown': 'Not in the data',
|
||||
'tours.bike.ways.way': 'Kind of way',
|
||||
'tours.bike.ways.way.cyclePath': 'Cycle path',
|
||||
'tours.bike.ways.way.cycleLane': 'Cycle lane beside traffic',
|
||||
'tours.bike.ways.way.quiet': 'Quiet street',
|
||||
'tours.bike.ways.way.road': 'Road with traffic',
|
||||
'tours.bike.ways.way.track': 'Farm or forest track',
|
||||
'tours.bike.ways.way.path': 'Path',
|
||||
'tours.bike.ways.way.ferry': 'Ferry',
|
||||
'tours.bike.ways.way.other': 'Other',
|
||||
'tours.bike.ways.loading': 'Reading the ways along the route…',
|
||||
'tours.bike.ways.offline': 'Surfaces and trail grades need a connection.',
|
||||
'tours.bike.ways.failed': 'The ways along the route could not be read. The route itself is unaffected.',
|
||||
'tours.bike.ways.source':
|
||||
'From OpenStreetMap, as the router reads it. Where the data says nothing, the surface is unknown, not fine.',
|
||||
'tours.bike.ways.showOnMap': 'Show on the map',
|
||||
'tours.bike.ways.unpavedBadge': '{share} unpaved',
|
||||
'tours.bike.ways.unpavedNotice': '{length} of the route is known to be unpaved.',
|
||||
'tours.bike.trail.title': 'Trail grade (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, no particular difficulty',
|
||||
'tours.bike.trail.s1': 'S1, small obstacles',
|
||||
'tours.bike.trail.s2': 'S2, roots, steps, loose ground',
|
||||
'tours.bike.trail.s3': 'S3, blocks, steep, tight bends',
|
||||
'tours.bike.trail.s4': 'S4 and up, expert ground',
|
||||
'tours.bike.trail.none': 'No grade in the data',
|
||||
'tours.bike.trail.unasked': 'Could not be asked this time',
|
||||
'tours.bike.trail.uphillTitle': 'Uphill grade (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Uphill {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Grades as OpenStreetMap mappers gave them. A way without one is unknown, not easy. They say nothing about whether a trail suits you.',
|
||||
'tours.bike.trail.loading': 'Reading the trail grades…',
|
||||
'tours.bike.trail.failed': 'The trail grades could not be read this time.',
|
||||
'tours.copy.action': 'Save as new tour',
|
||||
'tours.copy.hint': 'Copies this tour as a new, independent tour without its days. The original stays as it is.',
|
||||
'tours.copy.name': '{name} (copy)',
|
||||
@@ -351,6 +418,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Downloading a GPX file needs a connection to the server.',
|
||||
'tours.gpxExport.noRoute': 'This tour has no saved route to export.',
|
||||
'tours.gpxExport.failed': 'The GPX file could not be created.',
|
||||
'tours.gpxExport.share': 'Share GPX',
|
||||
'tours.gpxExport.shareShort': 'Share',
|
||||
'tours.gpxExport.shareSaveFirst': 'Save the tour first to share it as GPX',
|
||||
'tours.gpxExport.shareReady': 'The GPX file is ready. Tap share once more to open the share sheet.',
|
||||
'tours.gpxExport.shareFailed': 'Sharing did not work. The GPX file can still be downloaded.',
|
||||
'tours.revision.remoteTitle': 'This tour was saved elsewhere',
|
||||
'tours.revision.remoteBody':
|
||||
'Someone, or you on another device, saved it while you were editing. Your changes are kept until you decide.',
|
||||
@@ -381,15 +453,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'What the route choice does',
|
||||
'tours.preferences.info':
|
||||
'These choices weigh the way the router picks. Where there is no other way, the route still goes there. They say nothing about how difficult a path is; the difficulty limit above sets what the route may use.',
|
||||
'tours.preferences.avoid_climbs': 'Avoid steep climbs',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Gentler ways where there are some, even when they are longer. Where there is no way round, it stays steep.',
|
||||
'tours.preferences.avoid_steps': 'Avoid steps',
|
||||
'tours.preferences.avoid_stepsHint': 'Steps and stairs only where there is no way round.',
|
||||
'tours.preferences.avoid_ferries': 'Avoid ferries',
|
||||
'tours.preferences.avoid_ferriesHint': 'Over land where there is a way round, even when it is longer.',
|
||||
'tours.preferences.prefer_paths': 'Prefer footpaths',
|
||||
'tours.preferences.prefer_pathsHint': 'Paths of their own over roads and their pavements, where there are some.',
|
||||
'tours.preferences.shortest': 'Shorter over faster',
|
||||
'tours.preferences.shortestHint':
|
||||
'The way chosen by its length, not by the time it takes; often a little shorter, not always quicker.',
|
||||
'tours.planner.tourType': 'Tour type',
|
||||
'tours.paddle.boat': 'Boat',
|
||||
'tours.paddle.boat.kayak': 'Kayak',
|
||||
|
||||
@@ -652,6 +652,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Mapa de senderismo (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Ofrece OpenHikingMap como mapa base en el planificador de rutas. Gratis para instalaciones no comerciales hasta 500.000 teselas al mes; una instalación comercial o mayor necesita antes un acuerdo con openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Mapa ciclista (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Ofrece CyclOSM como mapa base para rutas en bici en el planificador. OpenStreetMap France lo sirve para un uso moderado; una instalación de uso intenso o comercial lo desactiva o llega antes a un acuerdo con el operador',
|
||||
'admin.addons.noAddons': 'No hay complementos disponibles',
|
||||
'admin.weather.title': 'Datos meteorológicos',
|
||||
'admin.weather.badge': 'Desde el 24 de marzo de 2026',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Satélite',
|
||||
'map.baseLayer.hiking': 'Senderismo',
|
||||
'map.baseLayer.hikingDown': 'OpenHikingMap no responde ahora mismo. Cambia a otro mapa; tu ruta se queda como está.',
|
||||
'map.baseLayer.cycle': 'Bici',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM no responde ahora mismo. Cambia a otro mapa; tu ruta sigue igual.',
|
||||
'map.baseLayer.switchToSatellite': 'Cambiar a vista de satélite',
|
||||
'map.baseLayer.switchToDefault': 'Cambiar a vista de mapa',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -98,6 +98,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'La navegación abre el punto de inicio; no guía a lo largo de la ruta.',
|
||||
'tours.planner.title': 'Planificar una excursión',
|
||||
'tours.planner.newTour': 'Planificar nueva excursión',
|
||||
'tours.planner.addTour': 'Añadir ruta',
|
||||
'tours.planner.addTourHint': 'Planifica una ruta nueva, importa archivos GPX o trae una ruta de otro viaje',
|
||||
'tours.planner.neutralTitle': 'Planificar una excursión',
|
||||
'tours.planner.neutralBody': 'Empieza una ruta nueva y haz clic en el mapa para colocar sus puntos.',
|
||||
'tours.planner.newTitle': 'Nueva excursión',
|
||||
@@ -114,6 +116,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Expandir perfil de altitud',
|
||||
'tours.planner.name': 'Nombre de la excursión',
|
||||
'tours.planner.namePlaceholder': 'Ruta de senderismo sin título',
|
||||
'tours.planner.namePlaceholder.bike': 'Ruta en bici sin nombre',
|
||||
'tours.planner.namePlaceholder.kayak': 'Ruta en kayak sin nombre',
|
||||
'tours.planner.mapSearch': 'Buscar lugar o dirección',
|
||||
'tours.planner.placePoints': 'Colocar puntos',
|
||||
'tours.planner.pointPlaced': 'Punto colocado',
|
||||
@@ -177,6 +181,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Pendiente de cálculo',
|
||||
'tours.planner.status.routing': 'Calculando ruta a pie…',
|
||||
'tours.planner.status.routingFailed': 'No se ha podido calcular la ruta. Se sigue mostrando la última ruta válida.',
|
||||
'tours.planner.status.unreachable': 'No se puede llegar al punto {point}',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Ningún sendero llega al punto {point}: el más cercano que conoce el enrutador está a {distance}. Mueve el punto a un camino o dibuja el tramo hasta él a mano.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Ninguna vía para bicis llega al punto {point}: la más cercana que conoce el enrutador está a {distance}. Mueve el punto a una vía o dibuja el tramo hasta él a mano.',
|
||||
'tours.planner.unreachable.show': 'Mostrar el punto {point}',
|
||||
'tours.planner.status.elevation': 'Cargando altitud…',
|
||||
'tours.planner.status.elevationFailed': 'La altitud no está disponible. Se sigue mostrando la ruta calculada.',
|
||||
'tours.planner.status.saving': 'Guardando…',
|
||||
@@ -345,6 +355,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Lugares de interés',
|
||||
'tours.poi.cat.slipway': 'Embarcaderos',
|
||||
'tours.poi.cat.boat_rental': 'Alquiler de barcas',
|
||||
'tours.poi.cat.bike_repair': 'Reparación de bicis',
|
||||
'tours.planner.tourTypeHint.hike': 'A pie, dentro de una dificultad que tú eliges',
|
||||
'tours.planner.tourTypeHint.bike': 'En bici, con ruta para cicloturismo, carretera, gravel o MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'En el agua, en kayak, canoa o SUP',
|
||||
'tours.bike.profile': 'Perfil',
|
||||
'tours.bike.profile.touring': 'Cicloturismo',
|
||||
'tours.bike.profile.road': 'Carretera',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Prefiere carriles bici y calles tranquilas, pavimentadas cuando se puede.',
|
||||
'tours.bike.profileHint.road': 'Se mantiene en el asfalto; se muestran los tramos sin pavimentar conocidos.',
|
||||
'tours.bike.profileHint.gravel': 'Toma grava y caminos rurales, no senderos técnicos.',
|
||||
'tours.bike.profileHint.mtb': 'Toma caminos duros tal como vienen; se muestran los grados donde están mapeados.',
|
||||
'tours.bike.timeEstimated': 'Tiempo en bici (estimado)',
|
||||
'tours.bike.timeEstimatedShort': 'Tiempo en bici',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Cómo se calcula el tiempo en bici',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'El tiempo del enrutador para el perfil elegido, con su velocidad y las subidas. Un valor de planificación, sin pausas.',
|
||||
'tours.bike.ways.title': 'Firme y vías',
|
||||
'tours.bike.ways.surface': 'Firme',
|
||||
'tours.bike.ways.surface.paved': 'Asfalto',
|
||||
'tours.bike.ways.surface.rough': 'Pavimento irregular',
|
||||
'tours.bike.ways.surface.compacted': 'Compactado',
|
||||
'tours.bike.ways.surface.gravel': 'Grava',
|
||||
'tours.bike.ways.surface.ground': 'Tierra natural',
|
||||
'tours.bike.ways.surface.unknown': 'No consta en los datos',
|
||||
'tours.bike.ways.way': 'Tipo de vía',
|
||||
'tours.bike.ways.way.cyclePath': 'Carril bici',
|
||||
'tours.bike.ways.way.cycleLane': 'Carril bici junto al tráfico',
|
||||
'tours.bike.ways.way.quiet': 'Calle tranquila',
|
||||
'tours.bike.ways.way.road': 'Carretera con tráfico',
|
||||
'tours.bike.ways.way.track': 'Camino agrícola o forestal',
|
||||
'tours.bike.ways.way.path': 'Sendero',
|
||||
'tours.bike.ways.way.ferry': 'Ferri',
|
||||
'tours.bike.ways.way.other': 'Otros',
|
||||
'tours.bike.ways.loading': 'Leyendo las vías a lo largo de la ruta…',
|
||||
'tours.bike.ways.offline': 'El firme y los grados de los senderos necesitan conexión.',
|
||||
'tours.bike.ways.failed': 'No se pudieron leer las vías de la ruta. La ruta en sí no cambia.',
|
||||
'tours.bike.ways.source':
|
||||
'De OpenStreetMap, tal como lo lee el enrutador. Donde los datos no dicen nada, el firme es desconocido, no bueno.',
|
||||
'tours.bike.ways.showOnMap': 'Mostrar en el mapa',
|
||||
'tours.bike.ways.unpavedBadge': '{share} sin pavimentar',
|
||||
'tours.bike.ways.unpavedNotice': 'Se sabe que {length} de la ruta no están pavimentados.',
|
||||
'tours.bike.trail.title': 'Grado del sendero (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, sin dificultad especial',
|
||||
'tours.bike.trail.s1': 'S1, obstáculos pequeños',
|
||||
'tours.bike.trail.s2': 'S2, raíces, escalones, suelo suelto',
|
||||
'tours.bike.trail.s3': 'S3, bloques, pendiente fuerte, curvas cerradas',
|
||||
'tours.bike.trail.s4': 'S4 o más, terreno de expertos',
|
||||
'tours.bike.trail.none': 'Sin grado en los datos',
|
||||
'tours.bike.trail.unasked': 'No se pudo consultar esta vez',
|
||||
'tours.bike.trail.uphillTitle': 'Grado de subida (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Subida {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Grados tal como los dieron los mapeadores de OpenStreetMap. Una vía sin grado es desconocida, no fácil. No dicen nada sobre si un sendero te conviene.',
|
||||
'tours.bike.trail.loading': 'Leyendo los grados de los senderos…',
|
||||
'tours.bike.trail.failed': 'Esta vez no se pudieron leer los grados de los senderos.',
|
||||
'tours.copy.action': 'Guardar como nueva excursión',
|
||||
'tours.copy.hint':
|
||||
'Copia esta excursión como una nueva excursión independiente, sin sus días. El original queda como está.',
|
||||
@@ -358,6 +425,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Para descargar un archivo GPX hace falta conexión con el servidor.',
|
||||
'tours.gpxExport.noRoute': 'Esta excursión no tiene una ruta guardada que exportar.',
|
||||
'tours.gpxExport.failed': 'No se pudo crear el archivo GPX.',
|
||||
'tours.gpxExport.share': 'Compartir GPX',
|
||||
'tours.gpxExport.shareShort': 'Compartir',
|
||||
'tours.gpxExport.shareSaveFirst': 'Guarda primero la ruta para compartirla como GPX',
|
||||
'tours.gpxExport.shareReady': 'El archivo GPX está listo. Toca Compartir otra vez para abrir el menú de compartir.',
|
||||
'tours.gpxExport.shareFailed': 'No se pudo compartir. El archivo GPX se puede seguir descargando.',
|
||||
'tours.revision.remoteTitle': 'Esta excursión se guardó en otro lugar',
|
||||
'tours.revision.remoteBody':
|
||||
'Alguien, o tú en otro dispositivo, la guardó mientras la editabas. Tus cambios se mantienen hasta que decidas.',
|
||||
@@ -388,15 +460,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Qué hace la elección de ruta',
|
||||
'tours.preferences.info':
|
||||
'Estas opciones ponderan el camino que elige el enrutador. Donde no hay otro camino, la ruta pasa igualmente por ahí. No dicen nada sobre la dificultad de un camino; el límite de dificultad de arriba fija lo que la ruta puede usar.',
|
||||
'tours.preferences.avoid_climbs': 'Evitar subidas pronunciadas',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Caminos más suaves donde los haya, aunque sean más largos. Donde no hay forma de rodear, sigue siendo empinado.',
|
||||
'tours.preferences.avoid_steps': 'Evitar escaleras',
|
||||
'tours.preferences.avoid_stepsHint': 'Escaleras solo donde no hay forma de rodearlas.',
|
||||
'tours.preferences.avoid_ferries': 'Evitar ferris',
|
||||
'tours.preferences.avoid_ferriesHint': 'Por tierra donde haya una forma de rodear, aunque sea más larga.',
|
||||
'tours.preferences.prefer_paths': 'Preferir senderos peatonales',
|
||||
'tours.preferences.prefer_pathsHint': 'Caminos propios en lugar de carreteras y aceras, donde los haya.',
|
||||
'tours.preferences.shortest': 'Más corto antes que más rápido',
|
||||
'tours.preferences.shortestHint':
|
||||
'El camino elegido por su longitud, no por el tiempo; a menudo algo más corto, no siempre más rápido.',
|
||||
'tours.planner.tourType': 'Tipo de ruta',
|
||||
'tours.paddle.boat': 'Embarcación',
|
||||
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
|
||||
|
||||
@@ -733,6 +733,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Matkakaart (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Pakub OpenHikingMapi matkaplaneerija aluskaardina. Mitteäriliste paigalduste jaoks tasuta kuni 500 000 paani kuus; äriline või suurem paigaldus vajab enne kokkulepet openmaps.fr-iga',
|
||||
'admin.toursCycleMap.title': 'Rattakaart (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Pakub CyclOSM-i rattamatkade aluskaardina matkaplaneerijas. OpenStreetMap France pakub seda mõõdukaks kasutuseks; tugeva kasutusega või äriline paigaldus lülitab selle välja või lepib enne operaatoriga kokku',
|
||||
'admin.addons.noAddons': 'Lisamooduleid pole saadaval',
|
||||
'admin.weather.title': 'Ilmaandmed',
|
||||
'admin.weather.badge': 'Alates 24. märtsist 2026',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Satelliit',
|
||||
'map.baseLayer.hiking': 'Matkamine',
|
||||
'map.baseLayer.hikingDown': 'OpenHikingMap ei vasta praegu. Vaheta teisele kaardile, sinu matk jääb samaks.',
|
||||
'map.baseLayer.cycle': 'Ratas',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM ei vasta praegu. Vaheta teise kaardi vastu, sinu matk jääb samaks.',
|
||||
'map.baseLayer.switchToSatellite': 'Lülitu satelliidivaatele',
|
||||
'map.baseLayer.switchToDefault': 'Lülitu kaardivaatele',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -94,6 +94,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Navigeerimine avab raja alguspunkti, mitte juhiseid mööda marsruuti.',
|
||||
'tours.planner.title': 'Planeeri retk',
|
||||
'tours.planner.newTour': 'Planeeri uus retk',
|
||||
'tours.planner.addTour': 'Lisa matk',
|
||||
'tours.planner.addTourHint': 'Planeeri uus matk, impordi GPX-faile või võta matk üle teiselt reisilt',
|
||||
'tours.planner.neutralTitle': 'Planeeri retk',
|
||||
'tours.planner.neutralBody': 'Alusta uut matka ja klõpsa kaardil, et selle punktid paigutada.',
|
||||
'tours.planner.newTitle': 'Uus retk',
|
||||
@@ -110,6 +112,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Laienda kõrgusprofiil',
|
||||
'tours.planner.name': 'Retke nimi',
|
||||
'tours.planner.namePlaceholder': 'Nimetu matk',
|
||||
'tours.planner.namePlaceholder.bike': 'Nimetu rattamatk',
|
||||
'tours.planner.namePlaceholder.kayak': 'Nimetu aerutusmatk',
|
||||
'tours.planner.mapSearch': 'Otsi kohta või aadressi',
|
||||
'tours.planner.placePoints': 'Lisa punkte',
|
||||
'tours.planner.pointPlaced': 'Punkt lisatud',
|
||||
@@ -174,6 +178,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.routing': 'Jalgsimarsruudi arvutamine…',
|
||||
'tours.planner.status.routingFailed':
|
||||
'Marsruudi arvutamine ebaõnnestus. Viimane õnnestunud marsruut on endiselt nähtav.',
|
||||
'tours.planner.status.unreachable': 'Punkti {point} ei saa jõuda',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Ükski matkarada ei jõua punktini {point}: lähim, mida marsruutija teab, on {distance} kaugusel. Liiguta punkt rajale või joonista lõik sinna käsitsi.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Ükski rattatee ei jõua punktini {point}: lähim, mida marsruutija teab, on {distance} kaugusel. Liiguta punkt teele või joonista lõik sinna käsitsi.',
|
||||
'tours.planner.unreachable.show': 'Näita punkti {point}',
|
||||
'tours.planner.status.elevation': 'Kõrgusandmete laadimine…',
|
||||
'tours.planner.status.elevationFailed': 'Kõrgusandmed pole saadaval. Arvutatud marsruut on endiselt nähtav.',
|
||||
'tours.planner.status.saving': 'Salvestamine…',
|
||||
@@ -342,6 +352,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Vaatamisväärsused',
|
||||
'tours.poi.cat.slipway': 'Vettelaskmiskohad',
|
||||
'tours.poi.cat.boat_rental': 'Paadilaenutus',
|
||||
'tours.poi.cat.bike_repair': 'Rattaremont',
|
||||
'tours.planner.tourTypeHint.hike': 'Jalgsi, sinu määratud raskuse piires',
|
||||
'tours.planner.tourTypeHint.bike': 'Rattaga, marsruut matka-, maantee-, gravel- või maastikurattale',
|
||||
'tours.planner.tourTypeHint.kayak': 'Vee peal, süsta, kanuu või SUP-iga',
|
||||
'tours.bike.profile': 'Profiil',
|
||||
'tours.bike.profile.touring': 'Matk',
|
||||
'tours.bike.profile.road': 'Maantee',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Eelistab rattateid ja vaikseid tänavaid, võimalusel kõvakattega.',
|
||||
'tours.bike.profileHint.road': 'Hoiab asfaldile; teadaolevad kattega lõigud näidatakse.',
|
||||
'tours.bike.profileHint.gravel': 'Kasutab kruusa- ja põllumajandusteid, mitte tehnilisi rajalõike.',
|
||||
'tours.bike.profileHint.mtb': 'Võtab karmid rajad nii, nagu need tulevad; rajaraskus näidatakse, kus kaardistatud.',
|
||||
'tours.bike.timeEstimated': 'Sõiduaeg (hinnanguline)',
|
||||
'tours.bike.timeEstimatedShort': 'Sõiduaeg',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Kuidas sõiduaeg arvutatakse',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Marsruudi aeg valitud profiilile, selle kiiruse ja tõusudega. Planeerimisväärtus, pausideta.',
|
||||
'tours.bike.ways.title': 'Kate ja teed',
|
||||
'tours.bike.ways.surface': 'Kate',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Konarlik kate',
|
||||
'tours.bike.ways.surface.compacted': 'Tihendatud',
|
||||
'tours.bike.ways.surface.gravel': 'Kruus',
|
||||
'tours.bike.ways.surface.ground': 'Pinnas',
|
||||
'tours.bike.ways.surface.unknown': 'Andmetes puudub',
|
||||
'tours.bike.ways.way': 'Tee liik',
|
||||
'tours.bike.ways.way.cyclePath': 'Rattatee',
|
||||
'tours.bike.ways.way.cycleLane': 'Rattarada liikluse kõrval',
|
||||
'tours.bike.ways.way.quiet': 'Vaikne tänav',
|
||||
'tours.bike.ways.way.road': 'Liiklusega tee',
|
||||
'tours.bike.ways.way.track': 'Põllu- või metsatee',
|
||||
'tours.bike.ways.way.path': 'Rada',
|
||||
'tours.bike.ways.way.ferry': 'Praam',
|
||||
'tours.bike.ways.way.other': 'Muu',
|
||||
'tours.bike.ways.loading': 'Loen marsruudi teid…',
|
||||
'tours.bike.ways.offline': 'Kate ja rajaraskus vajavad ühendust.',
|
||||
'tours.bike.ways.failed': 'Marsruudi teid ei õnnestunud lugeda. Marsruut ise ei muutu.',
|
||||
'tours.bike.ways.source':
|
||||
'OpenStreetMapist, nagu marsruutija seda loeb. Kus andmed midagi ei ütle, on kate teadmata, mitte hea.',
|
||||
'tours.bike.ways.showOnMap': 'Näita kaardil',
|
||||
'tours.bike.ways.unpavedBadge': '{share} katteta',
|
||||
'tours.bike.ways.unpavedNotice': 'Marsruudist {length} on teadaolevalt katteta.',
|
||||
'tours.bike.trail.title': 'Raja raskus (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, erilisi raskusi pole',
|
||||
'tours.bike.trail.s1': 'S1, väikesed takistused',
|
||||
'tours.bike.trail.s2': 'S2, juured, astmed, lahtine pinnas',
|
||||
'tours.bike.trail.s3': 'S3, rahnud, järsk, kitsad pöörded',
|
||||
'tours.bike.trail.s4': 'S4 ja üle, ekspertide maastik',
|
||||
'tours.bike.trail.none': 'Andmetes raskust pole',
|
||||
'tours.bike.trail.unasked': 'Seekord ei õnnestunud küsida',
|
||||
'tours.bike.trail.uphillTitle': 'Tõusu raskus (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Tõus {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Raskused nii, nagu OpenStreetMapi kaardistajad need andsid. Rada ilma raskuseta on teadmata, mitte kerge. Need ei ütle midagi selle kohta, kas rada sulle sobib.',
|
||||
'tours.bike.trail.loading': 'Loen radade raskust…',
|
||||
'tours.bike.trail.failed': 'Radade raskust ei õnnestunud seekord lugeda.',
|
||||
'tours.copy.action': 'Salvesta uue matkana',
|
||||
'tours.copy.hint': 'Kopeerib selle matka uue, iseseisva matkana ilma päevadeta. Originaal jääb samaks.',
|
||||
'tours.copy.name': '{name} (koopia)',
|
||||
@@ -354,6 +421,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'GPX-faili allalaadimiseks on vaja ühendust serveriga.',
|
||||
'tours.gpxExport.noRoute': 'Sellel matkal pole eksportimiseks salvestatud marsruuti.',
|
||||
'tours.gpxExport.failed': 'GPX-faili ei õnnestunud luua.',
|
||||
'tours.gpxExport.share': 'Jaga GPX-i',
|
||||
'tours.gpxExport.shareShort': 'Jaga',
|
||||
'tours.gpxExport.shareSaveFirst': 'Salvesta matk enne, et seda GPX-ina jagada',
|
||||
'tours.gpxExport.shareReady': 'GPX-fail on valmis. Puuduta veel kord Jaga, et avada jagamisaken.',
|
||||
'tours.gpxExport.shareFailed': 'Jagamine ei õnnestunud. GPX-faili saab endiselt alla laadida.',
|
||||
'tours.revision.remoteTitle': 'Seda matka salvestati mujal',
|
||||
'tours.revision.remoteBody':
|
||||
'Keegi või sina teises seadmes salvestas selle sinu muutmise ajal. Sinu muudatused jäävad alles, kuni otsustad.',
|
||||
@@ -383,14 +455,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Mida marsruudi valik teeb',
|
||||
'tours.preferences.info':
|
||||
'Need valikud kaaluvad, millise tee marsruutija valib. Kus teist teed pole, läheb marsruut ikkagi sealt. Tee raskuse kohta need midagi ei ütle; mida marsruut kasutada tohib, määrab ülal olev raskuspiir.',
|
||||
'tours.preferences.avoid_climbs': 'Väldi järske tõuse',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Laugemad teed, kus neid on, isegi kui need on pikemad. Kus ümbersõitu pole, jääb järsuks.',
|
||||
'tours.preferences.avoid_steps': 'Väldi treppe',
|
||||
'tours.preferences.avoid_stepsHint': 'Trepid ainult seal, kus ringi minna ei saa.',
|
||||
'tours.preferences.avoid_ferries': 'Väldi praame',
|
||||
'tours.preferences.avoid_ferriesHint': 'Mööda maad, kus saab ringi minna, isegi kui see on pikem.',
|
||||
'tours.preferences.prefer_paths': 'Eelista jalgradu',
|
||||
'tours.preferences.prefer_pathsHint': 'Omaette rajad teede ja kõnniteede asemel, kus neid on.',
|
||||
'tours.preferences.shortest': 'Lühem kiirema asemel',
|
||||
'tours.preferences.shortestHint': 'Tee valitakse pikkuse, mitte aja järgi; sageli veidi lühem, mitte alati kiirem.',
|
||||
'tours.planner.tourType': 'Matka tüüp',
|
||||
'tours.paddle.boat': 'Paat',
|
||||
'tours.paddle.boat.kayak': 'Süst',
|
||||
|
||||
@@ -674,6 +674,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Carte de randonnée (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
"Propose OpenHikingMap comme fond de carte dans le planificateur d'itinéraires. Gratuit pour les installations non commerciales jusqu'à 500 000 tuiles par mois ; une installation commerciale ou plus importante nécessite d'abord un accord avec openmaps.fr",
|
||||
'admin.toursCycleMap.title': 'Carte vélo (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
"Propose CyclOSM comme fond de carte pour les sorties vélo dans le planificateur. OpenStreetMap France le sert pour un usage modéré ; une installation à fort trafic ou commerciale le désactive ou conclut d'abord un accord avec l'opérateur",
|
||||
'admin.addons.noAddons': 'Aucune extension disponible',
|
||||
'admin.weather.title': 'Données météo',
|
||||
'admin.weather.badge': 'Depuis le 24 mars 2026',
|
||||
|
||||
@@ -21,6 +21,9 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Randonnée',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap ne répond pas pour le moment. Passe à une autre carte ; ton itinéraire reste inchangé.',
|
||||
'map.baseLayer.cycle': 'Vélo',
|
||||
'map.baseLayer.cycleDown':
|
||||
'CyclOSM ne répond pas pour le moment. Passe à une autre carte ; ta sortie reste telle quelle.',
|
||||
'map.baseLayer.switchToSatellite': 'Passer à la vue satellite',
|
||||
'map.baseLayer.switchToDefault': 'Passer à la vue carte',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -99,6 +99,9 @@ const tours: TranslationStrings = {
|
||||
"La navigation ouvre le point de départ du sentier ; elle ne vous guide pas le long de l'itinéraire.",
|
||||
'tours.planner.title': 'Planifier une sortie',
|
||||
'tours.planner.newTour': 'Planifier une nouvelle sortie',
|
||||
'tours.planner.addTour': 'Ajouter une sortie',
|
||||
'tours.planner.addTourHint':
|
||||
"Planifier une nouvelle sortie, importer des fichiers GPX ou reprendre une sortie d'un autre voyage",
|
||||
'tours.planner.neutralTitle': 'Planifier une sortie',
|
||||
'tours.planner.neutralBody': 'Commencez une nouvelle randonnée et cliquez sur la carte pour placer ses points.',
|
||||
'tours.planner.newTitle': 'Nouvelle sortie',
|
||||
@@ -115,6 +118,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Déployer le profil altimétrique',
|
||||
'tours.planner.name': 'Nom de la sortie',
|
||||
'tours.planner.namePlaceholder': 'Randonnée sans titre',
|
||||
'tours.planner.namePlaceholder.bike': 'Sortie vélo sans titre',
|
||||
'tours.planner.namePlaceholder.kayak': 'Sortie en pagaie sans titre',
|
||||
'tours.planner.mapSearch': 'Rechercher un lieu ou une adresse',
|
||||
'tours.planner.placePoints': 'Placer des points',
|
||||
'tours.planner.pointPlaced': 'Point placé',
|
||||
@@ -179,6 +184,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.routing': "Calcul de l'itinéraire à pied…",
|
||||
'tours.planner.status.routingFailed':
|
||||
"Le calcul de l'itinéraire a échoué. Le dernier itinéraire valide reste affiché.",
|
||||
'tours.planner.status.unreachable': 'Le point {point} est inaccessible',
|
||||
'tours.planner.unreachable.hike':
|
||||
"Aucun chemin de randonnée n'atteint le point {point} : le plus proche que connaît le routeur est à {distance}. Déplace le point sur un chemin ou dessine le tronçon à la main.",
|
||||
'tours.planner.unreachable.bike':
|
||||
"Aucune voie cyclable n'atteint le point {point} : la plus proche que connaît le routeur est à {distance}. Déplace le point sur une voie ou dessine le tronçon à la main.",
|
||||
'tours.planner.unreachable.show': 'Montrer le point {point}',
|
||||
'tours.planner.status.elevation': "Chargement de l'altitude…",
|
||||
'tours.planner.status.elevationFailed': "L'altitude est indisponible. L'itinéraire calculé reste affiché.",
|
||||
'tours.planner.status.saving': 'Enregistrement…',
|
||||
@@ -351,6 +362,64 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'À voir',
|
||||
'tours.poi.cat.slipway': "Mises à l'eau",
|
||||
'tours.poi.cat.boat_rental': 'Location de bateaux',
|
||||
'tours.poi.cat.bike_repair': 'Réparation vélo',
|
||||
'tours.planner.tourTypeHint.hike': 'À pied, dans la difficulté que tu choisis',
|
||||
'tours.planner.tourTypeHint.bike': 'À vélo, itinéraire pour le voyage, la route, le gravel ou le VTT',
|
||||
'tours.planner.tourTypeHint.kayak': "Sur l'eau, en kayak, canoë ou SUP",
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Voyage',
|
||||
'tours.bike.profile.road': 'Route',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'VTT',
|
||||
'tours.bike.profileHint.touring': 'Préfère les pistes cyclables et les routes calmes, revêtues si possible.',
|
||||
'tours.bike.profileHint.road': "Reste sur l'asphalte ; les tronçons non revêtus connus sont signalés.",
|
||||
'tours.bike.profileHint.gravel': 'Prend le gravier et les chemins agricoles, pas les sentiers techniques.',
|
||||
'tours.bike.profileHint.mtb':
|
||||
'Prend les chemins rugueux comme ils viennent ; la difficulté est montrée là où elle est cartographiée.',
|
||||
'tours.bike.timeEstimated': 'Temps à vélo (estimé)',
|
||||
'tours.bike.timeEstimatedShort': 'Temps à vélo',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Comment le temps à vélo est calculé',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Le temps du routeur pour le profil choisi, sa vitesse et les montées comprises. Une valeur de planification, sans pauses.',
|
||||
'tours.bike.ways.title': 'Revêtement et voies',
|
||||
'tours.bike.ways.surface': 'Revêtement',
|
||||
'tours.bike.ways.surface.paved': 'Asphalte',
|
||||
'tours.bike.ways.surface.rough': 'Pavé grossier',
|
||||
'tours.bike.ways.surface.compacted': 'Stabilisé',
|
||||
'tours.bike.ways.surface.gravel': 'Gravier',
|
||||
'tours.bike.ways.surface.ground': 'Sol naturel',
|
||||
'tours.bike.ways.surface.unknown': 'Absent des données',
|
||||
'tours.bike.ways.way': 'Type de voie',
|
||||
'tours.bike.ways.way.cyclePath': 'Piste cyclable',
|
||||
'tours.bike.ways.way.cycleLane': 'Bande cyclable le long du trafic',
|
||||
'tours.bike.ways.way.quiet': 'Rue calme',
|
||||
'tours.bike.ways.way.road': 'Route avec circulation',
|
||||
'tours.bike.ways.way.track': 'Chemin agricole ou forestier',
|
||||
'tours.bike.ways.way.path': 'Sentier',
|
||||
'tours.bike.ways.way.ferry': 'Bac',
|
||||
'tours.bike.ways.way.other': 'Autre',
|
||||
'tours.bike.ways.loading': "Lecture des voies le long de l'itinéraire…",
|
||||
'tours.bike.ways.offline': 'Le revêtement et la difficulté des sentiers demandent une connexion.',
|
||||
'tours.bike.ways.failed': "Impossible de lire les voies de l'itinéraire. L'itinéraire lui-même ne change pas.",
|
||||
'tours.bike.ways.source':
|
||||
'Depuis OpenStreetMap, tel que le routeur le lit. Là où les données ne disent rien, le revêtement est inconnu, pas bon.',
|
||||
'tours.bike.ways.showOnMap': 'Montrer sur la carte',
|
||||
'tours.bike.ways.unpavedBadge': '{share} non revêtu',
|
||||
'tours.bike.ways.unpavedNotice': "{length} de l'itinéraire sont connus comme non revêtus.",
|
||||
'tours.bike.trail.title': 'Difficulté du sentier (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, sans difficulté particulière',
|
||||
'tours.bike.trail.s1': 'S1, petits obstacles',
|
||||
'tours.bike.trail.s2': 'S2, racines, marches, sol meuble',
|
||||
'tours.bike.trail.s3': 'S3, blocs, raide, virages serrés',
|
||||
'tours.bike.trail.s4': "S4 et plus, terrain d'experts",
|
||||
'tours.bike.trail.none': 'Pas de difficulté dans les données',
|
||||
'tours.bike.trail.unasked': 'Pas interrogeable cette fois',
|
||||
'tours.bike.trail.uphillTitle': 'Difficulté en montée (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Montée {step}',
|
||||
'tours.bike.trail.note':
|
||||
"Difficultés telles que les contributeurs d'OpenStreetMap les ont notées. Une voie sans note est inconnue, pas facile. Elles ne disent rien de ce qui te convient.",
|
||||
'tours.bike.trail.loading': 'Lecture des difficultés des sentiers…',
|
||||
'tours.bike.trail.failed': "Les difficultés des sentiers n'ont pas pu être lues cette fois.",
|
||||
'tours.copy.action': 'Enregistrer comme nouvelle sortie',
|
||||
'tours.copy.hint':
|
||||
'Copie cette sortie en une nouvelle sortie indépendante, sans ses jours. L’original reste tel quel.',
|
||||
@@ -364,6 +433,12 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Le téléchargement d’un fichier GPX nécessite une connexion au serveur.',
|
||||
'tours.gpxExport.noRoute': 'Cette sortie n’a pas d’itinéraire enregistré à exporter.',
|
||||
'tours.gpxExport.failed': 'Le fichier GPX n’a pas pu être créé.',
|
||||
'tours.gpxExport.share': 'Partager le GPX',
|
||||
'tours.gpxExport.shareShort': 'Partager',
|
||||
'tours.gpxExport.shareSaveFirst': "Enregistre d'abord la sortie pour la partager en GPX",
|
||||
'tours.gpxExport.shareReady':
|
||||
'Le fichier GPX est prêt. Touche Partager encore une fois pour ouvrir le menu de partage.',
|
||||
'tours.gpxExport.shareFailed': "Le partage n'a pas fonctionné. Le fichier GPX reste téléchargeable.",
|
||||
'tours.revision.remoteTitle': 'Cette sortie a été enregistrée ailleurs',
|
||||
'tours.revision.remoteBody':
|
||||
'Quelqu’un, ou vous sur un autre appareil, l’a enregistrée pendant votre modification. Vos changements restent jusqu’à votre décision.',
|
||||
@@ -394,15 +469,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Ce que fait le choix de l’itinéraire',
|
||||
'tours.preferences.info':
|
||||
'Ces choix pondèrent le chemin que choisit le calcul d’itinéraire. Là où il n’y a pas d’autre chemin, l’itinéraire y passe quand même. Ils ne disent rien de la difficulté d’un chemin ; la limite de difficulté ci-dessus fixe ce que l’itinéraire peut emprunter.',
|
||||
'tours.preferences.avoid_climbs': 'Éviter les fortes montées',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Des chemins plus doux là où il y en a, même plus longs. Là où on ne peut pas contourner, ça reste raide.',
|
||||
'tours.preferences.avoid_steps': 'Éviter les escaliers',
|
||||
'tours.preferences.avoid_stepsHint': 'Des escaliers seulement là où on ne peut pas les contourner.',
|
||||
'tours.preferences.avoid_ferries': 'Éviter les bacs',
|
||||
'tours.preferences.avoid_ferriesHint': 'Par voie terrestre là où on peut contourner, même si c’est plus long.',
|
||||
'tours.preferences.prefer_paths': 'Préférer les sentiers',
|
||||
'tours.preferences.prefer_pathsHint': 'Des chemins à part plutôt que les routes et leurs trottoirs, là où il y en a.',
|
||||
'tours.preferences.shortest': 'Plus court plutôt que plus rapide',
|
||||
'tours.preferences.shortestHint':
|
||||
'Le chemin choisi selon sa longueur, pas selon le temps ; souvent un peu plus court, pas toujours plus rapide.',
|
||||
'tours.planner.tourType': 'Type de sortie',
|
||||
'tours.paddle.boat': 'Embarcation',
|
||||
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
|
||||
|
||||
@@ -737,6 +737,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Χάρτης πεζοπορίας (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Προσφέρει το OpenHikingMap ως χάρτη βάσης στον σχεδιαστή διαδρομών. Δωρεάν για μη εμπορικές εγκαταστάσεις έως 500.000 πλακίδια τον μήνα· μια εμπορική ή μεγαλύτερη εγκατάσταση χρειάζεται πρώτα συμφωνία με το openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Ποδηλατικός χάρτης (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Προσφέρει το CyclOSM ως βασικό χάρτη για ποδηλατικές διαδρομές στον σχεδιαστή. Το OpenStreetMap France το παρέχει για μέτρια χρήση· μια εγκατάσταση με μεγάλη ή εμπορική χρήση το απενεργοποιεί ή συμφωνεί πρώτα με τον πάροχο',
|
||||
'admin.addons.noAddons': 'Δεν υπάρχουν διαθέσιμα πρόσθετα',
|
||||
'admin.weather.title': 'Δεδομένα Καιρού',
|
||||
'admin.weather.badge': 'Από 24 Μαρτίου 2026',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Πεζοπορία',
|
||||
'map.baseLayer.hikingDown':
|
||||
'Το OpenHikingMap δεν απαντά αυτή τη στιγμή. Άλλαξε σε άλλο χάρτη· η διαδρομή σου μένει ως έχει.',
|
||||
'map.baseLayer.cycle': 'Ποδήλατο',
|
||||
'map.baseLayer.cycleDown': 'Το CyclOSM δεν απαντά αυτή τη στιγμή. Άλλαξε σε άλλον χάρτη· η διαδρομή σου μένει ίδια.',
|
||||
'map.baseLayer.switchToSatellite': 'Εναλλαγή σε δορυφορική προβολή',
|
||||
'map.baseLayer.switchToDefault': 'Εναλλαγή σε προβολή χάρτη',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -98,6 +98,8 @@ const tours: TranslationStrings = {
|
||||
'Η πλοήγηση ανοίγει την αφετηρία του μονοπατιού· δεν σας καθοδηγεί κατά μήκος της διαδρομής.',
|
||||
'tours.planner.title': 'Σχεδιασμός εκδρομής',
|
||||
'tours.planner.newTour': 'Σχεδιασμός νέας εκδρομής',
|
||||
'tours.planner.addTour': 'Προσθήκη διαδρομής',
|
||||
'tours.planner.addTourHint': 'Σχεδίασε νέα διαδρομή, εισήγαγε αρχεία GPX ή πάρε μια διαδρομή από άλλο ταξίδι',
|
||||
'tours.planner.neutralTitle': 'Σχεδιασμός εκδρομής',
|
||||
'tours.planner.neutralBody': 'Ξεκινήστε μια νέα διαδρομή και κάντε κλικ στον χάρτη για να ορίσετε τα σημεία της.',
|
||||
'tours.planner.newTitle': 'Νέα εκδρομή',
|
||||
@@ -114,6 +116,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Ανάπτυξη υψομετρικού προφίλ',
|
||||
'tours.planner.name': 'Όνομα εκδρομής',
|
||||
'tours.planner.namePlaceholder': 'Πεζοπορία χωρίς όνομα',
|
||||
'tours.planner.namePlaceholder.bike': 'Ποδηλατάδα χωρίς όνομα',
|
||||
'tours.planner.namePlaceholder.kayak': 'Κουπί χωρίς όνομα',
|
||||
'tours.planner.mapSearch': 'Αναζήτηση τόπου ή διεύθυνσης',
|
||||
'tours.planner.placePoints': 'Τοποθέτηση σημείων',
|
||||
'tours.planner.pointPlaced': 'Το σημείο τοποθετήθηκε',
|
||||
@@ -179,6 +183,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.routing': 'Υπολογισμός διαδρομής πεζοπορίας…',
|
||||
'tours.planner.status.routingFailed':
|
||||
'Ο υπολογισμός διαδρομής απέτυχε. Εμφανίζεται ακόμη η τελευταία έγκυρη διαδρομή.',
|
||||
'tours.planner.status.unreachable': 'Το σημείο {point} δεν είναι προσβάσιμο',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Κανένα μονοπάτι πεζοπορίας δεν φτάνει στο σημείο {point}: το πλησιέστερο που ξέρει ο δρομολογητής απέχει {distance}. Μετακίνησε το σημείο σε μονοπάτι ή σχεδίασε το τμήμα με το χέρι.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Κανένας δρόμος για ποδήλατα δεν φτάνει στο σημείο {point}: ο πλησιέστερος που ξέρει ο δρομολογητής απέχει {distance}. Μετακίνησε το σημείο σε δρόμο ή σχεδίασε το τμήμα με το χέρι.',
|
||||
'tours.planner.unreachable.show': 'Εμφάνιση σημείου {point}',
|
||||
'tours.planner.status.elevation': 'Φόρτωση υψομετρικών δεδομένων…',
|
||||
'tours.planner.status.elevationFailed':
|
||||
'Τα υψομετρικά δεδομένα δεν είναι διαθέσιμα. Η υπολογισμένη διαδρομή εξακολουθεί να εμφανίζεται.',
|
||||
@@ -352,6 +362,64 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Αξιοθέατα',
|
||||
'tours.poi.cat.slipway': 'Σημεία καθέλκυσης',
|
||||
'tours.poi.cat.boat_rental': 'Ενοικίαση σκαφών',
|
||||
'tours.poi.cat.bike_repair': 'Επισκευή ποδηλάτου',
|
||||
'tours.planner.tourTypeHint.hike': 'Με τα πόδια, μέσα στη δυσκολία που ορίζεις',
|
||||
'tours.planner.tourTypeHint.bike': 'Με ποδήλατο, διαδρομή για περιήγηση, δρόμο, gravel ή MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Στο νερό, με καγιάκ, κανό ή SUP',
|
||||
'tours.bike.profile': 'Προφίλ',
|
||||
'tours.bike.profile.touring': 'Περιήγηση',
|
||||
'tours.bike.profile.road': 'Δρόμου',
|
||||
'tours.bike.profile.gravel': 'Χαλίκι',
|
||||
'tours.bike.profile.mtb': 'MTB',
|
||||
'tours.bike.profileHint.touring': 'Προτιμά ποδηλατοδρόμους και ήσυχους δρόμους, ασφαλτοστρωμένους όπου γίνεται.',
|
||||
'tours.bike.profileHint.road': 'Μένει στην άσφαλτο· εμφανίζονται τα γνωστά χωμάτινα τμήματα.',
|
||||
'tours.bike.profileHint.gravel': 'Παίρνει χαλίκι και αγροτικούς δρόμους, όχι τεχνικά μονοπάτια.',
|
||||
'tours.bike.profileHint.mtb':
|
||||
'Παίρνει τραχιά μονοπάτια όπως έρχονται· οι βαθμοί δυσκολίας εμφανίζονται όπου είναι χαρτογραφημένοι.',
|
||||
'tours.bike.timeEstimated': 'Χρόνος ποδηλασίας (εκτίμηση)',
|
||||
'tours.bike.timeEstimatedShort': 'Χρόνος ποδηλασίας',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Πώς υπολογίζεται ο χρόνος ποδηλασίας',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Ο χρόνος του δρομολογητή για το προφίλ που επέλεξες, με την ταχύτητα και τις ανηφόρες. Τιμή σχεδιασμού, χωρίς διαλείμματα.',
|
||||
'tours.bike.ways.title': 'Επιφάνεια και δρόμοι',
|
||||
'tours.bike.ways.surface': 'Επιφάνεια',
|
||||
'tours.bike.ways.surface.paved': 'Άσφαλτος',
|
||||
'tours.bike.ways.surface.rough': 'Τραχύ πλακόστρωτο',
|
||||
'tours.bike.ways.surface.compacted': 'Συμπιεσμένο',
|
||||
'tours.bike.ways.surface.gravel': 'Χαλίκι',
|
||||
'tours.bike.ways.surface.ground': 'Φυσικό έδαφος',
|
||||
'tours.bike.ways.surface.unknown': 'Δεν υπάρχει στα δεδομένα',
|
||||
'tours.bike.ways.way': 'Είδος δρόμου',
|
||||
'tours.bike.ways.way.cyclePath': 'Ποδηλατόδρομος',
|
||||
'tours.bike.ways.way.cycleLane': 'Λωρίδα ποδηλάτου δίπλα στην κυκλοφορία',
|
||||
'tours.bike.ways.way.quiet': 'Ήσυχος δρόμος',
|
||||
'tours.bike.ways.way.road': 'Δρόμος με κίνηση',
|
||||
'tours.bike.ways.way.track': 'Αγροτικός ή δασικός δρόμος',
|
||||
'tours.bike.ways.way.path': 'Μονοπάτι',
|
||||
'tours.bike.ways.way.ferry': 'Πορθμείο',
|
||||
'tours.bike.ways.way.other': 'Άλλο',
|
||||
'tours.bike.ways.loading': 'Διαβάζονται οι δρόμοι κατά μήκος της διαδρομής…',
|
||||
'tours.bike.ways.offline': 'Η επιφάνεια και οι βαθμοί δυσκολίας χρειάζονται σύνδεση.',
|
||||
'tours.bike.ways.failed': 'Δεν διαβάστηκαν οι δρόμοι της διαδρομής. Η ίδια η διαδρομή δεν αλλάζει.',
|
||||
'tours.bike.ways.source':
|
||||
'Από το OpenStreetMap, όπως το διαβάζει ο δρομολογητής. Όπου τα δεδομένα δεν λένε τίποτα, η επιφάνεια είναι άγνωστη, όχι καλή.',
|
||||
'tours.bike.ways.showOnMap': 'Εμφάνιση στον χάρτη',
|
||||
'tours.bike.ways.unpavedBadge': '{share} χωρίς άσφαλτο',
|
||||
'tours.bike.ways.unpavedNotice': '{length} της διαδρομής είναι γνωστό ότι δεν έχουν άσφαλτο.',
|
||||
'tours.bike.trail.title': 'Βαθμός δυσκολίας (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, χωρίς ιδιαίτερη δυσκολία',
|
||||
'tours.bike.trail.s1': 'S1, μικρά εμπόδια',
|
||||
'tours.bike.trail.s2': 'S2, ρίζες, σκαλοπάτια, χαλαρό έδαφος',
|
||||
'tours.bike.trail.s3': 'S3, βράχια, απότομο, κλειστές στροφές',
|
||||
'tours.bike.trail.s4': 'S4 και πάνω, έδαφος για έμπειρους',
|
||||
'tours.bike.trail.none': 'Χωρίς βαθμό στα δεδομένα',
|
||||
'tours.bike.trail.unasked': 'Δεν ήταν δυνατό να ζητηθεί αυτή τη φορά',
|
||||
'tours.bike.trail.uphillTitle': 'Δυσκολία ανάβασης (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Ανάβαση {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Βαθμοί όπως τους έδωσαν οι χαρτογράφοι του OpenStreetMap. Δρόμος χωρίς βαθμό είναι άγνωστος, όχι εύκολος. Δεν λένε αν ένα μονοπάτι σου ταιριάζει.',
|
||||
'tours.bike.trail.loading': 'Διαβάζονται οι βαθμοί δυσκολίας…',
|
||||
'tours.bike.trail.failed': 'Οι βαθμοί δυσκολίας δεν διαβάστηκαν αυτή τη φορά.',
|
||||
'tours.copy.action': 'Αποθήκευση ως νέα εκδρομή',
|
||||
'tours.copy.hint':
|
||||
'Αντιγράφει αυτή την εκδρομή ως νέα, ανεξάρτητη εκδρομή χωρίς τις μέρες της. Το πρωτότυπο μένει ως έχει.',
|
||||
@@ -365,6 +433,12 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Η λήψη αρχείου GPX χρειάζεται σύνδεση με τον διακομιστή.',
|
||||
'tours.gpxExport.noRoute': 'Αυτή η εκδρομή δεν έχει αποθηκευμένη διαδρομή για εξαγωγή.',
|
||||
'tours.gpxExport.failed': 'Το αρχείο GPX δεν δημιουργήθηκε.',
|
||||
'tours.gpxExport.share': 'Κοινοποίηση GPX',
|
||||
'tours.gpxExport.shareShort': 'Κοινοποίηση',
|
||||
'tours.gpxExport.shareSaveFirst': 'Αποθήκευσε πρώτα τη διαδρομή για να την κοινοποιήσεις ως GPX',
|
||||
'tours.gpxExport.shareReady':
|
||||
'Το αρχείο GPX είναι έτοιμο. Πάτησε ξανά Κοινοποίηση για να ανοίξει το μενού κοινοποίησης.',
|
||||
'tours.gpxExport.shareFailed': 'Η κοινοποίηση δεν πέτυχε. Το αρχείο GPX μπορεί ακόμη να ληφθεί.',
|
||||
'tours.revision.remoteTitle': 'Αυτή η εκδρομή αποθηκεύτηκε αλλού',
|
||||
'tours.revision.remoteBody':
|
||||
'Κάποιος, ή εσείς σε άλλη συσκευή, την αποθήκευσε ενώ την επεξεργαζόσασταν. Οι αλλαγές σας μένουν μέχρι να αποφασίσετε.',
|
||||
@@ -395,15 +469,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Τι κάνει η επιλογή διαδρομής',
|
||||
'tours.preferences.info':
|
||||
'Αυτές οι επιλογές σταθμίζουν τον δρόμο που επιλέγει ο δρομολογητής. Όπου δεν υπάρχει άλλος δρόμος, η διαδρομή περνά από εκεί ούτως ή άλλως. Δεν λένε τίποτα για τη δυσκολία ενός μονοπατιού· το όριο δυσκολίας παραπάνω ορίζει τι μπορεί να χρησιμοποιήσει η διαδρομή.',
|
||||
'tours.preferences.avoid_climbs': 'Αποφυγή απότομων ανηφορών',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Ηπιότεροι δρόμοι όπου υπάρχουν, ακόμη κι αν είναι μακρύτεροι. Όπου δεν υπάρχει παράκαμψη, παραμένει απότομο.',
|
||||
'tours.preferences.avoid_steps': 'Αποφυγή σκαλοπατιών',
|
||||
'tours.preferences.avoid_stepsHint': 'Σκαλοπάτια μόνο όπου δεν υπάρχει παράκαμψη.',
|
||||
'tours.preferences.avoid_ferries': 'Αποφυγή πορθμείων',
|
||||
'tours.preferences.avoid_ferriesHint': 'Από ξηράς όπου υπάρχει παράκαμψη, ακόμη κι αν είναι μεγαλύτερη.',
|
||||
'tours.preferences.prefer_paths': 'Προτίμηση πεζόδρομων',
|
||||
'tours.preferences.prefer_pathsHint': 'Ξεχωριστά μονοπάτια αντί για δρόμους και πεζοδρόμια, όπου υπάρχουν.',
|
||||
'tours.preferences.shortest': 'Συντομότερη αντί για ταχύτερη',
|
||||
'tours.preferences.shortestHint':
|
||||
'Ο δρόμος επιλέγεται με βάση το μήκος, όχι τον χρόνο· συχνά λίγο μικρότερος, όχι πάντα ταχύτερος.',
|
||||
'tours.planner.tourType': 'Τύπος διαδρομής',
|
||||
'tours.paddle.boat': 'Σκάφος',
|
||||
'tours.paddle.boat.kayak': 'Καγιάκ',
|
||||
|
||||
@@ -675,6 +675,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Túratérkép (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Az OpenHikingMapet alaptérképként kínálja a túratervezőben. Nem kereskedelmi telepítéseknek havi 500 000 csempéig ingyenes; kereskedelmi vagy nagyobb telepítéshez előbb megállapodás kell az openmaps.fr-rel',
|
||||
'admin.toursCycleMap.title': 'Kerékpáros térkép (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'A CyclOSM-et kínálja alaptérképként a kerékpártúrákhoz a túratervezőben. Az OpenStreetMap France mérsékelt használatra biztosítja; egy erősen használt vagy kereskedelmi telepítés kikapcsolja, vagy előbb megállapodik az üzemeltetővel',
|
||||
'admin.addons.noAddons': 'Nincsenek elérhető bővítmények',
|
||||
'admin.weather.title': 'Időjárás adatok',
|
||||
'admin.weather.badge': '2026. március 24. óta',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Műhold',
|
||||
'map.baseLayer.hiking': 'Túrázás',
|
||||
'map.baseLayer.hikingDown': 'Az OpenHikingMap most nem válaszol. Válts másik térképre, a túrád változatlan marad.',
|
||||
'map.baseLayer.cycle': 'Kerékpár',
|
||||
'map.baseLayer.cycleDown': 'A CyclOSM most nem válaszol. Válts másik térképre, a túrád változatlan marad.',
|
||||
'map.baseLayer.switchToSatellite': 'Váltás műholdas nézetre',
|
||||
'map.baseLayer.switchToDefault': 'Váltás térkép nézetre',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -95,6 +95,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'A navigáció a túra kiindulópontját nyitja meg; nem vezet végig az útvonalon.',
|
||||
'tours.planner.title': 'Túra tervezése',
|
||||
'tours.planner.newTour': 'Új túra tervezése',
|
||||
'tours.planner.addTour': 'Túra hozzáadása',
|
||||
'tours.planner.addTourHint': 'Új túra tervezése, GPX-fájlok importálása vagy túra átvétele egy másik utazásból',
|
||||
'tours.planner.neutralTitle': 'Túra tervezése',
|
||||
'tours.planner.neutralBody': 'Indíts új túrát, és kattints a térképre a pontjai elhelyezéséhez.',
|
||||
'tours.planner.newTitle': 'Új túra',
|
||||
@@ -110,6 +112,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Magassági profil kibontása',
|
||||
'tours.planner.name': 'Túra neve',
|
||||
'tours.planner.namePlaceholder': 'Névtelen gyalogtúra',
|
||||
'tours.planner.namePlaceholder.bike': 'Névtelen kerékpártúra',
|
||||
'tours.planner.namePlaceholder.kayak': 'Névtelen evezőstúra',
|
||||
'tours.planner.mapSearch': 'Hely vagy cím keresése',
|
||||
'tours.planner.placePoints': 'Pontok elhelyezése',
|
||||
'tours.planner.pointPlaced': 'Pont elhelyezve',
|
||||
@@ -174,6 +178,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.routing': 'Gyalogos útvonal kiszámítása…',
|
||||
'tours.planner.status.routingFailed':
|
||||
'Az útvonalszámítás sikertelen. Továbbra is az utolsó sikeresen kiszámított útvonal látható.',
|
||||
'tours.planner.status.unreachable': 'A(z) {point}. pont nem érhető el',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Egyetlen gyalogút sem éri el a(z) {point}. pontot: a legközelebbi, amit az útvonaltervező ismer, {distance} távolságra van. Tedd a pontot egy útra, vagy rajzold meg kézzel a szakaszt.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Egyetlen kerékpárral járható út sem éri el a(z) {point}. pontot: a legközelebbi, amit az útvonaltervező ismer, {distance} távolságra van. Tedd a pontot egy útra, vagy rajzold meg kézzel a szakaszt.',
|
||||
'tours.planner.unreachable.show': 'A(z) {point}. pont mutatása',
|
||||
'tours.planner.status.elevation': 'Magassági adatok betöltése…',
|
||||
'tours.planner.status.elevationFailed':
|
||||
'A magassági adatok nem érhetők el. A kiszámított útvonal továbbra is látható.',
|
||||
@@ -346,6 +356,64 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Látnivalók',
|
||||
'tours.poi.cat.slipway': 'Vízre szállási helyek',
|
||||
'tours.poi.cat.boat_rental': 'Csónakkölcsönző',
|
||||
'tours.poi.cat.bike_repair': 'Kerékpárszerviz',
|
||||
'tours.planner.tourTypeHint.hike': 'Gyalog, az általad megadott nehézségig',
|
||||
'tours.planner.tourTypeHint.bike': 'Kerékpárral, túra, országúti, gravel vagy MTB útvonallal',
|
||||
'tours.planner.tourTypeHint.kayak': 'Vízen, kajakkal, kenuval vagy SUP-pal',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Túra',
|
||||
'tours.bike.profile.road': 'Országúti',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Kerékpárutakat és csendes utakat részesít előnyben, lehetőleg burkoltat.',
|
||||
'tours.bike.profileHint.road': 'Aszfalton marad; az ismert burkolatlan szakaszokat mutatja.',
|
||||
'tours.bike.profileHint.gravel': 'Kavicsos és gazdasági utakat használ, technikás singletraileket nem.',
|
||||
'tours.bike.profileHint.mtb':
|
||||
'Úgy veszi a durva utakat, ahogy jönnek; a nehézséget ott mutatja, ahol fel van térképezve.',
|
||||
'tours.bike.timeEstimated': 'Kerékpáros idő (becsült)',
|
||||
'tours.bike.timeEstimatedShort': 'Kerékpáros idő',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Hogyan számoljuk a kerékpáros időt',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Az útvonaltervező ideje a választott profilra, a sebességgel és az emelkedőkkel. Tervezési érték, szünetek nélkül.',
|
||||
'tours.bike.ways.title': 'Burkolat és utak',
|
||||
'tours.bike.ways.surface': 'Burkolat',
|
||||
'tours.bike.ways.surface.paved': 'Aszfalt',
|
||||
'tours.bike.ways.surface.rough': 'Durva burkolat',
|
||||
'tours.bike.ways.surface.compacted': 'Tömörített',
|
||||
'tours.bike.ways.surface.gravel': 'Kavics',
|
||||
'tours.bike.ways.surface.ground': 'Földút',
|
||||
'tours.bike.ways.surface.unknown': 'Nincs az adatokban',
|
||||
'tours.bike.ways.way': 'Úttípus',
|
||||
'tours.bike.ways.way.cyclePath': 'Kerékpárút',
|
||||
'tours.bike.ways.way.cycleLane': 'Kerékpársáv a forgalom mellett',
|
||||
'tours.bike.ways.way.quiet': 'Csendes utca',
|
||||
'tours.bike.ways.way.road': 'Forgalmas út',
|
||||
'tours.bike.ways.way.track': 'Mező- vagy erdei út',
|
||||
'tours.bike.ways.way.path': 'Ösvény',
|
||||
'tours.bike.ways.way.ferry': 'Komp',
|
||||
'tours.bike.ways.way.other': 'Egyéb',
|
||||
'tours.bike.ways.loading': 'Az útvonal mentén lévő utak beolvasása…',
|
||||
'tours.bike.ways.offline': 'A burkolathoz és a nehézséghez kapcsolat kell.',
|
||||
'tours.bike.ways.failed': 'Az útvonal menti utakat nem sikerült beolvasni. Maga az útvonal nem változik.',
|
||||
'tours.bike.ways.source':
|
||||
'Az OpenStreetMapből, ahogy az útvonaltervező olvassa. Ahol az adat hallgat, a burkolat ismeretlen, nem jó.',
|
||||
'tours.bike.ways.showOnMap': 'Mutasd a térképen',
|
||||
'tours.bike.ways.unpavedBadge': '{share} burkolatlan',
|
||||
'tours.bike.ways.unpavedNotice': 'Az útvonalból {length} ismerten burkolatlan.',
|
||||
'tours.bike.trail.title': 'Nehézségi fok (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, különösebb nehézség nélkül',
|
||||
'tours.bike.trail.s1': 'S1, kisebb akadályok',
|
||||
'tours.bike.trail.s2': 'S2, gyökerek, lépcsők, laza talaj',
|
||||
'tours.bike.trail.s3': 'S3, sziklák, meredek, szűk fordulók',
|
||||
'tours.bike.trail.s4': 'S4 és fölötte, haladó terep',
|
||||
'tours.bike.trail.none': 'Nincs fok az adatokban',
|
||||
'tours.bike.trail.unasked': 'Ezúttal nem lehetett lekérdezni',
|
||||
'tours.bike.trail.uphillTitle': 'Emelkedő nehézsége (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Emelkedő {step}',
|
||||
'tours.bike.trail.note':
|
||||
'A fokok az OpenStreetMap-térképezők szerint. A fok nélküli út ismeretlen, nem könnyű. Semmit sem mondanak arról, hogy egy ösvény neked való-e.',
|
||||
'tours.bike.trail.loading': 'Nehézségi fokok beolvasása…',
|
||||
'tours.bike.trail.failed': 'A nehézségi fokokat ezúttal nem sikerült beolvasni.',
|
||||
'tours.copy.action': 'Mentés új túraként',
|
||||
'tours.copy.hint': 'Ezt a túrát új, önálló túraként másolja a napjai nélkül. Az eredeti változatlan marad.',
|
||||
'tours.copy.name': '{name} (másolat)',
|
||||
@@ -358,6 +426,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'A GPX-fájl letöltéséhez kapcsolat kell a szerverrel.',
|
||||
'tours.gpxExport.noRoute': 'Ennek a túrának nincs exportálható mentett útvonala.',
|
||||
'tours.gpxExport.failed': 'A GPX-fájlt nem sikerült létrehozni.',
|
||||
'tours.gpxExport.share': 'GPX megosztása',
|
||||
'tours.gpxExport.shareShort': 'Megosztás',
|
||||
'tours.gpxExport.shareSaveFirst': 'Előbb mentsd a túrát, hogy GPX-ként megoszthasd',
|
||||
'tours.gpxExport.shareReady': 'A GPX-fájl kész. Koppints még egyszer a Megosztásra a megosztási ablak megnyitásához.',
|
||||
'tours.gpxExport.shareFailed': 'A megosztás nem sikerült. A GPX-fájl továbbra is letölthető.',
|
||||
'tours.revision.remoteTitle': 'Ezt a túrát máshol mentették',
|
||||
'tours.revision.remoteBody':
|
||||
'Valaki, vagy te egy másik eszközön, elmentette, miközben szerkesztetted. A módosításaid megmaradnak, amíg nem döntesz.',
|
||||
@@ -388,15 +461,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Mit tesz az útvonalválasztás',
|
||||
'tours.preferences.info':
|
||||
'Ezek a beállítások azt súlyozzák, melyik utat választja az útvonaltervező. Ahol nincs más út, az útvonal mégis arra vezet. A nehézségről nem mondanak semmit; hogy mit használhat az útvonal, a fenti nehézségi korlát szabja meg.',
|
||||
'tours.preferences.avoid_climbs': 'Meredek emelkedők kerülése',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Enyhébb utak, ahol vannak, még ha hosszabbak is. Ahol nincs kerülőút, meredek marad.',
|
||||
'tours.preferences.avoid_steps': 'Lépcsők kerülése',
|
||||
'tours.preferences.avoid_stepsHint': 'Lépcső csak ott, ahol nincs kerülőút.',
|
||||
'tours.preferences.avoid_ferries': 'Kompok kerülése',
|
||||
'tours.preferences.avoid_ferriesHint': 'Szárazföldön, ahol van kerülőút, akkor is, ha hosszabb.',
|
||||
'tours.preferences.prefer_paths': 'Gyalogutak előnyben',
|
||||
'tours.preferences.prefer_pathsHint': 'Önálló utak az utak és járdáik helyett, ahol vannak.',
|
||||
'tours.preferences.shortest': 'Rövidebb a gyorsabb helyett',
|
||||
'tours.preferences.shortestHint':
|
||||
'Az utat a hossza szerint választja, nem az idő szerint; gyakran kicsit rövidebb, nem mindig gyorsabb.',
|
||||
'tours.planner.tourType': 'Túratípus',
|
||||
'tours.paddle.boat': 'Hajó',
|
||||
'tours.paddle.boat.kayak': 'Kajak',
|
||||
|
||||
@@ -713,6 +713,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Peta pendakian (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Menawarkan OpenHikingMap sebagai peta dasar di perencana tur. Gratis untuk instalasi nonkomersial hingga 500.000 ubin per bulan; instalasi komersial atau yang lebih besar perlu perjanjian dengan openmaps.fr terlebih dahulu',
|
||||
'admin.toursCycleMap.title': 'Peta sepeda (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Menawarkan CyclOSM sebagai peta dasar untuk tur sepeda di perencana tur. OpenStreetMap France menyediakannya untuk penggunaan wajar; instalasi dengan penggunaan berat atau komersial mematikannya atau terlebih dahulu membuat kesepakatan dengan operator',
|
||||
'admin.addons.noAddons': 'Tidak ada addon yang tersedia',
|
||||
'admin.weather.title': 'Data Cuaca',
|
||||
'admin.weather.badge': 'Sejak 24 Maret 2026',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Satelit',
|
||||
'map.baseLayer.hiking': 'Pendakian',
|
||||
'map.baseLayer.hikingDown': 'OpenHikingMap sedang tidak merespons. Beralih ke peta lain; turmu tetap seperti semula.',
|
||||
'map.baseLayer.cycle': 'Sepeda',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM sedang tidak merespons. Beralih ke peta lain; turmu tetap seperti semula.',
|
||||
'map.baseLayer.switchToSatellite': 'Beralih ke tampilan satelit',
|
||||
'map.baseLayer.switchToDefault': 'Beralih ke tampilan peta',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -92,6 +92,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Navigasi membuka titik awal jalur, bukan memberikan panduan sepanjang rute.',
|
||||
'tours.planner.title': 'Rencanakan tur',
|
||||
'tours.planner.newTour': 'Rencanakan tur baru',
|
||||
'tours.planner.addTour': 'Tambah tur',
|
||||
'tours.planner.addTourHint': 'Rencanakan tur baru, impor file GPX, atau ambil tur dari perjalanan lain',
|
||||
'tours.planner.neutralTitle': 'Rencanakan tur',
|
||||
'tours.planner.neutralBody': 'Mulai tur baru dan klik peta untuk menempatkan titik-titiknya.',
|
||||
'tours.planner.newTitle': 'Tur baru',
|
||||
@@ -108,6 +110,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Perluas profil elevasi',
|
||||
'tours.planner.name': 'Nama tur',
|
||||
'tours.planner.namePlaceholder': 'Pendakian tanpa judul',
|
||||
'tours.planner.namePlaceholder.bike': 'Tur sepeda tanpa nama',
|
||||
'tours.planner.namePlaceholder.kayak': 'Tur dayung tanpa nama',
|
||||
'tours.planner.mapSearch': 'Cari tempat atau alamat',
|
||||
'tours.planner.placePoints': 'Tempatkan titik',
|
||||
'tours.planner.pointPlaced': 'Titik ditempatkan',
|
||||
@@ -170,6 +174,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.routing': 'Menghitung rute berjalan kaki...',
|
||||
'tours.planner.status.routingFailed':
|
||||
'Perhitungan rute gagal. Rute terakhir yang berhasil dihitung tetap ditampilkan.',
|
||||
'tours.planner.status.unreachable': 'Titik {point} tidak dapat dicapai',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Tidak ada jalur pendakian yang mencapai titik {point}: yang terdekat yang diketahui perute berjarak {distance}. Pindahkan titik ke jalur, atau gambar ruas ke sana secara manual.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Tidak ada jalan untuk sepeda yang mencapai titik {point}: yang terdekat yang diketahui perute berjarak {distance}. Pindahkan titik ke jalan, atau gambar ruas ke sana secara manual.',
|
||||
'tours.planner.unreachable.show': 'Tampilkan titik {point}',
|
||||
'tours.planner.status.elevation': 'Memuat elevasi...',
|
||||
'tours.planner.status.elevationFailed': 'Elevasi tidak tersedia. Rute yang telah dihitung tetap ditampilkan.',
|
||||
'tours.planner.status.saving': 'Menyimpan...',
|
||||
@@ -339,6 +349,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Tempat menarik',
|
||||
'tours.poi.cat.slipway': 'Titik masuk air',
|
||||
'tours.poi.cat.boat_rental': 'Penyewaan perahu',
|
||||
'tours.poi.cat.bike_repair': 'Bengkel sepeda',
|
||||
'tours.planner.tourTypeHint.hike': 'Jalan kaki, dalam tingkat kesulitan yang kamu tentukan',
|
||||
'tours.planner.tourTypeHint.bike': 'Dengan sepeda, rute untuk touring, road, gravel, atau MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Di air, dengan kayak, kano, atau SUP',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Touring', // same-as-en
|
||||
'tours.bike.profile.road': 'Road', // same-as-en
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Mengutamakan jalur sepeda dan jalan sepi, beraspal bila bisa.',
|
||||
'tours.bike.profileHint.road': 'Tetap di aspal; ruas tak beraspal yang diketahui ditampilkan.',
|
||||
'tours.bike.profileHint.gravel': 'Melewati kerikil dan jalan tani, bukan singletrail teknis.',
|
||||
'tours.bike.profileHint.mtb': 'Melewati jalur kasar apa adanya; tingkat jalur ditampilkan bila dipetakan.',
|
||||
'tours.bike.timeEstimated': 'Waktu bersepeda (perkiraan)',
|
||||
'tours.bike.timeEstimatedShort': 'Waktu bersepeda',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Cara waktu bersepeda dihitung',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Waktu dari perute untuk profil yang dipilih, termasuk kecepatan dan tanjakan. Nilai perencanaan, tanpa istirahat.',
|
||||
'tours.bike.ways.title': 'Permukaan dan jalan',
|
||||
'tours.bike.ways.surface': 'Permukaan',
|
||||
'tours.bike.ways.surface.paved': 'Aspal',
|
||||
'tours.bike.ways.surface.rough': 'Perkerasan kasar',
|
||||
'tours.bike.ways.surface.compacted': 'Dipadatkan',
|
||||
'tours.bike.ways.surface.gravel': 'Kerikil',
|
||||
'tours.bike.ways.surface.ground': 'Tanah alami',
|
||||
'tours.bike.ways.surface.unknown': 'Tidak ada di data',
|
||||
'tours.bike.ways.way': 'Jenis jalan',
|
||||
'tours.bike.ways.way.cyclePath': 'Jalur sepeda',
|
||||
'tours.bike.ways.way.cycleLane': 'Lajur sepeda di samping lalu lintas',
|
||||
'tours.bike.ways.way.quiet': 'Jalan sepi',
|
||||
'tours.bike.ways.way.road': 'Jalan ramai',
|
||||
'tours.bike.ways.way.track': 'Jalan tani atau hutan',
|
||||
'tours.bike.ways.way.path': 'Jalan setapak',
|
||||
'tours.bike.ways.way.ferry': 'Feri',
|
||||
'tours.bike.ways.way.other': 'Lainnya',
|
||||
'tours.bike.ways.loading': 'Membaca jalan di sepanjang rute…',
|
||||
'tours.bike.ways.offline': 'Permukaan dan tingkat jalur memerlukan koneksi.',
|
||||
'tours.bike.ways.failed': 'Jalan di sepanjang rute tidak dapat dibaca. Rutenya sendiri tidak berubah.',
|
||||
'tours.bike.ways.source':
|
||||
'Dari OpenStreetMap, sebagaimana dibaca perute. Bila data tidak menyebut apa pun, permukaannya tidak diketahui, bukan baik.',
|
||||
'tours.bike.ways.showOnMap': 'Tampilkan di peta',
|
||||
'tours.bike.ways.unpavedBadge': '{share} tak beraspal',
|
||||
'tours.bike.ways.unpavedNotice': '{length} rute diketahui tidak beraspal.',
|
||||
'tours.bike.trail.title': 'Tingkat jalur (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, tanpa kesulitan khusus',
|
||||
'tours.bike.trail.s1': 'S1, rintangan kecil',
|
||||
'tours.bike.trail.s2': 'S2, akar, undakan, tanah gembur',
|
||||
'tours.bike.trail.s3': 'S3, bebatuan, curam, tikungan tajam',
|
||||
'tours.bike.trail.s4': 'S4 ke atas, medan ahli',
|
||||
'tours.bike.trail.none': 'Tidak ada tingkat di data',
|
||||
'tours.bike.trail.unasked': 'Tidak dapat ditanyakan kali ini',
|
||||
'tours.bike.trail.uphillTitle': 'Tingkat tanjakan (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Tanjakan {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Tingkat sebagaimana diberikan pemeta OpenStreetMap. Jalan tanpa tingkat tidak diketahui, bukan mudah. Ini tidak menyatakan apakah jalur cocok untukmu.',
|
||||
'tours.bike.trail.loading': 'Membaca tingkat jalur…',
|
||||
'tours.bike.trail.failed': 'Tingkat jalur tidak dapat dibaca kali ini.',
|
||||
'tours.copy.action': 'Simpan sebagai tur baru',
|
||||
'tours.copy.hint': 'Menyalin tur ini sebagai tur baru yang mandiri tanpa hari-harinya. Aslinya tetap seperti semula.',
|
||||
'tours.copy.name': '{name} (salinan)',
|
||||
@@ -351,6 +418,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Mengunduh file GPX memerlukan koneksi ke server.',
|
||||
'tours.gpxExport.noRoute': 'Tur ini tidak punya rute tersimpan untuk diekspor.',
|
||||
'tours.gpxExport.failed': 'File GPX tidak dapat dibuat.',
|
||||
'tours.gpxExport.share': 'Bagikan GPX',
|
||||
'tours.gpxExport.shareShort': 'Bagikan',
|
||||
'tours.gpxExport.shareSaveFirst': 'Simpan tur dulu untuk membagikannya sebagai GPX',
|
||||
'tours.gpxExport.shareReady': 'File GPX sudah siap. Ketuk Bagikan sekali lagi untuk membuka lembar berbagi.',
|
||||
'tours.gpxExport.shareFailed': 'Berbagi tidak berhasil. File GPX masih bisa diunduh.',
|
||||
'tours.revision.remoteTitle': 'Tur ini disimpan di tempat lain',
|
||||
'tours.revision.remoteBody':
|
||||
'Seseorang, atau kamu di perangkat lain, menyimpannya saat kamu mengedit. Perubahanmu tetap ada sampai kamu memutuskan.',
|
||||
@@ -381,15 +453,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Apa yang dilakukan pilihan rute',
|
||||
'tours.preferences.info':
|
||||
'Pilihan ini memberi bobot pada jalan yang dipilih perute. Jika tidak ada jalan lain, rute tetap lewat sana. Pilihan ini tidak mengatakan apa pun soal tingkat kesulitan jalur; batas kesulitan di atas menentukan apa yang boleh dipakai rute.',
|
||||
'tours.preferences.avoid_climbs': 'Hindari tanjakan curam',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Jalan yang lebih landai jika ada, meski lebih panjang. Jika tidak ada jalan memutar, tetap curam.',
|
||||
'tours.preferences.avoid_steps': 'Hindari tangga',
|
||||
'tours.preferences.avoid_stepsHint': 'Tangga hanya jika tidak ada jalan memutar.',
|
||||
'tours.preferences.avoid_ferries': 'Hindari feri',
|
||||
'tours.preferences.avoid_ferriesHint': 'Lewat darat jika ada jalan memutar, meski lebih panjang.',
|
||||
'tours.preferences.prefer_paths': 'Utamakan jalur pejalan kaki',
|
||||
'tours.preferences.prefer_pathsHint': 'Jalur tersendiri alih-alih jalan raya dan trotoarnya, bila ada.',
|
||||
'tours.preferences.shortest': 'Lebih pendek ketimbang lebih cepat',
|
||||
'tours.preferences.shortestHint':
|
||||
'Jalan dipilih berdasarkan panjangnya, bukan waktunya; sering sedikit lebih pendek, tidak selalu lebih cepat.',
|
||||
'tours.planner.tourType': 'Jenis tur',
|
||||
'tours.paddle.boat': 'Perahu',
|
||||
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
|
||||
|
||||
@@ -669,6 +669,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Mappa escursionistica (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
"Offre OpenHikingMap come mappa di base nel pianificatore di percorsi. Gratuito per installazioni non commerciali fino a 500.000 tile al mese; un'installazione commerciale o più grande richiede prima un accordo con openmaps.fr",
|
||||
'admin.toursCycleMap.title': 'Mappa ciclabile (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
"Offre CyclOSM come mappa di base per i giri in bici nel pianificatore. OpenStreetMap France la fornisce per un uso moderato; un'installazione molto usata o commerciale la disattiva o prende prima un accordo con il gestore",
|
||||
'admin.addons.noAddons': 'Nessun modulo disponibile',
|
||||
'admin.weather.title': 'Dati meteo',
|
||||
'admin.weather.badge': 'Dal 24 marzo 2026',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Escursionismo',
|
||||
'map.baseLayer.hikingDown':
|
||||
"OpenHikingMap al momento non risponde. Passa a un'altra mappa; il tuo percorso resta com'è.",
|
||||
'map.baseLayer.cycle': 'Bici',
|
||||
'map.baseLayer.cycleDown': "CyclOSM al momento non risponde. Passa a un'altra mappa; il tuo giro resta com'è.",
|
||||
'map.baseLayer.switchToSatellite': 'Passa alla vista satellitare',
|
||||
'map.baseLayer.switchToDefault': 'Passa alla vista mappa',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -96,6 +96,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'La navigazione apre il punto di partenza del sentiero; non guida lungo il percorso.',
|
||||
'tours.planner.title': "Pianifica un'escursione",
|
||||
'tours.planner.newTour': 'Pianifica nuova escursione',
|
||||
'tours.planner.addTour': 'Aggiungi giro',
|
||||
'tours.planner.addTourHint': 'Pianifica un nuovo giro, importa file GPX o riprendi un giro da un altro viaggio',
|
||||
'tours.planner.neutralTitle': "Pianifica un'escursione",
|
||||
'tours.planner.neutralBody': 'Inizia un nuovo percorso e fai clic sulla mappa per posizionarne i punti.',
|
||||
'tours.planner.newTitle': 'Nuova escursione',
|
||||
@@ -112,6 +114,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Espandi profilo altimetrico',
|
||||
'tours.planner.name': "Nome dell'escursione",
|
||||
'tours.planner.namePlaceholder': 'Escursione a piedi senza titolo',
|
||||
'tours.planner.namePlaceholder.bike': 'Giro in bici senza nome',
|
||||
'tours.planner.namePlaceholder.kayak': 'Uscita in canoa senza nome',
|
||||
'tours.planner.mapSearch': 'Cerca luogo o indirizzo',
|
||||
'tours.planner.placePoints': 'Posiziona punti',
|
||||
'tours.planner.pointPlaced': 'Punto posizionato',
|
||||
@@ -175,6 +179,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'In attesa del calcolo',
|
||||
'tours.planner.status.routing': 'Calcolo del percorso a piedi…',
|
||||
'tours.planner.status.routingFailed': "Calcolo del percorso non riuscito. L'ultimo percorso valido resta visibile.",
|
||||
'tours.planner.status.unreachable': 'Il punto {point} non è raggiungibile',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Nessun sentiero raggiunge il punto {point}: il più vicino che il router conosce è a {distance}. Sposta il punto su un sentiero o disegna a mano il tratto fin lì.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Nessuna strada percorribile in bici raggiunge il punto {point}: la più vicina che il router conosce è a {distance}. Sposta il punto su una strada o disegna a mano il tratto fin lì.',
|
||||
'tours.planner.unreachable.show': 'Mostra il punto {point}',
|
||||
'tours.planner.status.elevation': 'Caricamento delle quote…',
|
||||
'tours.planner.status.elevationFailed': 'Le quote non sono disponibili. Il percorso calcolato resta visibile.',
|
||||
'tours.planner.status.saving': 'Salvataggio…',
|
||||
@@ -347,6 +357,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Da vedere',
|
||||
'tours.poi.cat.slipway': 'Punti di imbarco',
|
||||
'tours.poi.cat.boat_rental': 'Noleggio barche',
|
||||
'tours.poi.cat.bike_repair': 'Riparazione bici',
|
||||
'tours.planner.tourTypeHint.hike': 'A piedi, entro una difficoltà che scegli tu',
|
||||
'tours.planner.tourTypeHint.bike': 'In bici, percorso per cicloturismo, strada, gravel o MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'In acqua, in kayak, canoa o SUP',
|
||||
'tours.bike.profile': 'Profilo',
|
||||
'tours.bike.profile.touring': 'Turismo',
|
||||
'tours.bike.profile.road': 'Strada',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Preferisce piste ciclabili e strade tranquille, asfaltate dove possibile.',
|
||||
'tours.bike.profileHint.road': "Resta sull'asfalto; i tratti sterrati noti vengono mostrati.",
|
||||
'tours.bike.profileHint.gravel': 'Prende ghiaia e strade di campagna, non singletrack tecnici.',
|
||||
'tours.bike.profileHint.mtb': 'Prende i sentieri sconnessi come vengono; le difficoltà sono mostrate dove mappate.',
|
||||
'tours.bike.timeEstimated': 'Tempo in bici (stimato)',
|
||||
'tours.bike.timeEstimatedShort': 'Tempo in bici',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Come si calcola il tempo in bici',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Il tempo del router per il profilo scelto, con la sua velocità e le salite. Un valore di pianificazione, pause escluse.',
|
||||
'tours.bike.ways.title': 'Fondo e percorsi',
|
||||
'tours.bike.ways.surface': 'Fondo',
|
||||
'tours.bike.ways.surface.paved': 'Asfalto',
|
||||
'tours.bike.ways.surface.rough': 'Pavé irregolare',
|
||||
'tours.bike.ways.surface.compacted': 'Battuto',
|
||||
'tours.bike.ways.surface.gravel': 'Ghiaia',
|
||||
'tours.bike.ways.surface.ground': 'Terreno naturale',
|
||||
'tours.bike.ways.surface.unknown': 'Non nei dati',
|
||||
'tours.bike.ways.way': 'Tipo di percorso',
|
||||
'tours.bike.ways.way.cyclePath': 'Pista ciclabile',
|
||||
'tours.bike.ways.way.cycleLane': 'Corsia ciclabile accanto al traffico',
|
||||
'tours.bike.ways.way.quiet': 'Strada tranquilla',
|
||||
'tours.bike.ways.way.road': 'Strada trafficata',
|
||||
'tours.bike.ways.way.track': 'Strada agricola o forestale',
|
||||
'tours.bike.ways.way.path': 'Sentiero',
|
||||
'tours.bike.ways.way.ferry': 'Traghetto',
|
||||
'tours.bike.ways.way.other': 'Altro',
|
||||
'tours.bike.ways.loading': "Lettura dei percorsi lungo l'itinerario…",
|
||||
'tours.bike.ways.offline': 'Fondo e difficoltà dei sentieri richiedono una connessione.',
|
||||
'tours.bike.ways.failed': "Impossibile leggere i percorsi lungo l'itinerario. L'itinerario resta invariato.",
|
||||
'tours.bike.ways.source':
|
||||
'Da OpenStreetMap, come lo legge il router. Dove i dati non dicono nulla, il fondo è sconosciuto, non buono.',
|
||||
'tours.bike.ways.showOnMap': 'Mostra sulla mappa',
|
||||
'tours.bike.ways.unpavedBadge': '{share} sterrato',
|
||||
'tours.bike.ways.unpavedNotice': '{length} del percorso risultano sterrati.',
|
||||
'tours.bike.trail.title': 'Difficoltà del sentiero (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, nessuna difficoltà particolare',
|
||||
'tours.bike.trail.s1': 'S1, piccoli ostacoli',
|
||||
'tours.bike.trail.s2': 'S2, radici, gradini, fondo smosso',
|
||||
'tours.bike.trail.s3': 'S3, massi, ripido, tornanti stretti',
|
||||
'tours.bike.trail.s4': 'S4 e oltre, terreno da esperti',
|
||||
'tours.bike.trail.none': 'Nessun grado nei dati',
|
||||
'tours.bike.trail.unasked': 'Non interrogabile questa volta',
|
||||
'tours.bike.trail.uphillTitle': 'Difficoltà in salita (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Salita {step}',
|
||||
'tours.bike.trail.note':
|
||||
"Gradi come li hanno assegnati i mappatori di OpenStreetMap. Un percorso senza grado è sconosciuto, non facile. Non dicono nulla sull'adattezza del sentiero a te.",
|
||||
'tours.bike.trail.loading': 'Lettura delle difficoltà dei sentieri…',
|
||||
'tours.bike.trail.failed': 'Questa volta non è stato possibile leggere le difficoltà dei sentieri.',
|
||||
'tours.copy.action': 'Salva come nuovo tour',
|
||||
'tours.copy.hint': 'Copia questo tour come nuovo tour indipendente, senza i suoi giorni. L’originale resta com’è.',
|
||||
'tours.copy.name': '{name} (copia)',
|
||||
@@ -359,6 +426,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Per scaricare un file GPX serve una connessione al server.',
|
||||
'tours.gpxExport.noRoute': 'Questo tour non ha un percorso salvato da esportare.',
|
||||
'tours.gpxExport.failed': 'Non è stato possibile creare il file GPX.',
|
||||
'tours.gpxExport.share': 'Condividi GPX',
|
||||
'tours.gpxExport.shareShort': 'Condividi',
|
||||
'tours.gpxExport.shareSaveFirst': 'Salva prima il giro per condividerlo come GPX',
|
||||
'tours.gpxExport.shareReady': 'Il file GPX è pronto. Tocca di nuovo Condividi per aprire il menu di condivisione.',
|
||||
'tours.gpxExport.shareFailed': 'La condivisione non è riuscita. Il file GPX si può comunque scaricare.',
|
||||
'tours.revision.remoteTitle': 'Questo tour è stato salvato altrove',
|
||||
'tours.revision.remoteBody':
|
||||
'Qualcuno, o tu su un altro dispositivo, l’ha salvato mentre lo modificavi. Le tue modifiche restano finché non decidi.',
|
||||
@@ -389,15 +461,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Cosa fa la scelta del percorso',
|
||||
'tours.preferences.info':
|
||||
'Queste scelte pesano la strada che sceglie il calcolo del percorso. Dove non c’è un’altra via, il percorso passa comunque di lì. Non dicono nulla sulla difficoltà di un sentiero; il limite di difficoltà sopra stabilisce cosa può usare il percorso.',
|
||||
'tours.preferences.avoid_climbs': 'Evita salite ripide',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
"Percorsi più dolci dove ci sono, anche se più lunghi. Dove non c'è modo di aggirare, resta ripido.",
|
||||
'tours.preferences.avoid_steps': 'Evita le scale',
|
||||
'tours.preferences.avoid_stepsHint': 'Scale solo dove non c’è modo di aggirarle.',
|
||||
'tours.preferences.avoid_ferries': 'Evita i traghetti',
|
||||
'tours.preferences.avoid_ferriesHint': 'Via terra dove c’è un modo di aggirare, anche se più lungo.',
|
||||
'tours.preferences.prefer_paths': 'Preferisci i sentieri',
|
||||
'tours.preferences.prefer_pathsHint': 'Sentieri propri invece di strade e marciapiedi, dove ci sono.',
|
||||
'tours.preferences.shortest': 'Più breve invece che più veloce',
|
||||
'tours.preferences.shortestHint':
|
||||
'La strada scelta per lunghezza, non per tempo; spesso un po’ più breve, non sempre più veloce.',
|
||||
'tours.planner.tourType': 'Tipo di tour',
|
||||
'tours.paddle.boat': 'Imbarcazione',
|
||||
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
|
||||
|
||||
@@ -676,6 +676,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'ハイキング地図 (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'ツアープランナーのベースマップとして OpenHikingMap を提供します。非商用の環境では月 500,000 タイルまで無料です。商用またはそれ以上の規模の環境では、先に openmaps.fr との合意が必要です',
|
||||
'admin.toursCycleMap.title': 'サイクリング地図(CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'ツアープランナーで自転車ツアーのベース地図として CyclOSM を提供します。OpenStreetMap France が適度な利用向けに配信しています。利用の多い、または商用のインストールでは無効にするか、事前に運営者と合意してください',
|
||||
'admin.addons.noAddons': '利用可能なアドオンはありません',
|
||||
'admin.weather.title': '天気データ',
|
||||
'admin.weather.badge': '2026年3月24日以降',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'ハイキング',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap が現在応答していません。別の地図に切り替えてください。ツアーはそのまま残ります。',
|
||||
'map.baseLayer.cycle': '自転車',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM が現在応答していません。別の地図に切り替えてください。ツアーはそのままです。',
|
||||
'map.baseLayer.switchToSatellite': '衛星表示に切り替える',
|
||||
'map.baseLayer.switchToDefault': '地図表示に切り替える',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -91,6 +91,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'ナビには登山口が表示されます。ルートに沿った案内は行いません。',
|
||||
'tours.planner.title': 'ツアーを計画',
|
||||
'tours.planner.newTour': '新しいツアーを計画',
|
||||
'tours.planner.addTour': 'ツアーを追加',
|
||||
'tours.planner.addTourHint': '新しいツアーを計画、GPX ファイルを読み込み、または別の旅行からツアーを引き継ぎます',
|
||||
'tours.planner.neutralTitle': 'ツアーを計画',
|
||||
'tours.planner.neutralBody': '新しいツアーを始めて、地図をクリックして経由地点を置きます。',
|
||||
'tours.planner.newTitle': '新しいツアー',
|
||||
@@ -106,6 +108,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': '標高グラフを展開',
|
||||
'tours.planner.name': 'ツアー名',
|
||||
'tours.planner.namePlaceholder': '名前のないハイキング',
|
||||
'tours.planner.namePlaceholder.bike': '名前のないサイクリング',
|
||||
'tours.planner.namePlaceholder.kayak': '名前のないパドルツアー',
|
||||
'tours.planner.mapSearch': '場所や住所を検索',
|
||||
'tours.planner.placePoints': 'ポイントを配置',
|
||||
'tours.planner.pointPlaced': 'ポイントを配置しました',
|
||||
@@ -167,6 +171,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': '計算待ち',
|
||||
'tours.planner.status.routing': '徒歩ルートを計算中…',
|
||||
'tours.planner.status.routingFailed': 'ルートの計算に失敗しました。直前に計算できたルートを引き続き表示しています。',
|
||||
'tours.planner.status.unreachable': 'ポイント {point} に到達できません',
|
||||
'tours.planner.unreachable.hike':
|
||||
'ポイント {point} に届く登山道がありません。ルーターが知る最も近い道は {distance} 先です。ポイントを道の上に移すか、その区間を手で描いてください。',
|
||||
'tours.planner.unreachable.bike':
|
||||
'ポイント {point} に届く自転車の通れる道がありません。ルーターが知る最も近い道は {distance} 先です。ポイントを道の上に移すか、その区間を手で描いてください。',
|
||||
'tours.planner.unreachable.show': 'ポイント {point} を表示',
|
||||
'tours.planner.status.elevation': '標高を読み込み中…',
|
||||
'tours.planner.status.elevationFailed': '標高を取得できません。計算済みのルートを引き続き表示しています。',
|
||||
'tours.planner.status.saving': '保存中…',
|
||||
@@ -333,6 +343,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': '見どころ',
|
||||
'tours.poi.cat.slipway': '入水地点',
|
||||
'tours.poi.cat.boat_rental': 'ボートレンタル',
|
||||
'tours.poi.cat.bike_repair': '自転車修理',
|
||||
'tours.planner.tourTypeHint.hike': '徒歩、設定した難易度まで',
|
||||
'tours.planner.tourTypeHint.bike': '自転車で、ツーリング、ロード、グラベル、MTB向けのルート',
|
||||
'tours.planner.tourTypeHint.kayak': '水上で、カヤック、カヌー、SUPで',
|
||||
'tours.bike.profile': 'プロファイル',
|
||||
'tours.bike.profile.touring': 'ツーリング',
|
||||
'tours.bike.profile.road': 'ロード',
|
||||
'tours.bike.profile.gravel': 'グラベル',
|
||||
'tours.bike.profile.mtb': 'MTB',
|
||||
'tours.bike.profileHint.touring': '自転車道と静かな道を優先し、できる限り舗装路を選びます。',
|
||||
'tours.bike.profileHint.road': 'アスファルトを選び、既知の未舗装区間を表示します。',
|
||||
'tours.bike.profileHint.gravel': '砂利道や農道を通り、テクニカルなシングルトラックは避けます。',
|
||||
'tours.bike.profileHint.mtb': '荒れた道もそのまま通り、記録のある箇所はトレイル難度を表示します。',
|
||||
'tours.bike.timeEstimated': '走行時間(推定)',
|
||||
'tours.bike.timeEstimatedShort': '走行時間',
|
||||
'tours.bike.timeEstimatedInfoLabel': '走行時間の算出方法',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'選んだプロファイルの速度と登りを考慮したルーターの時間です。休憩を含まない計画値です。',
|
||||
'tours.bike.ways.title': '路面と道',
|
||||
'tours.bike.ways.surface': '路面',
|
||||
'tours.bike.ways.surface.paved': 'アスファルト',
|
||||
'tours.bike.ways.surface.rough': '荒れた舗装',
|
||||
'tours.bike.ways.surface.compacted': '締め固め',
|
||||
'tours.bike.ways.surface.gravel': '砂利',
|
||||
'tours.bike.ways.surface.ground': '未舗装の土',
|
||||
'tours.bike.ways.surface.unknown': 'データなし',
|
||||
'tours.bike.ways.way': '道の種類',
|
||||
'tours.bike.ways.way.cyclePath': '自転車道',
|
||||
'tours.bike.ways.way.cycleLane': '車道沿いの自転車レーン',
|
||||
'tours.bike.ways.way.quiet': '静かな道',
|
||||
'tours.bike.ways.way.road': '交通のある道路',
|
||||
'tours.bike.ways.way.track': '農道・林道',
|
||||
'tours.bike.ways.way.path': '小道',
|
||||
'tours.bike.ways.way.ferry': 'フェリー',
|
||||
'tours.bike.ways.way.other': 'その他',
|
||||
'tours.bike.ways.loading': 'ルート沿いの道を読み込み中…',
|
||||
'tours.bike.ways.offline': '路面とトレイル難度には接続が必要です。',
|
||||
'tours.bike.ways.failed': 'ルート沿いの道を読み込めませんでした。ルート自体は変わりません。',
|
||||
'tours.bike.ways.source':
|
||||
'ルーターが読み取った OpenStreetMap のデータです。記載がない箇所の路面は不明であり、良好という意味ではありません。',
|
||||
'tours.bike.ways.showOnMap': '地図に表示',
|
||||
'tours.bike.ways.unpavedBadge': '未舗装 {share}',
|
||||
'tours.bike.ways.unpavedNotice': 'ルートのうち {length} が未舗装と分かっています。',
|
||||
'tours.bike.trail.title': 'トレイル難度(mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0、特に難しさなし',
|
||||
'tours.bike.trail.s1': 'S1、小さな障害物',
|
||||
'tours.bike.trail.s2': 'S2、木の根、段差、緩い地面',
|
||||
'tours.bike.trail.s3': 'S3、岩、急斜面、急カーブ',
|
||||
'tours.bike.trail.s4': 'S4以上、上級者向け',
|
||||
'tours.bike.trail.none': 'データに難度なし',
|
||||
'tours.bike.trail.unasked': '今回は取得できませんでした',
|
||||
'tours.bike.trail.uphillTitle': '登りの難度(mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': '登り {step}',
|
||||
'tours.bike.trail.note':
|
||||
'OpenStreetMap のマッパーが付けた難度です。難度のない道は簡単ではなく不明です。そのトレイルがあなたに合うかどうかは示しません。',
|
||||
'tours.bike.trail.loading': 'トレイル難度を読み込み中…',
|
||||
'tours.bike.trail.failed': '今回はトレイル難度を読み込めませんでした。',
|
||||
'tours.copy.action': '新しいツアーとして保存',
|
||||
'tours.copy.hint': 'このツアーを日程なしの独立した新しいツアーとしてコピーします。元のツアーはそのままです。',
|
||||
'tours.copy.name': '{name}(コピー)',
|
||||
@@ -345,6 +412,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'GPXファイルのダウンロードにはサーバーへの接続が必要です。',
|
||||
'tours.gpxExport.noRoute': 'このツアーにはエクスポートできる保存済みルートがありません。',
|
||||
'tours.gpxExport.failed': 'GPXファイルを作成できませんでした。',
|
||||
'tours.gpxExport.share': 'GPX を共有',
|
||||
'tours.gpxExport.shareShort': '共有',
|
||||
'tours.gpxExport.shareSaveFirst': 'GPX として共有するには先にツアーを保存してください',
|
||||
'tours.gpxExport.shareReady': 'GPX ファイルの準備ができました。もう一度「共有」をタップすると共有シートが開きます。',
|
||||
'tours.gpxExport.shareFailed': '共有できませんでした。GPX ファイルは引き続きダウンロードできます。',
|
||||
'tours.revision.remoteTitle': 'このツアーは別の場所で保存されました',
|
||||
'tours.revision.remoteBody': '編集中に、誰か(または別の端末のあなた)が保存しました。変更は決めるまで残ります。',
|
||||
'tours.revision.loadNewer': '新しい版を読み込む',
|
||||
@@ -373,15 +445,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'ルートの選び方の働き',
|
||||
'tours.preferences.info':
|
||||
'これらの選択はルート計算が選ぶ道の重み付けです。ほかに道がない場合は、それでもその道を通ります。道の難しさについては何も示しません。ルートが使える道は上の難易度上限で決まります。',
|
||||
'tours.preferences.avoid_climbs': '急な登りを避ける',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'あればより緩やかな道を選びます。長くなる場合もあります。迂回路がない所は急なままです。',
|
||||
'tours.preferences.avoid_steps': '階段を避ける',
|
||||
'tours.preferences.avoid_stepsHint': '回り道がない場所だけ階段を通ります。',
|
||||
'tours.preferences.avoid_ferries': 'フェリーを避ける',
|
||||
'tours.preferences.avoid_ferriesHint': '回り道があれば、長くても陸路を通ります。',
|
||||
'tours.preferences.prefer_paths': '歩道を優先',
|
||||
'tours.preferences.prefer_pathsHint': 'あれば、車道や歩道より独立した道を通ります。',
|
||||
'tours.preferences.shortest': '速さより短さ',
|
||||
'tours.preferences.shortestHint':
|
||||
'所要時間ではなく距離で道を選びます。少し短くなることが多いですが、速いとは限りません。',
|
||||
'tours.planner.tourType': 'ツアーの種類',
|
||||
'tours.paddle.boat': 'ボート',
|
||||
'tours.paddle.boat.kayak': 'カヤック',
|
||||
|
||||
@@ -674,6 +674,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': '하이킹 지도 (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'투어 플래너에서 OpenHikingMap을 기본 지도로 제공합니다. 비상업적 설치는 월 500,000 타일까지 무료이며, 상업적이거나 더 큰 설치는 먼저 openmaps.fr과의 협의가 필요합니다',
|
||||
'admin.toursCycleMap.title': '자전거 지도 (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'투어 플래너에서 자전거 투어의 기본 지도로 CyclOSM을 제공합니다. OpenStreetMap France가 적당한 사용을 위해 제공하며, 사용량이 많거나 상업적인 설치는 이를 끄거나 먼저 운영자와 합의합니다',
|
||||
'admin.addons.noAddons': '사용 가능한 애드온이 없습니다',
|
||||
'admin.weather.title': '날씨 데이터',
|
||||
'admin.weather.badge': '2026년 3월 24일부터',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': '하이킹',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap이 지금 응답하지 않습니다. 다른 지도로 전환하세요. 투어는 그대로 유지됩니다.',
|
||||
'map.baseLayer.cycle': '자전거',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM이 지금 응답하지 않습니다. 다른 지도로 바꾸세요. 투어는 그대로입니다.',
|
||||
'map.baseLayer.switchToSatellite': '위성 보기로 전환',
|
||||
'map.baseLayer.switchToDefault': '지도 보기로 전환',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -88,6 +88,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': '길찾기는 탐방로 입구를 목적지로 엽니다. 투어 경로를 따라 안내하지는 않습니다.',
|
||||
'tours.planner.title': '투어 계획',
|
||||
'tours.planner.newTour': '새 투어 계획',
|
||||
'tours.planner.addTour': '투어 추가',
|
||||
'tours.planner.addTourHint': '새 투어를 계획하거나 GPX 파일을 가져오거나 다른 여행의 투어를 가져옵니다',
|
||||
'tours.planner.neutralTitle': '투어 계획',
|
||||
'tours.planner.neutralBody': '새 투어를 시작하고 지도를 클릭해 경유 지점을 놓으세요.',
|
||||
'tours.planner.newTitle': '새 투어',
|
||||
@@ -104,6 +106,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': '고도 그래프 펼치기',
|
||||
'tours.planner.name': '투어 이름',
|
||||
'tours.planner.namePlaceholder': '이름 없는 하이킹',
|
||||
'tours.planner.namePlaceholder.bike': '이름 없는 자전거 투어',
|
||||
'tours.planner.namePlaceholder.kayak': '이름 없는 패들 투어',
|
||||
'tours.planner.mapSearch': '장소 또는 주소 검색',
|
||||
'tours.planner.placePoints': '지점 찍기',
|
||||
'tours.planner.pointPlaced': '지점을 찍었습니다',
|
||||
@@ -165,6 +169,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': '계산 대기 중',
|
||||
'tours.planner.status.routing': '도보 경로 계산 중…',
|
||||
'tours.planner.status.routingFailed': '경로 계산에 실패했습니다. 마지막으로 계산에 성공한 경로를 계속 표시합니다.',
|
||||
'tours.planner.status.unreachable': '지점 {point}에 도달할 수 없습니다',
|
||||
'tours.planner.unreachable.hike':
|
||||
'지점 {point}에 닿는 등산로가 없습니다. 라우터가 아는 가장 가까운 길은 {distance} 떨어져 있습니다. 지점을 길 위로 옮기거나 그 구간을 직접 그리세요.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'지점 {point}에 닿는 자전거 길이 없습니다. 라우터가 아는 가장 가까운 길은 {distance} 떨어져 있습니다. 지점을 길 위로 옮기거나 그 구간을 직접 그리세요.',
|
||||
'tours.planner.unreachable.show': '지점 {point} 보기',
|
||||
'tours.planner.status.elevation': '고도 불러오는 중…',
|
||||
'tours.planner.status.elevationFailed': '고도를 불러올 수 없습니다. 계산된 경로를 계속 표시합니다.',
|
||||
'tours.planner.status.saving': '저장 중…',
|
||||
@@ -330,6 +340,62 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': '볼거리',
|
||||
'tours.poi.cat.slipway': '입수 지점',
|
||||
'tours.poi.cat.boat_rental': '보트 대여',
|
||||
'tours.poi.cat.bike_repair': '자전거 수리',
|
||||
'tours.planner.tourTypeHint.hike': '도보로, 설정한 난이도 안에서',
|
||||
'tours.planner.tourTypeHint.bike': '자전거로, 투어링, 로드, 그래블, MTB에 맞춘 경로',
|
||||
'tours.planner.tourTypeHint.kayak': '물 위에서, 카약, 카누 또는 SUP로',
|
||||
'tours.bike.profile': '프로필',
|
||||
'tours.bike.profile.touring': '투어링',
|
||||
'tours.bike.profile.road': '로드',
|
||||
'tours.bike.profile.gravel': '그래블',
|
||||
'tours.bike.profile.mtb': 'MTB',
|
||||
'tours.bike.profileHint.touring': '자전거도로와 한적한 도로를 우선하며, 가능하면 포장도로를 택합니다.',
|
||||
'tours.bike.profileHint.road': '아스팔트를 따르며, 알려진 비포장 구간을 표시합니다.',
|
||||
'tours.bike.profileHint.gravel': '자갈길과 농로를 따르고, 기술적인 싱글트랙은 피합니다.',
|
||||
'tours.bike.profileHint.mtb': '거친 길도 그대로 지나며, 기록된 곳에는 트레일 난이도를 표시합니다.',
|
||||
'tours.bike.timeEstimated': '주행 시간(추정)',
|
||||
'tours.bike.timeEstimatedShort': '주행 시간',
|
||||
'tours.bike.timeEstimatedInfoLabel': '주행 시간 계산 방식',
|
||||
'tours.bike.timeEstimatedInfo': '선택한 프로필의 속도와 오르막을 반영한 라우터 시간입니다. 휴식을 뺀 계획 값입니다.',
|
||||
'tours.bike.ways.title': '노면과 길',
|
||||
'tours.bike.ways.surface': '노면',
|
||||
'tours.bike.ways.surface.paved': '아스팔트',
|
||||
'tours.bike.ways.surface.rough': '거친 포장',
|
||||
'tours.bike.ways.surface.compacted': '다짐길',
|
||||
'tours.bike.ways.surface.gravel': '자갈',
|
||||
'tours.bike.ways.surface.ground': '자연 노면',
|
||||
'tours.bike.ways.surface.unknown': '데이터 없음',
|
||||
'tours.bike.ways.way': '길의 종류',
|
||||
'tours.bike.ways.way.cyclePath': '자전거도로',
|
||||
'tours.bike.ways.way.cycleLane': '차도 옆 자전거 차선',
|
||||
'tours.bike.ways.way.quiet': '한적한 도로',
|
||||
'tours.bike.ways.way.road': '차량 통행 도로',
|
||||
'tours.bike.ways.way.track': '농로·임도',
|
||||
'tours.bike.ways.way.path': '오솔길',
|
||||
'tours.bike.ways.way.ferry': '페리',
|
||||
'tours.bike.ways.way.other': '기타',
|
||||
'tours.bike.ways.loading': '경로의 길 정보를 읽는 중…',
|
||||
'tours.bike.ways.offline': '노면과 트레일 난이도에는 연결이 필요합니다.',
|
||||
'tours.bike.ways.failed': '경로의 길 정보를 읽지 못했습니다. 경로 자체는 그대로입니다.',
|
||||
'tours.bike.ways.source':
|
||||
'라우터가 읽은 OpenStreetMap 데이터입니다. 정보가 없는 곳의 노면은 좋은 것이 아니라 알 수 없는 것입니다.',
|
||||
'tours.bike.ways.showOnMap': '지도에 표시',
|
||||
'tours.bike.ways.unpavedBadge': '비포장 {share}',
|
||||
'tours.bike.ways.unpavedNotice': '경로 중 {length}가 비포장으로 알려져 있습니다.',
|
||||
'tours.bike.trail.title': '트레일 난이도 (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, 특별한 어려움 없음',
|
||||
'tours.bike.trail.s1': 'S1, 작은 장애물',
|
||||
'tours.bike.trail.s2': 'S2, 뿌리, 계단, 무른 노면',
|
||||
'tours.bike.trail.s3': 'S3, 바위, 급경사, 급커브',
|
||||
'tours.bike.trail.s4': 'S4 이상, 전문가 지형',
|
||||
'tours.bike.trail.none': '데이터에 난이도 없음',
|
||||
'tours.bike.trail.unasked': '이번에는 조회할 수 없었음',
|
||||
'tours.bike.trail.uphillTitle': '오르막 난이도 (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': '오르막 {step}',
|
||||
'tours.bike.trail.note':
|
||||
'OpenStreetMap 매퍼가 매긴 난이도입니다. 난이도가 없는 길은 쉬운 것이 아니라 알 수 없는 것입니다. 그 트레일이 당신에게 맞는지는 알려 주지 않습니다.',
|
||||
'tours.bike.trail.loading': '트레일 난이도를 읽는 중…',
|
||||
'tours.bike.trail.failed': '이번에는 트레일 난이도를 읽지 못했습니다.',
|
||||
'tours.copy.action': '새 투어로 저장',
|
||||
'tours.copy.hint': '이 투어를 날짜 없이 독립된 새 투어로 복사합니다. 원본은 그대로입니다.',
|
||||
'tours.copy.name': '{name} (사본)',
|
||||
@@ -342,6 +408,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'GPX 파일을 다운로드하려면 서버 연결이 필요합니다.',
|
||||
'tours.gpxExport.noRoute': '이 투어에는 내보낼 저장된 경로가 없습니다.',
|
||||
'tours.gpxExport.failed': 'GPX 파일을 만들지 못했습니다.',
|
||||
'tours.gpxExport.share': 'GPX 공유',
|
||||
'tours.gpxExport.shareShort': '공유',
|
||||
'tours.gpxExport.shareSaveFirst': 'GPX로 공유하려면 먼저 투어를 저장하세요',
|
||||
'tours.gpxExport.shareReady': 'GPX 파일이 준비되었습니다. 공유 창을 열려면 공유를 한 번 더 누르세요.',
|
||||
'tours.gpxExport.shareFailed': '공유하지 못했습니다. GPX 파일은 계속 다운로드할 수 있습니다.',
|
||||
'tours.revision.remoteTitle': '이 투어가 다른 곳에서 저장되었습니다',
|
||||
'tours.revision.remoteBody':
|
||||
'편집하는 동안 누군가 또는 다른 기기의 내가 저장했습니다. 결정할 때까지 변경 내용은 그대로 남습니다.',
|
||||
@@ -371,15 +442,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': '경로 선택이 하는 일',
|
||||
'tours.preferences.info':
|
||||
'이 선택은 경로 계산이 어떤 길을 고를지에 가중치를 줍니다. 다른 길이 없으면 경로는 그래도 그 길로 갑니다. 길의 난이도에 대해서는 아무것도 말하지 않으며, 경로가 쓸 수 있는 길은 위의 난이도 제한이 정합니다.',
|
||||
'tours.preferences.avoid_climbs': '가파른 오르막 피하기',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'더 완만한 길이 있으면 그 길로, 더 길어지더라도 택합니다. 돌아갈 길이 없으면 가파른 채로 남습니다.',
|
||||
'tours.preferences.avoid_steps': '계단 피하기',
|
||||
'tours.preferences.avoid_stepsHint': '돌아갈 길이 없을 때만 계단을 지납니다.',
|
||||
'tours.preferences.avoid_ferries': '페리 피하기',
|
||||
'tours.preferences.avoid_ferriesHint': '돌아가는 길이 있으면 더 길어도 육로로 갑니다.',
|
||||
'tours.preferences.prefer_paths': '보행로 우선',
|
||||
'tours.preferences.prefer_pathsHint': '있다면 도로와 인도 대신 별도의 길로 갑니다.',
|
||||
'tours.preferences.shortest': '빠르기보다 짧게',
|
||||
'tours.preferences.shortestHint':
|
||||
'걸리는 시간이 아니라 길이로 길을 고릅니다. 대개 조금 짧지만 항상 빠르지는 않습니다.',
|
||||
'tours.planner.tourType': '투어 유형',
|
||||
'tours.paddle.boat': '보트',
|
||||
'tours.paddle.boat.kayak': '카약',
|
||||
|
||||
@@ -668,6 +668,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Wandelkaart (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Biedt OpenHikingMap aan als basiskaart in de tochtplanner. Gratis voor niet-commerciële installaties tot 500.000 tegels per maand; een commerciële of grotere installatie heeft eerst een afspraak met openmaps.fr nodig',
|
||||
'admin.toursCycleMap.title': 'Fietskaart (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Biedt CyclOSM aan als basiskaart voor fietstochten in de tourplanner. OpenStreetMap France levert hem voor gematigd gebruik; een intensief gebruikte of commerciële installatie zet hem uit of maakt eerst afspraken met de beheerder',
|
||||
'admin.addons.noAddons': 'Geen add-ons beschikbaar',
|
||||
'admin.weather.title': 'Weergegevens',
|
||||
'admin.weather.badge': 'Sinds 24 maart 2026',
|
||||
|
||||
@@ -21,6 +21,9 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Wandelen',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap reageert op dit moment niet. Schakel over naar een andere kaart; je tocht blijft zoals hij is.',
|
||||
'map.baseLayer.cycle': 'Fiets',
|
||||
'map.baseLayer.cycleDown':
|
||||
'CyclOSM reageert nu niet. Schakel over naar een andere kaart; je tocht blijft zoals hij is.',
|
||||
'map.baseLayer.switchToSatellite': 'Overschakelen naar satellietweergave',
|
||||
'map.baseLayer.switchToDefault': 'Overschakelen naar kaartweergave',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -97,6 +97,9 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'De navigatie opent het startpunt van het pad; ze begeleidt je niet langs de route.',
|
||||
'tours.planner.title': 'Een tocht plannen',
|
||||
'tours.planner.newTour': 'Nieuwe tocht plannen',
|
||||
'tours.planner.addTour': 'Tocht toevoegen',
|
||||
'tours.planner.addTourHint':
|
||||
'Een nieuwe tocht plannen, GPX-bestanden importeren of een tocht uit een andere reis overnemen',
|
||||
'tours.planner.neutralTitle': 'Een tocht plannen',
|
||||
'tours.planner.neutralBody': 'Begin een nieuwe tocht en klik op de kaart om de punten te plaatsen.',
|
||||
'tours.planner.newTitle': 'Nieuwe tocht',
|
||||
@@ -113,6 +116,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Hoogteprofiel uitklappen',
|
||||
'tours.planner.name': 'Naam van de tocht',
|
||||
'tours.planner.namePlaceholder': 'Naamloze wandeling',
|
||||
'tours.planner.namePlaceholder.bike': 'Fietstocht zonder naam',
|
||||
'tours.planner.namePlaceholder.kayak': 'Peddeltocht zonder naam',
|
||||
'tours.planner.mapSearch': 'Zoek plaats of adres',
|
||||
'tours.planner.placePoints': 'Punten plaatsen',
|
||||
'tours.planner.pointPlaced': 'Punt geplaatst',
|
||||
@@ -175,6 +180,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Wachten op berekening',
|
||||
'tours.planner.status.routing': 'Wandelroute berekenen…',
|
||||
'tours.planner.status.routingFailed': 'Routeberekening mislukt. De laatste geldige route blijft zichtbaar.',
|
||||
'tours.planner.status.unreachable': 'Punt {point} is niet bereikbaar',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Geen wandelpad bereikt punt {point}: het dichtstbijzijnde dat de router kent ligt {distance} verderop. Zet het punt op een pad of teken het stuk ernaartoe met de hand.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Geen fietsbare weg bereikt punt {point}: de dichtstbijzijnde die de router kent ligt {distance} verderop. Zet het punt op een weg of teken het stuk ernaartoe met de hand.',
|
||||
'tours.planner.unreachable.show': 'Punt {point} tonen',
|
||||
'tours.planner.status.elevation': 'Hoogtegegevens laden…',
|
||||
'tours.planner.status.elevationFailed': 'Hoogtegegevens zijn niet beschikbaar. De berekende route blijft zichtbaar.',
|
||||
'tours.planner.status.saving': 'Opslaan…',
|
||||
@@ -347,6 +358,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Bezienswaardigheden',
|
||||
'tours.poi.cat.slipway': 'Instapplaatsen',
|
||||
'tours.poi.cat.boat_rental': 'Bootverhuur',
|
||||
'tours.poi.cat.bike_repair': 'Fietsreparatie',
|
||||
'tours.planner.tourTypeHint.hike': 'Te voet, binnen een moeilijkheid die je kiest',
|
||||
'tours.planner.tourTypeHint.bike': 'Met de fiets, gerouteerd voor toer, race, gravel of MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Op het water, met kajak, kano of SUP',
|
||||
'tours.bike.profile': 'Profiel',
|
||||
'tours.bike.profile.touring': 'Toer',
|
||||
'tours.bike.profile.road': 'Racefiets',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Kiest bij voorkeur fietspaden en rustige wegen, verhard waar het kan.',
|
||||
'tours.bike.profileHint.road': 'Blijft op asfalt; bekende onverharde stukken worden getoond.',
|
||||
'tours.bike.profileHint.gravel': 'Neemt grind en landwegen, geen technische singletracks.',
|
||||
'tours.bike.profileHint.mtb': 'Neemt ruwe paden zoals ze komen; trailgraden worden getoond waar ze in kaart staan.',
|
||||
'tours.bike.timeEstimated': 'Fietstijd (geschat)',
|
||||
'tours.bike.timeEstimatedShort': 'Fietstijd',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Hoe de fietstijd wordt berekend',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'De tijd van de router voor het gekozen profiel, met de snelheid en de klimmen meegerekend. Een planningswaarde, zonder pauzes.',
|
||||
'tours.bike.ways.title': 'Ondergrond en wegen',
|
||||
'tours.bike.ways.surface': 'Ondergrond',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Ruwe bestrating',
|
||||
'tours.bike.ways.surface.compacted': 'Halfverhard',
|
||||
'tours.bike.ways.surface.gravel': 'Grind',
|
||||
'tours.bike.ways.surface.ground': 'Natuurlijke ondergrond',
|
||||
'tours.bike.ways.surface.unknown': 'Niet in de gegevens',
|
||||
'tours.bike.ways.way': 'Soort weg',
|
||||
'tours.bike.ways.way.cyclePath': 'Fietspad',
|
||||
'tours.bike.ways.way.cycleLane': 'Fietsstrook naast het verkeer',
|
||||
'tours.bike.ways.way.quiet': 'Rustige straat',
|
||||
'tours.bike.ways.way.road': 'Weg met verkeer',
|
||||
'tours.bike.ways.way.track': 'Land- of bosweg',
|
||||
'tours.bike.ways.way.path': 'Pad',
|
||||
'tours.bike.ways.way.ferry': 'Veerpont',
|
||||
'tours.bike.ways.way.other': 'Overig',
|
||||
'tours.bike.ways.loading': 'Wegen langs de route worden gelezen…',
|
||||
'tours.bike.ways.offline': 'Ondergrond en trailgraden hebben een verbinding nodig.',
|
||||
'tours.bike.ways.failed': 'De wegen langs de route konden niet worden gelezen. De route zelf blijft hetzelfde.',
|
||||
'tours.bike.ways.source':
|
||||
'Uit OpenStreetMap, zoals de router het leest. Waar de gegevens niets zeggen, is de ondergrond onbekend, niet goed.',
|
||||
'tours.bike.ways.showOnMap': 'Op de kaart tonen',
|
||||
'tours.bike.ways.unpavedBadge': '{share} onverhard',
|
||||
'tours.bike.ways.unpavedNotice': '{length} van de route is bekend onverhard.',
|
||||
'tours.bike.trail.title': 'Trailgraad (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, geen bijzondere moeilijkheden',
|
||||
'tours.bike.trail.s1': 'S1, kleine obstakels',
|
||||
'tours.bike.trail.s2': 'S2, wortels, treden, losse grond',
|
||||
'tours.bike.trail.s3': 'S3, rotsblokken, steil, krappe bochten',
|
||||
'tours.bike.trail.s4': 'S4 en hoger, terrein voor experts',
|
||||
'tours.bike.trail.none': 'Geen graad in de gegevens',
|
||||
'tours.bike.trail.unasked': 'Kon deze keer niet worden opgevraagd',
|
||||
'tours.bike.trail.uphillTitle': 'Klimgraad (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Klim {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Graden zoals OpenStreetMap-mappers ze gaven. Een weg zonder graad is onbekend, niet makkelijk. Ze zeggen niets over of een trail bij je past.',
|
||||
'tours.bike.trail.loading': 'Trailgraden worden gelezen…',
|
||||
'tours.bike.trail.failed': 'De trailgraden konden deze keer niet worden gelezen.',
|
||||
'tours.copy.action': 'Opslaan als nieuwe tocht',
|
||||
'tours.copy.hint':
|
||||
'Kopieert deze tocht als nieuwe, zelfstandige tocht zonder zijn dagen. Het origineel blijft zoals het is.',
|
||||
@@ -360,6 +428,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Voor het downloaden van een GPX-bestand is een verbinding met de server nodig.',
|
||||
'tours.gpxExport.noRoute': 'Deze tocht heeft geen opgeslagen route om te exporteren.',
|
||||
'tours.gpxExport.failed': 'Het GPX-bestand kon niet worden gemaakt.',
|
||||
'tours.gpxExport.share': 'GPX delen',
|
||||
'tours.gpxExport.shareShort': 'Delen',
|
||||
'tours.gpxExport.shareSaveFirst': 'Sla de tocht eerst op om hem als GPX te delen',
|
||||
'tours.gpxExport.shareReady': 'Het GPX-bestand staat klaar. Tik nog een keer op Delen om het deelmenu te openen.',
|
||||
'tours.gpxExport.shareFailed': 'Delen is niet gelukt. Het GPX-bestand kan nog steeds worden gedownload.',
|
||||
'tours.revision.remoteTitle': 'Deze tocht is elders opgeslagen',
|
||||
'tours.revision.remoteBody':
|
||||
'Iemand, of jij op een ander apparaat, heeft hem opgeslagen terwijl je bewerkte. Je wijzigingen blijven tot je beslist.',
|
||||
@@ -390,14 +463,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Wat de routekeuze doet',
|
||||
'tours.preferences.info':
|
||||
'Deze keuzes wegen welke weg de routeplanner kiest. Waar geen andere weg is, gaat de route er toch langs. Ze zeggen niets over hoe moeilijk een pad is; de moeilijkheidsgrens hierboven bepaalt wat de route mag gebruiken.',
|
||||
'tours.preferences.avoid_climbs': 'Steile hellingen vermijden',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Vlakkere wegen waar die er zijn, ook als ze langer zijn. Waar geen omweg is, blijft het steil.',
|
||||
'tours.preferences.avoid_steps': 'Trappen vermijden',
|
||||
'tours.preferences.avoid_stepsHint': 'Trappen alleen waar er geen weg omheen is.',
|
||||
'tours.preferences.avoid_ferries': 'Veerponten vermijden',
|
||||
'tours.preferences.avoid_ferriesHint': 'Over land waar er een weg omheen is, ook als die langer is.',
|
||||
'tours.preferences.prefer_paths': 'Voetpaden voorkeur geven',
|
||||
'tours.preferences.prefer_pathsHint': 'Eigen paden in plaats van wegen en stoepen, waar ze er zijn.',
|
||||
'tours.preferences.shortest': 'Korter in plaats van sneller',
|
||||
'tours.preferences.shortestHint': 'De weg gekozen op lengte, niet op tijd; vaak iets korter, niet altijd sneller.',
|
||||
'tours.planner.tourType': 'Soort tocht',
|
||||
'tours.paddle.boat': 'Boot',
|
||||
'tours.paddle.boat.kayak': 'Kajak',
|
||||
|
||||
@@ -688,6 +688,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Mapa turystyczna (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Udostępnia OpenHikingMap jako mapę bazową w planerze tras. Bezpłatnie dla niekomercyjnych instalacji do 500 000 kafelków miesięcznie; instalacja komercyjna lub większa wymaga najpierw porozumienia z openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Mapa rowerowa (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Udostępnia CyclOSM jako mapę bazową dla wycieczek rowerowych w planerze. OpenStreetMap France udostępnia ją do umiarkowanego użytku; instalacja intensywnie używana lub komercyjna wyłącza ją albo najpierw zawiera porozumienie z operatorem',
|
||||
'admin.addons.noAddons': 'Brak dostępnych dodatków',
|
||||
'admin.weather.title': 'Dane pogodowe',
|
||||
'admin.weather.badge': 'Od 24 marca 2026',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Turystyka',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap w tej chwili nie odpowiada. Przełącz na inną mapę; twoja trasa pozostaje bez zmian.',
|
||||
'map.baseLayer.cycle': 'Rower',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM teraz nie odpowiada. Przełącz na inną mapę; twoja trasa pozostaje bez zmian.',
|
||||
'map.baseLayer.switchToSatellite': 'Przełącz na widok satelitarny',
|
||||
'map.baseLayer.switchToDefault': 'Przełącz na widok mapy',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -104,6 +104,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Nawigacja otwiera punkt początkowy szlaku; nie prowadzi wzdłuż trasy.',
|
||||
'tours.planner.title': 'Zaplanuj wycieczkę',
|
||||
'tours.planner.newTour': 'Zaplanuj nową wycieczkę',
|
||||
'tours.planner.addTour': 'Dodaj trasę',
|
||||
'tours.planner.addTourHint': 'Zaplanuj nową trasę, zaimportuj pliki GPX lub przejmij trasę z innej podróży',
|
||||
'tours.planner.neutralTitle': 'Zaplanuj wycieczkę',
|
||||
'tours.planner.neutralBody': 'Rozpocznij nową trasę i kliknij mapę, aby wyznaczyć jej punkty.',
|
||||
'tours.planner.newTitle': 'Nowa wycieczka',
|
||||
@@ -120,6 +122,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Rozwiń profil wysokościowy',
|
||||
'tours.planner.name': 'Nazwa wycieczki',
|
||||
'tours.planner.namePlaceholder': 'Wędrówka bez nazwy',
|
||||
'tours.planner.namePlaceholder.bike': 'Wycieczka rowerowa bez nazwy',
|
||||
'tours.planner.namePlaceholder.kayak': 'Spływ bez nazwy',
|
||||
'tours.planner.mapSearch': 'Szukaj miejsca lub adresu',
|
||||
'tours.planner.placePoints': 'Ustawiaj punkty',
|
||||
'tours.planner.pointPlaced': 'Punkt ustawiony',
|
||||
@@ -184,6 +188,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Oczekiwanie na obliczenie',
|
||||
'tours.planner.status.routing': 'Obliczanie trasy pieszej…',
|
||||
'tours.planner.status.routingFailed': 'Nie udało się obliczyć trasy. Nadal widoczna jest ostatnia poprawna trasa.',
|
||||
'tours.planner.status.unreachable': 'Punkt {point} jest nieosiągalny',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Żadna ścieżka piesza nie dochodzi do punktu {point}: najbliższa, którą zna router, jest {distance} dalej. Przesuń punkt na ścieżkę albo narysuj odcinek do niego ręcznie.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Żadna droga dla rowerów nie dochodzi do punktu {point}: najbliższa, którą zna router, jest {distance} dalej. Przesuń punkt na drogę albo narysuj odcinek do niego ręcznie.',
|
||||
'tours.planner.unreachable.show': 'Pokaż punkt {point}',
|
||||
'tours.planner.status.elevation': 'Wczytywanie wysokości…',
|
||||
'tours.planner.status.elevationFailed': 'Dane wysokościowe są niedostępne. Obliczona trasa nadal jest widoczna.',
|
||||
'tours.planner.status.saving': 'Zapisywanie…',
|
||||
@@ -353,6 +363,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Atrakcje',
|
||||
'tours.poi.cat.slipway': 'Miejsca wodowania',
|
||||
'tours.poi.cat.boat_rental': 'Wypożyczalnia łodzi',
|
||||
'tours.poi.cat.bike_repair': 'Naprawa rowerów',
|
||||
'tours.planner.tourTypeHint.hike': 'Pieszo, w granicach wybranej trudności',
|
||||
'tours.planner.tourTypeHint.bike': 'Na rowerze, trasa dla turystyki, szosy, gravela lub MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Na wodzie, kajakiem, kanadyjką lub SUP-em',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Turystyka',
|
||||
'tours.bike.profile.road': 'Szosa',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Preferuje drogi rowerowe i spokojne ulice, utwardzone, gdzie się da.',
|
||||
'tours.bike.profileHint.road': 'Trzyma się asfaltu; znane nieutwardzone odcinki są pokazane.',
|
||||
'tours.bike.profileHint.gravel': 'Wybiera szuter i drogi polne, nie techniczne single.',
|
||||
'tours.bike.profileHint.mtb': 'Jedzie trudnymi ścieżkami, jakie są; stopnie trudności widać tam, gdzie są zmapowane.',
|
||||
'tours.bike.timeEstimated': 'Czas jazdy (szacowany)',
|
||||
'tours.bike.timeEstimatedShort': 'Czas jazdy',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Jak liczony jest czas jazdy',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Czas routera dla wybranego profilu, z jego prędkością i podjazdami. Wartość planistyczna, bez przerw.',
|
||||
'tours.bike.ways.title': 'Nawierzchnia i drogi',
|
||||
'tours.bike.ways.surface': 'Nawierzchnia',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Nierówna nawierzchnia',
|
||||
'tours.bike.ways.surface.compacted': 'Utwardzona',
|
||||
'tours.bike.ways.surface.gravel': 'Szuter',
|
||||
'tours.bike.ways.surface.ground': 'Grunt naturalny',
|
||||
'tours.bike.ways.surface.unknown': 'Brak w danych',
|
||||
'tours.bike.ways.way': 'Rodzaj drogi',
|
||||
'tours.bike.ways.way.cyclePath': 'Droga rowerowa',
|
||||
'tours.bike.ways.way.cycleLane': 'Pas rowerowy przy ruchu',
|
||||
'tours.bike.ways.way.quiet': 'Spokojna ulica',
|
||||
'tours.bike.ways.way.road': 'Droga z ruchem',
|
||||
'tours.bike.ways.way.track': 'Droga polna lub leśna',
|
||||
'tours.bike.ways.way.path': 'Ścieżka',
|
||||
'tours.bike.ways.way.ferry': 'Prom',
|
||||
'tours.bike.ways.way.other': 'Inne',
|
||||
'tours.bike.ways.loading': 'Odczytywanie dróg wzdłuż trasy…',
|
||||
'tours.bike.ways.offline': 'Nawierzchnia i stopnie trudności wymagają połączenia.',
|
||||
'tours.bike.ways.failed': 'Nie udało się odczytać dróg wzdłuż trasy. Sama trasa się nie zmienia.',
|
||||
'tours.bike.ways.source':
|
||||
'Z OpenStreetMap, tak jak czyta to router. Tam, gdzie dane milczą, nawierzchnia jest nieznana, a nie dobra.',
|
||||
'tours.bike.ways.showOnMap': 'Pokaż na mapie',
|
||||
'tours.bike.ways.unpavedBadge': '{share} nieutwardzone',
|
||||
'tours.bike.ways.unpavedNotice': '{length} trasy to znane odcinki bez utwardzenia.',
|
||||
'tours.bike.trail.title': 'Stopień trudności (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, bez szczególnych trudności',
|
||||
'tours.bike.trail.s1': 'S1, małe przeszkody',
|
||||
'tours.bike.trail.s2': 'S2, korzenie, stopnie, luźne podłoże',
|
||||
'tours.bike.trail.s3': 'S3, bloki skalne, stromo, ciasne zakręty',
|
||||
'tours.bike.trail.s4': 'S4 i więcej, teren dla ekspertów',
|
||||
'tours.bike.trail.none': 'Brak stopnia w danych',
|
||||
'tours.bike.trail.unasked': 'Tym razem nie udało się sprawdzić',
|
||||
'tours.bike.trail.uphillTitle': 'Trudność podjazdu (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Podjazd {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Stopnie nadane przez maperów OpenStreetMap. Droga bez stopnia jest nieznana, a nie łatwa. Nie mówią, czy szlak jest dla ciebie odpowiedni.',
|
||||
'tours.bike.trail.loading': 'Odczytywanie stopni trudności…',
|
||||
'tours.bike.trail.failed': 'Tym razem nie udało się odczytać stopni trudności.',
|
||||
'tours.copy.action': 'Zapisz jako nową trasę',
|
||||
'tours.copy.hint': 'Kopiuje tę trasę jako nową, niezależną trasę bez jej dni. Oryginał zostaje bez zmian.',
|
||||
'tours.copy.name': '{name} (kopia)',
|
||||
@@ -365,6 +432,12 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Pobranie pliku GPX wymaga połączenia z serwerem.',
|
||||
'tours.gpxExport.noRoute': 'Ta trasa nie ma zapisanego przebiegu do eksportu.',
|
||||
'tours.gpxExport.failed': 'Nie udało się utworzyć pliku GPX.',
|
||||
'tours.gpxExport.share': 'Udostępnij GPX',
|
||||
'tours.gpxExport.shareShort': 'Udostępnij',
|
||||
'tours.gpxExport.shareSaveFirst': 'Najpierw zapisz trasę, aby udostępnić ją jako GPX',
|
||||
'tours.gpxExport.shareReady':
|
||||
'Plik GPX jest gotowy. Stuknij jeszcze raz Udostępnij, aby otworzyć okno udostępniania.',
|
||||
'tours.gpxExport.shareFailed': 'Udostępnianie nie powiodło się. Plik GPX nadal można pobrać.',
|
||||
'tours.revision.remoteTitle': 'Ta trasa została zapisana gdzie indziej',
|
||||
'tours.revision.remoteBody':
|
||||
'Ktoś, albo ty na innym urządzeniu, zapisał ją podczas twojej edycji. Twoje zmiany zostają, dopóki nie zdecydujesz.',
|
||||
@@ -395,15 +468,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Co robi wybór trasy',
|
||||
'tours.preferences.info':
|
||||
'Te ustawienia ważą, którą drogę wybiera wyznaczanie trasy. Gdzie nie ma innej drogi, trasa i tak tamtędy prowadzi. Nie mówią nic o trudności ścieżki; to, czego trasa może użyć, ustala limit trudności powyżej.',
|
||||
'tours.preferences.avoid_climbs': 'Unikaj stromych podejść',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Łagodniejsze drogi tam, gdzie są, nawet jeśli dłuższe. Gdzie nie ma objazdu, zostaje stromo.',
|
||||
'tours.preferences.avoid_steps': 'Unikaj schodów',
|
||||
'tours.preferences.avoid_stepsHint': 'Schody tylko tam, gdzie nie da się ich obejść.',
|
||||
'tours.preferences.avoid_ferries': 'Unikaj promów',
|
||||
'tours.preferences.avoid_ferriesHint': 'Lądem, gdzie da się obejść, nawet jeśli dłużej.',
|
||||
'tours.preferences.prefer_paths': 'Preferuj ścieżki piesze',
|
||||
'tours.preferences.prefer_pathsHint': 'Osobne ścieżki zamiast dróg i chodników, gdzie są.',
|
||||
'tours.preferences.shortest': 'Krócej zamiast szybciej',
|
||||
'tours.preferences.shortestHint':
|
||||
'Droga wybrana według długości, nie czasu; często trochę krótsza, nie zawsze szybsza.',
|
||||
'tours.planner.tourType': 'Rodzaj wycieczki',
|
||||
'tours.paddle.boat': 'Łódź',
|
||||
'tours.paddle.boat.kayak': 'Kajak',
|
||||
|
||||
@@ -693,6 +693,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Туристическая карта (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Предлагает OpenHikingMap как базовую карту в планировщике маршрутов. Бесплатно для некоммерческих установок до 500 000 тайлов в месяц; коммерческой или более крупной установке сначала нужно соглашение с openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Велокарта (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Предлагает CyclOSM как базовую карту для велопоездок в планировщике. OpenStreetMap France предоставляет её для умеренного использования; нагруженная или коммерческая установка отключает её или сначала договаривается с оператором',
|
||||
'admin.addons.noAddons': 'Нет доступных дополнений',
|
||||
'admin.weather.title': 'Данные о погоде',
|
||||
'admin.weather.badge': 'С 24 марта 2026',
|
||||
|
||||
@@ -21,6 +21,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.hiking': 'Походы',
|
||||
'map.baseLayer.hikingDown':
|
||||
'OpenHikingMap сейчас не отвечает. Переключитесь на другую карту, ваш маршрут останется без изменений.',
|
||||
'map.baseLayer.cycle': 'Вело',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM сейчас не отвечает. Переключись на другую карту, маршрут останется прежним.',
|
||||
'map.baseLayer.switchToSatellite': 'Переключить на спутниковый вид',
|
||||
'map.baseLayer.switchToDefault': 'Переключить на вид карты',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -104,6 +104,9 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Навигация открывает начальную точку тропы, но не ведёт по маршруту.',
|
||||
'tours.planner.title': 'Спланировать тур',
|
||||
'tours.planner.newTour': 'Спланировать новый тур',
|
||||
'tours.planner.addTour': 'Добавить маршрут',
|
||||
'tours.planner.addTourHint':
|
||||
'Спланировать новый маршрут, импортировать файлы GPX или перенести маршрут из другой поездки',
|
||||
'tours.planner.neutralTitle': 'Спланировать тур',
|
||||
'tours.planner.neutralBody': 'Начните новый маршрут и нажимайте на карту, чтобы расставить точки.',
|
||||
'tours.planner.newTitle': 'Новый тур',
|
||||
@@ -120,6 +123,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Развернуть профиль высот',
|
||||
'tours.planner.name': 'Название тура',
|
||||
'tours.planner.namePlaceholder': 'Поход без названия',
|
||||
'tours.planner.namePlaceholder.bike': 'Велопоездка без названия',
|
||||
'tours.planner.namePlaceholder.kayak': 'Сплав без названия',
|
||||
'tours.planner.mapSearch': 'Поиск места или адреса',
|
||||
'tours.planner.placePoints': 'Ставить точки',
|
||||
'tours.planner.pointPlaced': 'Точка поставлена',
|
||||
@@ -186,6 +191,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.routing': 'Расчёт пешего маршрута…',
|
||||
'tours.planner.status.routingFailed':
|
||||
'Не удалось рассчитать маршрут. По-прежнему отображается последний успешно рассчитанный маршрут.',
|
||||
'tours.planner.status.unreachable': 'Точка {point} недостижима',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Ни одна пешеходная тропа не доходит до точки {point}: ближайшая известная маршрутизатору в {distance}. Перенеси точку на тропу или нарисуй участок к ней вручную.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Ни одна дорога для велосипедов не доходит до точки {point}: ближайшая известная маршрутизатору в {distance}. Перенеси точку на дорогу или нарисуй участок к ней вручную.',
|
||||
'tours.planner.unreachable.show': 'Показать точку {point}',
|
||||
'tours.planner.status.elevation': 'Загрузка высот…',
|
||||
'tours.planner.status.elevationFailed': 'Данные о высоте недоступны. Рассчитанный маршрут по-прежнему отображается.',
|
||||
'tours.planner.status.saving': 'Сохранение…',
|
||||
@@ -357,6 +368,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Достопримечательности',
|
||||
'tours.poi.cat.slipway': 'Места спуска на воду',
|
||||
'tours.poi.cat.boat_rental': 'Прокат лодок',
|
||||
'tours.poi.cat.bike_repair': 'Ремонт велосипедов',
|
||||
'tours.planner.tourTypeHint.hike': 'Пешком, в пределах выбранной сложности',
|
||||
'tours.planner.tourTypeHint.bike': 'На велосипеде, маршрут для туризма, шоссе, гравела или МТБ',
|
||||
'tours.planner.tourTypeHint.kayak': 'По воде, на каяке, каноэ или SUP',
|
||||
'tours.bike.profile': 'Профиль',
|
||||
'tours.bike.profile.touring': 'Туризм',
|
||||
'tours.bike.profile.road': 'Шоссе',
|
||||
'tours.bike.profile.gravel': 'Гравел',
|
||||
'tours.bike.profile.mtb': 'МТБ',
|
||||
'tours.bike.profileHint.touring': 'Предпочитает велодорожки и тихие улицы, по возможности с покрытием.',
|
||||
'tours.bike.profileHint.road': 'Держится асфальта; известные участки без покрытия показаны.',
|
||||
'tours.bike.profileHint.gravel': 'Едет по гравию и полевым дорогам, не по техничным синглтрекам.',
|
||||
'tours.bike.profileHint.mtb': 'Едет по грубым тропам как есть; сложность троп показана там, где она размечена.',
|
||||
'tours.bike.timeEstimated': 'Время в пути (оценка)',
|
||||
'tours.bike.timeEstimatedShort': 'Время в пути',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Как считается время в пути',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Время маршрутизатора для выбранного профиля с его скоростью и подъёмами. Плановое значение без перерывов.',
|
||||
'tours.bike.ways.title': 'Покрытие и дороги',
|
||||
'tours.bike.ways.surface': 'Покрытие',
|
||||
'tours.bike.ways.surface.paved': 'Асфальт',
|
||||
'tours.bike.ways.surface.rough': 'Грубое покрытие',
|
||||
'tours.bike.ways.surface.compacted': 'Уплотнённое',
|
||||
'tours.bike.ways.surface.gravel': 'Гравий',
|
||||
'tours.bike.ways.surface.ground': 'Грунт',
|
||||
'tours.bike.ways.surface.unknown': 'Нет в данных',
|
||||
'tours.bike.ways.way': 'Тип дороги',
|
||||
'tours.bike.ways.way.cyclePath': 'Велодорожка',
|
||||
'tours.bike.ways.way.cycleLane': 'Велополоса рядом с движением',
|
||||
'tours.bike.ways.way.quiet': 'Тихая улица',
|
||||
'tours.bike.ways.way.road': 'Дорога с движением',
|
||||
'tours.bike.ways.way.track': 'Полевая или лесная дорога',
|
||||
'tours.bike.ways.way.path': 'Тропа',
|
||||
'tours.bike.ways.way.ferry': 'Паром',
|
||||
'tours.bike.ways.way.other': 'Другое',
|
||||
'tours.bike.ways.loading': 'Читаем дороги вдоль маршрута…',
|
||||
'tours.bike.ways.offline': 'Для покрытия и сложности троп нужно подключение.',
|
||||
'tours.bike.ways.failed': 'Не удалось прочитать дороги вдоль маршрута. Сам маршрут не меняется.',
|
||||
'tours.bike.ways.source':
|
||||
'Из OpenStreetMap, как их читает маршрутизатор. Где данных нет, покрытие неизвестно, а не хорошее.',
|
||||
'tours.bike.ways.showOnMap': 'Показать на карте',
|
||||
'tours.bike.ways.unpavedBadge': '{share} без покрытия',
|
||||
'tours.bike.ways.unpavedNotice': 'Известно, что {length} маршрута без покрытия.',
|
||||
'tours.bike.trail.title': 'Сложность тропы (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, без особых трудностей',
|
||||
'tours.bike.trail.s1': 'S1, небольшие препятствия',
|
||||
'tours.bike.trail.s2': 'S2, корни, ступени, сыпучий грунт',
|
||||
'tours.bike.trail.s3': 'S3, камни, круто, узкие повороты',
|
||||
'tours.bike.trail.s4': 'S4 и выше, рельеф для экспертов',
|
||||
'tours.bike.trail.none': 'Нет сложности в данных',
|
||||
'tours.bike.trail.unasked': 'На этот раз не удалось запросить',
|
||||
'tours.bike.trail.uphillTitle': 'Сложность подъёма (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Подъём {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Сложность, как её указали участники OpenStreetMap. Дорога без оценки неизвестна, а не проста. Это ничего не говорит о том, подходит ли тебе трейл.',
|
||||
'tours.bike.trail.loading': 'Читаем сложность троп…',
|
||||
'tours.bike.trail.failed': 'На этот раз не удалось прочитать сложность троп.',
|
||||
'tours.copy.action': 'Сохранить как новый поход',
|
||||
'tours.copy.hint': 'Копирует этот поход как новый, самостоятельный поход без его дней. Оригинал остаётся как есть.',
|
||||
'tours.copy.name': '{name} (копия)',
|
||||
@@ -369,6 +437,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Для скачивания файла GPX нужно соединение с сервером.',
|
||||
'tours.gpxExport.noRoute': 'У этого похода нет сохранённого маршрута для экспорта.',
|
||||
'tours.gpxExport.failed': 'Не удалось создать файл GPX.',
|
||||
'tours.gpxExport.share': 'Поделиться GPX',
|
||||
'tours.gpxExport.shareShort': 'Поделиться',
|
||||
'tours.gpxExport.shareSaveFirst': 'Сначала сохрани маршрут, чтобы поделиться им в GPX',
|
||||
'tours.gpxExport.shareReady': 'Файл GPX готов. Нажми «Поделиться» ещё раз, чтобы открыть меню отправки.',
|
||||
'tours.gpxExport.shareFailed': 'Не удалось поделиться. Файл GPX по-прежнему можно скачать.',
|
||||
'tours.revision.remoteTitle': 'Этот поход сохранили в другом месте',
|
||||
'tours.revision.remoteBody':
|
||||
'Кто-то, или вы на другом устройстве, сохранил его во время правки. Ваши изменения останутся, пока вы не решите.',
|
||||
@@ -398,15 +471,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Что делает выбор маршрута',
|
||||
'tours.preferences.info':
|
||||
'Эти настройки влияют на то, какой путь выбирает маршрутизатор. Где другого пути нет, маршрут всё равно идёт там. О сложности тропы они ничего не говорят; что может использовать маршрут, задаёт предел сложности выше.',
|
||||
'tours.preferences.avoid_climbs': 'Избегать крутых подъёмов',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Более пологие пути, где они есть, даже если длиннее. Где обхода нет, остаётся круто.',
|
||||
'tours.preferences.avoid_steps': 'Избегать лестниц',
|
||||
'tours.preferences.avoid_stepsHint': 'Лестницы только там, где их не обойти.',
|
||||
'tours.preferences.avoid_ferries': 'Избегать паромов',
|
||||
'tours.preferences.avoid_ferriesHint': 'По суше, где есть обход, даже если он длиннее.',
|
||||
'tours.preferences.prefer_paths': 'Предпочитать пешеходные тропы',
|
||||
'tours.preferences.prefer_pathsHint': 'Отдельные тропы вместо дорог и тротуаров, где они есть.',
|
||||
'tours.preferences.shortest': 'Короче, а не быстрее',
|
||||
'tours.preferences.shortestHint':
|
||||
'Путь выбирается по длине, а не по времени; часто чуть короче, но не всегда быстрее.',
|
||||
'tours.planner.tourType': 'Тип тура',
|
||||
'tours.paddle.boat': 'Лодка',
|
||||
'tours.paddle.boat.kayak': 'Каяк',
|
||||
|
||||
@@ -740,6 +740,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Turistická mapa (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Ponúka OpenHikingMap ako podkladovú mapu v plánovači túr. Zadarmo pre nekomerčné inštalácie do 500 000 dlaždíc mesačne; komerčná alebo väčšia inštalácia potrebuje najprv dohodu s openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Cyklomapa (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Ponúka CyclOSM ako podkladovú mapu pre cyklotúry v plánovači. OpenStreetMap France ju poskytuje na primerané používanie; silne využívaná alebo komerčná inštalácia ju vypne, alebo sa najprv dohodne s prevádzkovateľom',
|
||||
'admin.addons.noAddons': 'Žiadne doplnky nie sú dostupné',
|
||||
'admin.weather.title': 'Dáta o počasí',
|
||||
'admin.weather.badge': 'Od 24. marca 2026',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Satelit',
|
||||
'map.baseLayer.hiking': 'Turistika',
|
||||
'map.baseLayer.hikingDown': 'OpenHikingMap teraz neodpovedá. Prepni na inú mapu, tvoja túra zostane bez zmeny.',
|
||||
'map.baseLayer.cycle': 'Cyklo',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM teraz neodpovedá. Prepni na inú mapu, tvoja túra zostáva bez zmeny.',
|
||||
'map.baseLayer.switchToSatellite': 'Prepnúť na satelitné zobrazenie',
|
||||
'map.baseLayer.switchToDefault': 'Prepnúť na mapové zobrazenie',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -98,6 +98,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Navigácia otvorí začiatok túry; nevedie po trase.',
|
||||
'tours.planner.title': 'Naplánovať túru',
|
||||
'tours.planner.newTour': 'Naplánovať novú túru',
|
||||
'tours.planner.addTour': 'Pridať túru',
|
||||
'tours.planner.addTourHint': 'Naplánuj novú túru, importuj súbory GPX alebo prevezmi túru z inej cesty',
|
||||
'tours.planner.neutralTitle': 'Naplánovať túru',
|
||||
'tours.planner.neutralBody': 'Začnite novú túru a kliknutím do mapy umiestnite jej body.',
|
||||
'tours.planner.newTitle': 'Nová túra',
|
||||
@@ -113,6 +115,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Rozbaliť výškový profil',
|
||||
'tours.planner.name': 'Názov túry',
|
||||
'tours.planner.namePlaceholder': 'Pešia túra bez názvu',
|
||||
'tours.planner.namePlaceholder.bike': 'Cyklotúra bez názvu',
|
||||
'tours.planner.namePlaceholder.kayak': 'Vodácky výlet bez názvu',
|
||||
'tours.planner.mapSearch': 'Hľadať miesto alebo adresu',
|
||||
'tours.planner.placePoints': 'Pridávať body',
|
||||
'tours.planner.pointPlaced': 'Bod pridaný',
|
||||
@@ -176,6 +180,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Čaká na výpočet',
|
||||
'tours.planner.status.routing': 'Vypočítava sa pešia trasa…',
|
||||
'tours.planner.status.routingFailed': 'Výpočet trasy zlyhal. Naďalej sa zobrazuje posledná úspešne vypočítaná trasa.',
|
||||
'tours.planner.status.unreachable': 'Bod {point} je nedosiahnuteľný',
|
||||
'tours.planner.unreachable.hike':
|
||||
'K bodu {point} nevedie žiadna turistická cesta: najbližšia, ktorú router pozná, je {distance} ďaleko. Presuň bod na cestu alebo úsek k nemu nakresli ručne.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'K bodu {point} nevedie žiadna cesta pre bicykle: najbližšia, ktorú router pozná, je {distance} ďaleko. Presuň bod na cestu alebo úsek k nemu nakresli ručne.',
|
||||
'tours.planner.unreachable.show': 'Ukázať bod {point}',
|
||||
'tours.planner.status.elevation': 'Načítavajú sa výškové údaje…',
|
||||
'tours.planner.status.elevationFailed': 'Výškové údaje nie sú dostupné. Vypočítaná trasa sa naďalej zobrazuje.',
|
||||
'tours.planner.status.saving': 'Ukladá sa…',
|
||||
@@ -340,6 +350,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Pamätihodnosti',
|
||||
'tours.poi.cat.slipway': 'Nástupné miesta',
|
||||
'tours.poi.cat.boat_rental': 'Požičovňa lodí',
|
||||
'tours.poi.cat.bike_repair': 'Oprava bicyklov',
|
||||
'tours.planner.tourTypeHint.hike': 'Pešo, do obtiažnosti, ktorú nastavíš',
|
||||
'tours.planner.tourTypeHint.bike': 'Na bicykli, trasa pre trekking, cestu, gravel alebo MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'Na vode, na kajaku, kanoe alebo SUP',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Trekking',
|
||||
'tours.bike.profile.road': 'Cestný',
|
||||
'tours.bike.profile.gravel': 'Gravel', // same-as-en
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Uprednostňuje cyklotrasy a tiché cesty, kde sa dá, spevnené.',
|
||||
'tours.bike.profileHint.road': 'Drží sa asfaltu; známe nespevnené úseky sa zobrazia.',
|
||||
'tours.bike.profileHint.gravel': 'Ide po štrku a poľných cestách, nie po technických singletrailoch.',
|
||||
'tours.bike.profileHint.mtb': 'Ide po drsných cestách, ako prídu; obtiažnosť trailov sa ukáže, kde je zmapovaná.',
|
||||
'tours.bike.timeEstimated': 'Čas jazdy (odhad)',
|
||||
'tours.bike.timeEstimatedShort': 'Čas jazdy',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Ako sa počíta čas jazdy',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Čas routera pre zvolený profil, s jeho rýchlosťou a stúpaniami. Plánovacia hodnota bez prestávok.',
|
||||
'tours.bike.ways.title': 'Povrch a cesty',
|
||||
'tours.bike.ways.surface': 'Povrch',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Hrubá dlažba',
|
||||
'tours.bike.ways.surface.compacted': 'Zhutnený',
|
||||
'tours.bike.ways.surface.gravel': 'Štrk',
|
||||
'tours.bike.ways.surface.ground': 'Prírodný povrch',
|
||||
'tours.bike.ways.surface.unknown': 'Nie je v dátach',
|
||||
'tours.bike.ways.way': 'Druh cesty',
|
||||
'tours.bike.ways.way.cyclePath': 'Cyklotrasa',
|
||||
'tours.bike.ways.way.cycleLane': 'Cyklopruh vedľa premávky',
|
||||
'tours.bike.ways.way.quiet': 'Tichá ulica',
|
||||
'tours.bike.ways.way.road': 'Cesta s premávkou',
|
||||
'tours.bike.ways.way.track': 'Poľná alebo lesná cesta',
|
||||
'tours.bike.ways.way.path': 'Chodník',
|
||||
'tours.bike.ways.way.ferry': 'Kompa',
|
||||
'tours.bike.ways.way.other': 'Ostatné',
|
||||
'tours.bike.ways.loading': 'Načítavajú sa cesty pozdĺž trasy…',
|
||||
'tours.bike.ways.offline': 'Povrch a obtiažnosť trailov potrebujú pripojenie.',
|
||||
'tours.bike.ways.failed': 'Cesty pozdĺž trasy sa nepodarilo načítať. Samotná trasa zostáva bez zmeny.',
|
||||
'tours.bike.ways.source':
|
||||
'Z OpenStreetMap, ako ich číta router. Kde dáta nič nehovoria, je povrch neznámy, nie dobrý.',
|
||||
'tours.bike.ways.showOnMap': 'Ukázať na mape',
|
||||
'tours.bike.ways.unpavedBadge': '{share} nespevnené',
|
||||
'tours.bike.ways.unpavedNotice': '{length} trasy je podľa dát nespevnených.',
|
||||
'tours.bike.trail.title': 'Obtiažnosť trailu (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, bez zvláštnych ťažkostí',
|
||||
'tours.bike.trail.s1': 'S1, malé prekážky',
|
||||
'tours.bike.trail.s2': 'S2, korene, schody, sypký povrch',
|
||||
'tours.bike.trail.s3': 'S3, bloky, strmé, úzke zákruty',
|
||||
'tours.bike.trail.s4': 'S4 a viac, terén pre expertov',
|
||||
'tours.bike.trail.none': 'Bez stupňa v dátach',
|
||||
'tours.bike.trail.unasked': 'Tentoraz sa nedalo zistiť',
|
||||
'tours.bike.trail.uphillTitle': 'Obtiažnosť do kopca (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Do kopca {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Stupne, ako ich zadali mapovači OpenStreetMap. Cesta bez stupňa je neznáma, nie ľahká. Nehovoria nič o tom, či sa trail hodí pre teba.',
|
||||
'tours.bike.trail.loading': 'Načítava sa obtiažnosť trailov…',
|
||||
'tours.bike.trail.failed': 'Obtiažnosť trailov sa tentoraz nepodarilo načítať.',
|
||||
'tours.copy.action': 'Uložiť ako novú túru',
|
||||
'tours.copy.hint': 'Skopíruje túto túru ako novú, samostatnú túru bez jej dní. Originál zostane, ako je.',
|
||||
'tours.copy.name': '{name} (kópia)',
|
||||
@@ -352,6 +419,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'Na stiahnutie súboru GPX je potrebné pripojenie k serveru.',
|
||||
'tours.gpxExport.noRoute': 'Táto túra nemá uloženú trasu na export.',
|
||||
'tours.gpxExport.failed': 'Súbor GPX sa nepodarilo vytvoriť.',
|
||||
'tours.gpxExport.share': 'Zdieľať GPX',
|
||||
'tours.gpxExport.shareShort': 'Zdieľať',
|
||||
'tours.gpxExport.shareSaveFirst': 'Najprv túru ulož, potom ju môžeš zdieľať ako GPX',
|
||||
'tours.gpxExport.shareReady': 'Súbor GPX je pripravený. Ťukni ešte raz na Zdieľať a otvorí sa ponuka zdieľania.',
|
||||
'tours.gpxExport.shareFailed': 'Zdieľanie sa nepodarilo. Súbor GPX si môžeš ďalej stiahnuť.',
|
||||
'tours.revision.remoteTitle': 'Táto túra bola uložená inde',
|
||||
'tours.revision.remoteBody':
|
||||
'Niekto, alebo ty na inom zariadení, ju uložil, kým ju upravuješ. Tvoje zmeny zostanú, kým sa nerozhodneš.',
|
||||
@@ -382,15 +454,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Čo robí voľba trasy',
|
||||
'tours.preferences.info':
|
||||
'Táto voľba ovplyvňuje, ktorú cestu plánovač vyberie. Kde iná cesta nie je, vedie trasa tadiaľ aj tak. O náročnosti cesty nehovorí nič; čo smie trasa použiť, určuje limit náročnosti vyššie.',
|
||||
'tours.preferences.avoid_climbs': 'Vyhnúť sa prudkým stúpaniam',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Miernejšie cesty, kde existujú, aj keď sú dlhšie. Kde sa nedá obísť, zostáva to strmé.',
|
||||
'tours.preferences.avoid_steps': 'Vyhnúť sa schodom',
|
||||
'tours.preferences.avoid_stepsHint': 'Schody len tam, kde nie je cesta okolo.',
|
||||
'tours.preferences.avoid_ferries': 'Vyhnúť sa prievozom',
|
||||
'tours.preferences.avoid_ferriesHint': 'Po súši, kde vedie cesta okolo, aj keď je dlhšia.',
|
||||
'tours.preferences.prefer_paths': 'Uprednostniť chodníky',
|
||||
'tours.preferences.prefer_pathsHint': 'Samostatné cesty namiesto ciest a chodníkov, kde sú.',
|
||||
'tours.preferences.shortest': 'Kratšie namiesto rýchlejšie',
|
||||
'tours.preferences.shortestHint':
|
||||
'Cesta vybraná podľa dĺžky, nie podľa času; často o niečo kratšia, nie vždy rýchlejšia.',
|
||||
'tours.planner.tourType': 'Typ túry',
|
||||
'tours.paddle.boat': 'Loď',
|
||||
'tours.paddle.boat.kayak': 'Kajak',
|
||||
|
||||
@@ -735,6 +735,9 @@ const admin: TranslationStrings = {
|
||||
'admin.toursHikingMap.title': 'Vandringskarta (OpenHikingMap)',
|
||||
'admin.toursHikingMap.subtitle':
|
||||
'Erbjuder OpenHikingMap som baskarta i turplaneraren. Gratis för icke-kommersiella installationer upp till 500 000 rutor i månaden; en kommersiell eller större installation behöver först ett avtal med openmaps.fr',
|
||||
'admin.toursCycleMap.title': 'Cykelkarta (CyclOSM)',
|
||||
'admin.toursCycleMap.subtitle':
|
||||
'Erbjuder CyclOSM som baskarta för cykelturer i turplaneraren. OpenStreetMap France tillhandahåller den för måttlig användning; en tungt använd eller kommersiell installation stänger av den eller kommer först överens med operatören',
|
||||
'admin.addons.noAddons': 'Inga tillägg tillgängliga',
|
||||
'admin.weather.title': 'Väderdata',
|
||||
'admin.weather.badge': 'Sedan den 24 mars 2026',
|
||||
|
||||
@@ -20,6 +20,8 @@ const map: TranslationStrings = {
|
||||
'map.baseLayer.satellite': 'Satellit',
|
||||
'map.baseLayer.hiking': 'Vandring',
|
||||
'map.baseLayer.hikingDown': 'OpenHikingMap svarar inte just nu. Byt till en annan karta; din tur förblir som den är.',
|
||||
'map.baseLayer.cycle': 'Cykel',
|
||||
'map.baseLayer.cycleDown': 'CyclOSM svarar inte just nu. Byt till en annan karta; din tur förblir som den är.',
|
||||
'map.baseLayer.switchToSatellite': 'Byt till satellitvy',
|
||||
'map.baseLayer.switchToDefault': 'Byt till kartvy',
|
||||
'map.location.denied':
|
||||
|
||||
@@ -94,6 +94,8 @@ const tours: TranslationStrings = {
|
||||
'tours.detail.navigationHint': 'Navigeringen öppnar ledens startpunkt; den vägleder inte längs rutten.',
|
||||
'tours.planner.title': 'Planera en tur',
|
||||
'tours.planner.newTour': 'Planera ny tur',
|
||||
'tours.planner.addTour': 'Lägg till tur',
|
||||
'tours.planner.addTourHint': 'Planera en ny tur, importera GPX-filer eller ta över en tur från en annan resa',
|
||||
'tours.planner.neutralTitle': 'Planera en tur',
|
||||
'tours.planner.neutralBody': 'Starta en ny tur och klicka på kartan för att placera dess punkter.',
|
||||
'tours.planner.newTitle': 'Ny tur',
|
||||
@@ -109,6 +111,8 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.expandElevation': 'Fäll ut höjdprofil',
|
||||
'tours.planner.name': 'Turens namn',
|
||||
'tours.planner.namePlaceholder': 'Vandring utan namn',
|
||||
'tours.planner.namePlaceholder.bike': 'Cykeltur utan namn',
|
||||
'tours.planner.namePlaceholder.kayak': 'Paddeltur utan namn',
|
||||
'tours.planner.mapSearch': 'Sök plats eller adress',
|
||||
'tours.planner.placePoints': 'Sätt punkter',
|
||||
'tours.planner.pointPlaced': 'Punkt satt',
|
||||
@@ -171,6 +175,12 @@ const tours: TranslationStrings = {
|
||||
'tours.planner.status.dirty': 'Väntar på beräkning',
|
||||
'tours.planner.status.routing': 'Beräknar vandringsrutt…',
|
||||
'tours.planner.status.routingFailed': 'Ruttberäkningen misslyckades. Den senaste giltiga rutten visas fortfarande.',
|
||||
'tours.planner.status.unreachable': 'Punkt {point} kan inte nås',
|
||||
'tours.planner.unreachable.hike':
|
||||
'Ingen vandringsled når punkt {point}: den närmaste routern känner till ligger {distance} bort. Flytta punkten till en stig eller rita sträckan dit för hand.',
|
||||
'tours.planner.unreachable.bike':
|
||||
'Ingen väg för cyklar når punkt {point}: den närmaste routern känner till ligger {distance} bort. Flytta punkten till en väg eller rita sträckan dit för hand.',
|
||||
'tours.planner.unreachable.show': 'Visa punkt {point}',
|
||||
'tours.planner.status.elevation': 'Läser in höjddata…',
|
||||
'tours.planner.status.elevationFailed': 'Höjddata är inte tillgängliga. Den beräknade rutten visas fortfarande.',
|
||||
'tours.planner.status.saving': 'Sparar…',
|
||||
@@ -338,6 +348,63 @@ const tours: TranslationStrings = {
|
||||
'tours.poi.cat.sights': 'Sevärdheter',
|
||||
'tours.poi.cat.slipway': 'Isättningsplatser',
|
||||
'tours.poi.cat.boat_rental': 'Båtuthyrning',
|
||||
'tours.poi.cat.bike_repair': 'Cykelreparation',
|
||||
'tours.planner.tourTypeHint.hike': 'Till fots, inom en svårighet du väljer',
|
||||
'tours.planner.tourTypeHint.bike': 'Med cykel, rutt för tur, landsväg, grus eller MTB',
|
||||
'tours.planner.tourTypeHint.kayak': 'På vattnet, med kajak, kanot eller SUP',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Tur',
|
||||
'tours.bike.profile.road': 'Landsväg',
|
||||
'tours.bike.profile.gravel': 'Grus',
|
||||
'tours.bike.profile.mtb': 'MTB', // same-as-en
|
||||
'tours.bike.profileHint.touring': 'Föredrar cykelvägar och lugna gator, belagda där det går.',
|
||||
'tours.bike.profileHint.road': 'Håller sig till asfalt; kända grusavsnitt visas.',
|
||||
'tours.bike.profileHint.gravel': 'Tar grus och skogsvägar, inte tekniska stigar.',
|
||||
'tours.bike.profileHint.mtb': 'Tar grova stigar som de kommer; svårighetsgrader visas där de är karterade.',
|
||||
'tours.bike.timeEstimated': 'Cykeltid (uppskattad)',
|
||||
'tours.bike.timeEstimatedShort': 'Cykeltid',
|
||||
'tours.bike.timeEstimatedInfoLabel': 'Hur cykeltiden räknas fram',
|
||||
'tours.bike.timeEstimatedInfo':
|
||||
'Routerns tid för valt profil, med dess fart och stigningar. Ett planeringsvärde, utan pauser.',
|
||||
'tours.bike.ways.title': 'Underlag och vägar',
|
||||
'tours.bike.ways.surface': 'Underlag',
|
||||
'tours.bike.ways.surface.paved': 'Asfalt',
|
||||
'tours.bike.ways.surface.rough': 'Ojämn beläggning',
|
||||
'tours.bike.ways.surface.compacted': 'Packat grus',
|
||||
'tours.bike.ways.surface.gravel': 'Grus',
|
||||
'tours.bike.ways.surface.ground': 'Naturmark',
|
||||
'tours.bike.ways.surface.unknown': 'Saknas i datan',
|
||||
'tours.bike.ways.way': 'Vägtyp',
|
||||
'tours.bike.ways.way.cyclePath': 'Cykelväg',
|
||||
'tours.bike.ways.way.cycleLane': 'Cykelfält intill trafiken',
|
||||
'tours.bike.ways.way.quiet': 'Lugn gata',
|
||||
'tours.bike.ways.way.road': 'Väg med trafik',
|
||||
'tours.bike.ways.way.track': 'Skogs- eller jordbruksväg',
|
||||
'tours.bike.ways.way.path': 'Stig',
|
||||
'tours.bike.ways.way.ferry': 'Färja',
|
||||
'tours.bike.ways.way.other': 'Övrigt',
|
||||
'tours.bike.ways.loading': 'Läser vägarna längs rutten…',
|
||||
'tours.bike.ways.offline': 'Underlag och svårighetsgrader kräver anslutning.',
|
||||
'tours.bike.ways.failed': 'Vägarna längs rutten kunde inte läsas. Själva rutten påverkas inte.',
|
||||
'tours.bike.ways.source':
|
||||
'Från OpenStreetMap, som routern läser det. Där datan inte säger något är underlaget okänt, inte bra.',
|
||||
'tours.bike.ways.showOnMap': 'Visa på kartan',
|
||||
'tours.bike.ways.unpavedBadge': '{share} grus',
|
||||
'tours.bike.ways.unpavedNotice': '{length} av rutten är känt grus.',
|
||||
'tours.bike.trail.title': 'Svårighetsgrad (mtb:scale)',
|
||||
'tours.bike.trail.s0': 'S0, inga särskilda svårigheter',
|
||||
'tours.bike.trail.s1': 'S1, små hinder',
|
||||
'tours.bike.trail.s2': 'S2, rötter, trappsteg, löst underlag',
|
||||
'tours.bike.trail.s3': 'S3, block, brant, snäva kurvor',
|
||||
'tours.bike.trail.s4': 'S4 och uppåt, terräng för experter',
|
||||
'tours.bike.trail.none': 'Ingen grad i datan',
|
||||
'tours.bike.trail.unasked': 'Kunde inte hämtas den här gången',
|
||||
'tours.bike.trail.uphillTitle': 'Uppförsgrad (mtb:scale:uphill)',
|
||||
'tours.bike.trail.uphillStep': 'Uppför {step}',
|
||||
'tours.bike.trail.note':
|
||||
'Grader som OpenStreetMaps kartläggare satt dem. En väg utan grad är okänd, inte lätt. De säger inget om huruvida en stig passar dig.',
|
||||
'tours.bike.trail.loading': 'Läser svårighetsgraderna…',
|
||||
'tours.bike.trail.failed': 'Svårighetsgraderna kunde inte läsas den här gången.',
|
||||
'tours.copy.action': 'Spara som ny tur',
|
||||
'tours.copy.hint': 'Kopierar turen som en ny, fristående tur utan dess dagar. Originalet förblir som det är.',
|
||||
'tours.copy.name': '{name} (kopia)',
|
||||
@@ -350,6 +417,11 @@ const tours: TranslationStrings = {
|
||||
'tours.gpxExport.offline': 'För att ladda ner en GPX-fil behövs en anslutning till servern.',
|
||||
'tours.gpxExport.noRoute': 'Den här turen har ingen sparad rutt att exportera.',
|
||||
'tours.gpxExport.failed': 'GPX-filen kunde inte skapas.',
|
||||
'tours.gpxExport.share': 'Dela GPX',
|
||||
'tours.gpxExport.shareShort': 'Dela',
|
||||
'tours.gpxExport.shareSaveFirst': 'Spara turen först för att dela den som GPX',
|
||||
'tours.gpxExport.shareReady': 'GPX-filen är klar. Tryck på Dela en gång till för att öppna delningsmenyn.',
|
||||
'tours.gpxExport.shareFailed': 'Delningen fungerade inte. GPX-filen kan fortfarande laddas ner.',
|
||||
'tours.revision.remoteTitle': 'Den här turen sparades någon annanstans',
|
||||
'tours.revision.remoteBody':
|
||||
'Någon, eller du på en annan enhet, sparade den medan du redigerade. Dina ändringar finns kvar tills du bestämmer dig.',
|
||||
@@ -380,14 +452,13 @@ const tours: TranslationStrings = {
|
||||
'tours.preferences.infoLabel': 'Vad ruttvalet gör',
|
||||
'tours.preferences.info':
|
||||
'De här valen väger vilken väg ruttplaneraren väljer. Där det inte finns någon annan väg går rutten ändå där. De säger inget om hur svår en stig är; svårighetsgränsen ovanför bestämmer vad rutten får använda.',
|
||||
'tours.preferences.avoid_climbs': 'Undvik branta stigningar',
|
||||
'tours.preferences.avoid_climbsHint':
|
||||
'Flackare vägar där sådana finns, även när de är längre. Där det inte finns någon väg runt förblir det brant.',
|
||||
'tours.preferences.avoid_steps': 'Undvik trappor',
|
||||
'tours.preferences.avoid_stepsHint': 'Trappor bara där det inte finns någon väg runt.',
|
||||
'tours.preferences.avoid_ferries': 'Undvik färjor',
|
||||
'tours.preferences.avoid_ferriesHint': 'Över land där det finns en väg runt, även om den är längre.',
|
||||
'tours.preferences.prefer_paths': 'Föredra gångvägar',
|
||||
'tours.preferences.prefer_pathsHint': 'Egna gångvägar i stället för vägar och trottoarer, där de finns.',
|
||||
'tours.preferences.shortest': 'Kortare i stället för snabbare',
|
||||
'tours.preferences.shortestHint': 'Vägen väljs efter längd, inte efter tid; ofta lite kortare, inte alltid snabbare.',
|
||||
'tours.planner.tourType': 'Turtyp',
|
||||
'tours.paddle.boat': 'Båt',
|
||||
'tours.paddle.boat.kayak': 'Kajak',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user