fix(tours): read and write a run's pace in the user's distance unit

The pace was always min/km and the run's time sum forced kilometres, so
with distances in miles the run card disagreed with every other figure in
the planner. The pace is still kept per km, but is typed, shown and
checked per mile when distances are in miles, and the time sum reads in
that unit too.
This commit is contained in:
Maurice
2026-10-20 16:51:34 +02:00
parent 2a40b884a1
commit a249c861e6
36 changed files with 206 additions and 78 deletions
@@ -204,7 +204,7 @@ export function TourDayRowPills({ tour, compact = false }: { tour: TourListItem;
const walking = times.walkingMinutes == null ? null : formatPlannedTourDuration(times.walkingMinutes);
const total = plannedTotalText(times, t);
const staysText = times.walk ? walkStaysText(times.walk, t) : null;
const pace = runPaceText(tour, t);
const pace = runPaceText(tour, t, unit);
const timeParts = [
walking ? `${movingTimeLabel(tour, t('tours.planner.inspector.duration'), t)}: ${walking}` : null,
// RUN-01: the pace the running time is planned at.
@@ -275,7 +275,7 @@ export default function TourDayRowFacts({
movingTimeLabel(tour, t('tours.planner.inspector.duration'), t),
'trip-plan-tour-walking-time'
);
const pace = runPaceText(tour, t);
const pace = runPaceText(tour, t, unit);
if (pace) add(Gauge, pace, t('tours.run.pace'), 'plan-tour-run-pace');
if (times.breakMinutes != null && times.breakMinutes > 0)
add(
@@ -1,6 +1,7 @@
import type { CollectionTourItem } from '@trek/shared';
import { AlertTriangle, CopyPlus, Gauge, Moon, Route, Trash2, Waypoints, WifiOff } from 'lucide-react';
import { useTranslation } from '../../../i18n';
import { useSettingsStore } from '../../../store/settingsStore';
import { WhiteBadge } from '../../Planner/planParts';
import ConfirmDialog from '../../shared/ConfirmDialog';
import { fs } from '../../shared/DialogShell';
@@ -24,7 +25,8 @@ function sourceText(tour: CollectionTourItem, t: (key: string, params?: Record<s
*/
function KeptTourTypeBadges({ tour }: { tour: CollectionTourItem }) {
const { t } = useTranslation();
const pace = runPaceText(tour, t);
const unit = useSettingsStore((state) => state.settings.distance_unit) ?? 'metric';
const pace = runPaceText(tour, t, unit);
const stages = tour.stage_count ? t('tours.stages.count', { count: tour.stage_count }) : null;
const icon = 'flex-none text-content-faint';
return (
@@ -1,10 +1,12 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { useState, type ComponentProps } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { resetAllStores, seedStore } from '../../../../tests/helpers/store';
import { useSettingsStore } from '../../../store/settingsStore';
import { RunFields, RunProfileField } from './RunFields';
import { runPaceInvalid } from './runPace';
// FE-TOUR-RUN-FIELDS-001 to FE-TOUR-RUN-FIELDS-008
// FE-TOUR-RUN-FIELDS-001 to FE-TOUR-RUN-FIELDS-009
type Planner = ComponentProps<typeof RunFields>['planner'];
@@ -42,6 +44,8 @@ function Stateful({
const paceField = () => screen.getByRole('textbox');
afterEach(() => resetAllStores());
describe('RunFields (RUN-01)', () => {
it('FE-TOUR-RUN-FIELDS-001: offers the profile on purpose, with what it asks the router for', () => {
const onChange = vi.fn();
@@ -142,10 +146,31 @@ describe('RunFields (RUN-01)', () => {
it('FE-TOUR-RUN-FIELDS-008: a trail run is timed as any run, distance times pace, nothing added for the terrain (RUN-02)', () => {
render(<Stateful initial={450} profile="trail" />);
const sum = screen.getByTestId('tour-run-time-sum');
expect(sum).toHaveTextContent('7:30 /km');
expect(sum).toHaveTextContent('7:30 tours.run.paceUnit');
expect(sum).toHaveTextContent('1 h');
expect(screen.getByRole('button', { name: 'tours.run.paceInfoLabel' })).toHaveAccessibleDescription(
'tours.run.paceInfo'
);
});
it('FE-TOUR-RUN-FIELDS-009: with distances in miles the pace is typed and read per mile, kept per km, and the sum in miles', () => {
seedStore(useSettingsStore, { settings: { distance_unit: 'imperial' } });
render(<Stateful />);
expect(screen.getByText('tours.run.paceUnitMi')).toBeInTheDocument();
fireEvent.focus(paceField());
// 9:39 a mile is 6:00 a kilometre, the way it is kept.
fireEvent.change(paceField(), { target: { value: '9:39' } });
expect(screen.getByTestId('pace')).toHaveTextContent('360');
fireEvent.blur(paceField());
expect(paceField()).toHaveValue('9:39');
const sum = screen.getByTestId('tour-run-time-sum');
expect(sum).toHaveTextContent('5 mi');
expect(sum).not.toHaveTextContent('km');
expect(sum).toHaveTextContent('9:39 tours.run.paceUnitMi');
expect(sum).toHaveTextContent('48 min');
fireEvent.focus(paceField());
fireEvent.change(paceField(), { target: { value: '1:30' } });
fireEvent.blur(paceField());
expect(screen.getByRole('alert')).toHaveTextContent('tours.run.paceInvalidMi');
});
});
+29 -19
View File
@@ -7,6 +7,8 @@ import {
import { Clock, Gauge, Info, Ruler } from 'lucide-react';
import { useState } from 'react';
import { useTranslation } from '../../../i18n';
import { useSettingsStore } from '../../../store/settingsStore';
import type { DistanceUnit } from '../../../types';
import { formatDistance } from '../../../utils/units';
import { INPUT, LABEL, Segmented } from '../../shared/dialogParts';
import { fs } from '../../shared/DialogShell';
@@ -14,7 +16,7 @@ import { FigureBadge } from '../paddle/FigureBadge';
import type { TourPlannerController } from '../planner/useTourPlanner';
import { InfoDot, TourNotice } from '../tourParts';
import { formatPlannedTourDuration } from '../tourPresentation';
import { formatRunPace, parseRunPace, RUN_PACE_INPUT_MAX_LENGTH, runPaceInvalid } from './runPace';
import { formatRunPace, parseRunPace, RUN_PACE_INPUT_MAX_LENGTH, runPaceInvalid, runPaceUnitText } from './runPace';
import { RUN_PROFILE_ICON } from './runProfileIcon';
/**
@@ -53,8 +55,14 @@ type RunPlanner = Pick<
'runProfile' | 'setRunProfile' | 'runPace' | 'runPaceMissing' | 'runPaceInvalid' | 'routeAnalysis'
> & { walkingDurationMinutes: number | null };
/** The distance unit the user reads distances in, which a pace is typed and read in too. */
function useDistanceUnit(): DistanceUnit {
return useSettingsStore((state) => state.settings.distance_unit) ?? 'metric';
}
/**
* The pace as min:s per km. A pace a run can be saved with reaches the planner
* The pace as min:s per km, or per mile with distances in miles; it is kept
* per km whatever the unit. A pace a run can be saved with reaches the planner
* while it is typed. Anything else waits for leaving the field (or Enter), so
* the first digit of "12:30" flashes no error: then a pace out of bounds
* reaches the planner as it is and text that is no pace as NaN, the planner
@@ -63,11 +71,12 @@ type RunPlanner = Pick<
*/
function RunPaceInput({ planner, locked }: { planner: RunPlanner; locked: boolean }) {
const { t } = useTranslation();
const unit = useDistanceUnit();
const [draft, setDraft] = useState<string | null>(null);
const [focused, setFocused] = useState(false);
const { paceSecondsPerKm: pace, setPace } = planner.runPace;
const unreadable = pace !== null && Number.isNaN(pace);
const shown = pace === null || unreadable ? '' : formatRunPace(pace);
const shown = pace === null || unreadable ? '' : formatRunPace(pace, unit);
const showDraft = draft !== null && (focused || unreadable);
return (
<div className="relative">
@@ -87,13 +96,13 @@ function RunPaceInput({ planner, locked }: { planner: RunPlanner; locked: boolea
}}
onChange={(event) => {
setDraft(event.target.value);
const parsed = parseRunPace(event.target.value);
const parsed = parseRunPace(event.target.value, unit);
if (!runPaceInvalid(parsed)) setPace(parsed);
}}
onBlur={() => {
setFocused(false);
if (draft === null) return;
const parsed = parseRunPace(draft);
const parsed = parseRunPace(draft, unit);
setPace(parsed);
if (!Number.isNaN(parsed)) setDraft(null);
}}
@@ -109,42 +118,41 @@ function RunPaceInput({ planner, locked }: { planner: RunPlanner; locked: boolea
className="pointer-events-none absolute inset-y-0 end-3 flex items-center text-content-faint"
style={fs(12, 'body')}
>
{t('tours.run.paceUnit')}
{runPaceUnitText(unit, t)}
</span>
</div>
);
}
/**
* The sum the running time is, laid out as badges: the distance in km, times
* the pace, is the time. Shown once all three are known, so the runner sees
* The sum the running time is, laid out as badges: the distance, times the
* pace, is the time, both in the unit the user reads distances in. Shown once all three are known, so the runner sees
* where the time comes from and that nothing else goes into it.
*/
function RunTimeSum({ planner }: { planner: RunPlanner }) {
const { t } = useTranslation();
const unit = useDistanceUnit();
const distanceKm = planner.routeAnalysis?.distanceKm ?? null;
const pace = planner.runPace.paceSecondsPerKm;
const minutes = planner.walkingDurationMinutes;
if (distanceKm === null || pace === null || planner.runPaceInvalid || minutes === null) return null;
// The pace is per km, so the sum reads in km whatever unit the app shows distances in.
const distance = formatDistance(distanceKm, 'metric');
// The pace reads per km or per mile as the distances do, so the sum reads in that unit too.
const distance = formatDistance(distanceKm, unit);
const paceText = `${formatRunPace(pace, unit)} ${runPaceUnitText(unit, t)}`;
const time = formatPlannedTourDuration(minutes);
const operator = 'font-geist font-semibold text-content-faint';
return (
<div
data-testid="tour-run-time-sum"
role="note"
aria-label={t('tours.run.timeSum', { distance, pace: formatRunPace(pace), time })}
aria-label={t('tours.run.timeSum', { distance, pace: paceText, time })}
className="mt-2 flex min-w-0 flex-wrap items-center gap-1.5"
>
<FigureBadge icon={<Ruler size={11} strokeWidth={2} aria-hidden="true" />} value={distance} />
<span aria-hidden="true" className={operator} style={fs(12)}>
×
</span>
<FigureBadge
icon={<Gauge size={11} strokeWidth={2} aria-hidden="true" />}
value={`${formatRunPace(pace)} ${t('tours.run.paceUnit')}`}
/>
<FigureBadge icon={<Gauge size={11} strokeWidth={2} aria-hidden="true" />} value={paceText} />
<span aria-hidden="true" className={operator} style={fs(12)}>
=
</span>
@@ -161,6 +169,11 @@ function RunTimeSum({ planner }: { planner: RunPlanner }) {
*/
export function RunFields({ planner, locked }: { planner: RunPlanner; locked: boolean }) {
const { t } = useTranslation();
const unit = useDistanceUnit();
const bounds = {
fastest: formatRunPace(MIN_RUN_PACE_SECONDS_PER_KM, unit),
slowest: formatRunPace(MAX_RUN_PACE_SECONDS_PER_KM, unit),
};
return (
<div data-testid="tour-planner-run-fields" className="flex min-w-0 flex-col gap-3">
<RunProfileField value={planner.runProfile} onChange={planner.setRunProfile} />
@@ -174,10 +187,7 @@ export function RunFields({ planner, locked }: { planner: RunPlanner; locked: bo
<RunPaceInput planner={planner} locked={locked} />
{planner.runPaceInvalid ? (
<span id="tour-planner-run-pace-error" role="alert" className="mt-1 block text-danger" style={fs(11)}>
{t('tours.run.paceInvalid', {
fastest: formatRunPace(MIN_RUN_PACE_SECONDS_PER_KM),
slowest: formatRunPace(MAX_RUN_PACE_SECONDS_PER_KM),
})}
{t(unit === 'imperial' ? 'tours.run.paceInvalidMi' : 'tours.run.paceInvalid', bounds)}
</span>
) : (
<RunTimeSum planner={planner} />
@@ -1,6 +1,7 @@
import type { TourListItem } from '@trek/shared';
import { Gauge } from 'lucide-react';
import { useTranslation } from '../../../i18n';
import { useSettingsStore } from '../../../store/settingsStore';
import { fs } from '../../shared/DialogShell';
import { Tooltip } from '../../shared/Tooltip';
import { runPaceText } from './runPaceText';
@@ -19,8 +20,9 @@ export function RunHeadPills({
className: string;
}) {
const { t } = useTranslation();
const unit = useSettingsStore((state) => state.settings.distance_unit) ?? 'metric';
if (tour.tour_type !== 'run') return null;
const pace = runPaceText(tour, t);
const pace = runPaceText(tour, t, unit);
const ProfileIcon = RUN_PROFILE_ICON[tour.run_profile ?? 'running'];
return (
<>
@@ -1,7 +1,8 @@
import { describe, expect, it } from 'vitest';
import { formatRunPace, parseRunPace, runPaceInvalid } from './runPace';
import { formatRunPace, parseRunPace, runPaceInvalid, runPaceUnitText } from './runPace';
import { runPaceText } from './runPaceText';
// FE-TOUR-RUN-PACE-001 to FE-TOUR-RUN-PACE-003
// FE-TOUR-RUN-PACE-001 to FE-TOUR-RUN-PACE-004
describe('run pace (RUN-01)', () => {
it('FE-TOUR-RUN-PACE-001: reads min:s, whole minutes, minutes and a tenth, two digits after a point as seconds, and nothing as no pace', () => {
@@ -32,4 +33,20 @@ describe('run pace (RUN-01)', () => {
expect(runPaceInvalid(360)).toBe(false);
expect([Number.NaN, 0, 119, 1201].map(runPaceInvalid)).toEqual([true, true, true, true]);
});
it('FE-TOUR-RUN-PACE-004: with distances in miles a pace is read and written per mile and kept per km', () => {
expect(parseRunPace('9:39', 'imperial')).toBe(360);
expect(parseRunPace('8', 'imperial')).toBe(298);
expect(parseRunPace('', 'imperial')).toBeNull();
expect(parseRunPace('9:75', 'imperial')).toBeNaN();
expect(formatRunPace(360, 'imperial')).toBe('9:39');
expect(formatRunPace(parseRunPace('9:39', 'imperial') ?? 0, 'imperial')).toBe('9:39');
expect(formatRunPace(360, 'metric')).toBe('6:00');
const t = (key: string) => key;
expect(runPaceUnitText('imperial', t)).toBe('tours.run.paceUnitMi');
expect(runPaceUnitText('metric', t)).toBe('tours.run.paceUnit');
const run = { tour_type: 'run' as const, run_pace_seconds_per_km: 360 };
expect(runPaceText(run, t, 'imperial')).toBe('9:39 tours.run.paceUnitMi');
expect(runPaceText(run, t)).toBe('6:00 tours.run.paceUnit');
});
});
+32 -16
View File
@@ -1,4 +1,5 @@
import { runPaceSecondsPerKmSchema } from '@trek/shared';
import type { DistanceUnit } from '../../../types';
/** The longest text the pace field takes: "12:30" and a little room. */
export const RUN_PACE_INPUT_MAX_LENGTH = 6;
@@ -9,22 +10,14 @@ function clockSeconds(minutes: string, seconds: string): number {
return secondsNumber < 60 ? Number(minutes) * 60 + secondsNumber : Number.NaN;
}
/** A statute mile in kilometres: a pace per mile is this many times a pace per kilometre. */
const KM_PER_MILE = 1.609344;
const CLOCK = /^(\d{1,2}):(\d{2})$/;
const MINUTES = /^(\d{1,2})(?:[.,](\d{1,2}))?$/;
/**
* RUN-01: a pace as typed, read as whole seconds per kilometre. "5:30" is
* minutes and seconds, "6" whole minutes and "5,5" or "5.5" minutes and a
* tenth. Two digits after the point or comma are seconds, the way "5.30" or
* "5,30" is written for 5:30 in much of Europe, not hundredths of a minute.
* An empty field is no pace (null); anything else, seconds of 60 or more
* included, is NaN, so the planner marks it and holds Save back while the
* text stays for the runner to fix. Whether the pace is in bounds is the
* contract's to say, not this one's.
*/
export function parseRunPace(text: string): number | null {
const value = text.trim();
if (value === '') return null;
/** A pace as typed, whole seconds per the unit it is typed in. */
function typedSeconds(value: string): number {
const clock = CLOCK.exec(value);
if (clock) return clockSeconds(clock[1], clock[2]);
const minutes = MINUTES.exec(value);
@@ -34,12 +27,35 @@ export function parseRunPace(text: string): number | null {
return Math.round(Number(`${whole}.${part}`) * 60);
}
/** A pace as the runner reads it, minutes and seconds per kilometre: 360 is "6:00". */
export function formatRunPace(secondsPerKm: number): string {
const whole = Math.round(secondsPerKm);
/**
* RUN-01: a pace as typed, read as whole seconds per kilometre, the way it is
* stored. The runner types it per kilometre or, with distances in miles, per
* mile. "5:30" is minutes and seconds, "6" whole minutes and "5,5" or "5.5"
* minutes and a tenth. Two digits after the point or comma are seconds, the
* way "5.30" or "5,30" is written for 5:30 in much of Europe, not hundredths
* of a minute. An empty field is no pace (null); anything else, seconds of 60
* or more included, is NaN, so the planner marks it and holds Save back while
* the text stays for the runner to fix. Whether the pace is in bounds is the
* contract's to say, not this one's.
*/
export function parseRunPace(text: string, unit: DistanceUnit = 'metric'): number | null {
const value = text.trim();
if (value === '') return null;
const seconds = typedSeconds(value);
return unit === 'imperial' && !Number.isNaN(seconds) ? Math.round(seconds / KM_PER_MILE) : seconds;
}
/** A pace as the runner reads it, minutes and seconds per kilometre or per mile: 360 s/km is "6:00" or "9:39". */
export function formatRunPace(secondsPerKm: number, unit: DistanceUnit = 'metric'): string {
const whole = Math.round(unit === 'imperial' ? secondsPerKm * KM_PER_MILE : secondsPerKm);
return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, '0')}`;
}
/** The unit a pace reads in, in the user's language: minutes per kilometre, or per mile with distances in miles. */
export function runPaceUnitText(unit: DistanceUnit, t: (key: string) => string): string {
return t(unit === 'imperial' ? 'tours.run.paceUnitMi' : 'tours.run.paceUnit');
}
/** Whether a pace held by the planner is one a run can be saved with: unset, or in the contract's bounds. */
export function runPaceInvalid(secondsPerKm: number | null): boolean {
return secondsPerKm !== null && !runPaceSecondsPerKmSchema.safeParse(secondsPerKm).success;
@@ -1,16 +1,18 @@
import type { TourListItem } from '@trek/shared';
import { formatRunPace, runPaceInvalid } from './runPace';
import type { DistanceUnit } from '../../../types';
import { formatRunPace, runPaceInvalid, runPaceUnitText } from './runPace';
/**
* RUN-01: a saved run's pace as its facts say it, "6:00 min/km" with the unit
* in the user's language, or null for another type and for a run without one
* (an import, a run whose runner has not entered one yet): unknown stays
* unknown.
* in the user's language, or "9:39 min/mi" with distances in miles; null for
* another type and for a run without one (an import, a run whose runner has
* not entered one yet): unknown stays unknown.
*/
export function runPaceText(
tour: Pick<TourListItem, 'tour_type'> & Partial<Pick<TourListItem, 'run_pace_seconds_per_km'>>,
t: (key: string) => string
t: (key: string) => string,
unit: DistanceUnit = 'metric'
): string | null {
const pace = tour.tour_type === 'run' ? (tour.run_pace_seconds_per_km ?? null) : null;
return pace === null || runPaceInvalid(pace) ? null : `${formatRunPace(pace)} ${t('tours.run.paceUnit')}`;
return pace === null || runPaceInvalid(pace) ? null : `${formatRunPace(pace, unit)} ${runPaceUnitText(unit, t)}`;
}
+3 -1
View File
@@ -449,14 +449,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'وتيرتك الخاصة لكل كيلومتر. وقت الجري هو المسافة × الوتيرة؛ ولا تُحتسب الصعودات والنزولات ولا الصعوبة التقنية.',
'tours.run.paceUnit': 'دقيقة/كم',
'tours.run.paceUnitMi': 'دقيقة/ميل',
'tours.run.paceInvalid': 'أدخل الوتيرة بصيغة دقيقة:ثانية لكل كم، من {fastest} إلى {slowest}.',
'tours.run.paceInvalidMi': 'أدخل الوتيرة بصيغة دقيقة:ثانية لكل ميل، من {fastest} إلى {slowest}.',
'tours.run.paceNeeded': 'أدخل وتيرتك لتخطيط وقت الجري وحفظ هذا الجري.',
'tours.run.timeEstimated': 'وقت الجري (مخطط)',
'tours.run.timeEstimatedShort': 'وقت الجري',
'tours.run.timeEstimatedInfoLabel': 'كيف يُخطط وقت الجري',
'tours.run.timeEstimatedInfo':
'المسافة × وتيرتك ولا شيء غير ذلك: لا تُحتسب الصعودات والنزولات ولا الصعوبة. إنه تخطيط، وليس وقتًا مسجّلًا.',
'tours.run.timeSum': '{distance} بوتيرة {pace} دقيقة/كم تساوي {time}',
'tours.run.timeSum': '{distance} بوتيرة {pace} تساوي {time}',
'tours.run.profile.trail': 'الجري على المسارات الطبيعية',
'tours.run.profileHint.trail': 'يفضّل طرق الغابات والممرات الطبيعية، حتى الممرات الجبلية (T2).',
'tours.run.ways.gradeTop': 'أصعب مسار مصنّف: {grade}',
+3 -1
View File
@@ -439,14 +439,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Kilometr başına öz tempiniz. Qaçış vaxtı məsafə × tempdir; yoxuşlar, enişlər və texniki çətinlik nəzərə alınmır.',
'tours.run.paceUnit': 'dəq/km',
'tours.run.paceUnitMi': 'dəq/mi',
'tours.run.paceInvalid': 'Tempi km başına dəq:san kimi, {fastest} ilə {slowest} arasında daxil edin.',
'tours.run.paceInvalidMi': 'Tempi mil başına dəq:san kimi, {fastest} ilə {slowest} arasında daxil edin.',
'tours.run.paceNeeded': 'Qaçış vaxtını planlaşdırmaq və bu qaçışı saxlamaq üçün tempinizi daxil edin.',
'tours.run.timeEstimated': 'Qaçış vaxtı (planlaşdırılan)',
'tours.run.timeEstimatedShort': 'Qaçış vaxtı',
'tours.run.timeEstimatedInfoLabel': 'Qaçış vaxtı necə planlaşdırılır',
'tours.run.timeEstimatedInfo':
'Məsafə × sizin temp, başqa heç nə: yoxuşlar, enişlər və çətinlik nəzərə alınmır. Bu plandır, qeydə alınmış vaxt deyil.',
'tours.run.timeSum': '{distance}, {pace} dəq/km tempdə: {time}',
'tours.run.timeSum': '{distance}, {pace} tempdə: {time}',
'tours.run.profile.trail': 'Trail qaçışı',
'tours.run.profileHint.trail': 'Meşə yollarına və təbii cığırlara üstünlük verir, dağ cığırlarına (T2) qədər.',
'tours.run.ways.gradeTop': 'Ən çətin qiymətləndirilmiş cığır: {grade}',
+3 -1
View File
@@ -445,14 +445,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Seu próprio ritmo por quilômetro. O tempo de corrida é distância × ritmo; subidas, descidas e dificuldade técnica não entram.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Informe o ritmo como min:s por km, de {fastest} a {slowest}.',
'tours.run.paceInvalidMi': 'Informe o ritmo como min:s por milha, de {fastest} a {slowest}.',
'tours.run.paceNeeded': 'Informe seu ritmo para planejar o tempo de corrida e salvar esta corrida.',
'tours.run.timeEstimated': 'Tempo de corrida (planejado)',
'tours.run.timeEstimatedShort': 'Tempo de corrida',
'tours.run.timeEstimatedInfoLabel': 'Como o tempo de corrida é planejado',
'tours.run.timeEstimatedInfo':
'Distância × seu ritmo e nada mais: subidas, descidas e dificuldade não entram. Um plano, não um tempo registrado.',
'tours.run.timeSum': '{distance} a {pace} min/km dão {time}',
'tours.run.timeSum': '{distance} a {pace} dão {time}',
'tours.run.profile.trail': 'Corrida em trilha',
'tours.run.profileHint.trail': 'Prefere estradas florestais e trilhas naturais, até trilhas de montanha (T2).',
'tours.run.ways.gradeTop': 'Trilha classificada mais difícil: {grade}',
+3 -1
View File
@@ -446,14 +446,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'El teu ritme per quilòmetre. El temps de cursa és distància × ritme; el desnivell i la dificultat tècnica no hi compten.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Introdueix el ritme com a min:s per km, de {fastest} a {slowest}.',
'tours.run.paceInvalidMi': 'Introdueix el ritme com a min:s per milla, de {fastest} a {slowest}.',
'tours.run.paceNeeded': 'Introdueix el teu ritme per planificar el temps de cursa i desar aquesta cursa.',
'tours.run.timeEstimated': 'Temps de cursa (previst)',
'tours.run.timeEstimatedShort': 'Temps de cursa',
'tours.run.timeEstimatedInfoLabel': 'Com es planifica el temps de cursa',
'tours.run.timeEstimatedInfo':
'Distància × el teu ritme i res més: el desnivell i la dificultat no hi compten. Una planificació, no un temps enregistrat.',
'tours.run.timeSum': '{distance} a {pace} min/km són {time}',
'tours.run.timeSum': '{distance} a {pace} són {time}',
'tours.run.profile.trail': 'Curses de muntanya',
'tours.run.profileHint.trail': 'Prefereix pistes forestals i corriols naturals, fins a camins de muntanya (T2).',
'tours.run.ways.gradeTop': 'Camí classificat més difícil: {grade}',
+3 -1
View File
@@ -438,14 +438,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Vaše vlastní tempo na kilometr. Čas běhu je vzdálenost × tempo; převýšení a technická obtížnost se nezapočítávají.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Zadejte tempo jako min:s na km, od {fastest} do {slowest}.',
'tours.run.paceInvalidMi': 'Zadejte tempo jako min:s na míli, od {fastest} do {slowest}.',
'tours.run.paceNeeded': 'Zadejte své tempo, aby se naplánoval čas běhu a běh šel uložit.',
'tours.run.timeEstimated': 'Čas běhu (plánovaný)',
'tours.run.timeEstimatedShort': 'Čas běhu',
'tours.run.timeEstimatedInfoLabel': 'Jak se plánuje čas běhu',
'tours.run.timeEstimatedInfo':
'Vzdálenost × vaše tempo a nic dalšího: převýšení a obtížnost se nezapočítávají. Plán, ne zaznamenaný čas.',
'tours.run.timeSum': '{distance} při {pace} min/km dává {time}',
'tours.run.timeSum': '{distance} při {pace} dává {time}',
'tours.run.profile.trail': 'Trailový běh',
'tours.run.profileHint.trail': 'Upřednostňuje lesní cesty a přírodní stezky, až po horské stezky (T2).',
'tours.run.ways.gradeTop': 'Nejtěžší hodnocená cesta: {grade}',
+3 -1
View File
@@ -448,14 +448,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Deine eigene Pace pro Kilometer. Die Laufzeit ist Distanz × Pace; Höhenmeter und technische Schwierigkeit gehen nicht ein.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Gib die Pace als Min:Sek pro km ein, von {fastest} bis {slowest}.',
'tours.run.paceInvalidMi': 'Gib die Pace als Min:Sek pro Meile ein, von {fastest} bis {slowest}.',
'tours.run.paceNeeded': 'Gib deine Pace ein, um die Laufzeit zu planen und diesen Lauf zu speichern.',
'tours.run.timeEstimated': 'Laufzeit (geplant)',
'tours.run.timeEstimatedShort': 'Laufzeit',
'tours.run.timeEstimatedInfoLabel': 'Wie die Laufzeit geplant wird',
'tours.run.timeEstimatedInfo':
'Distanz × deine Pace und sonst nichts: Höhenmeter und Schwierigkeit gehen nicht ein. Eine Planung, keine aufgezeichnete Zeit.',
'tours.run.timeSum': '{distance} bei {pace} min/km ergibt {time}',
'tours.run.timeSum': '{distance} bei {pace} ergibt {time}',
'tours.run.profile.trail': 'Trailrunning',
'tours.run.profileHint.trail': 'Bevorzugt Forstwege und naturbelassene Pfade, bis zu Bergwegen (T2).',
'tours.run.ways.gradeTop': 'Schwierigster bewerteter Weg: {grade}',
+3 -1
View File
@@ -437,14 +437,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Your own pace per kilometre. The running time is distance × pace; climbs, descents and how technical the way is are not included.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Enter the pace as min:sec per km, from {fastest} to {slowest}.',
'tours.run.paceInvalidMi': 'Enter the pace as min:sec per mile, from {fastest} to {slowest}.',
'tours.run.paceNeeded': 'Enter your pace to plan the running time and save this run.',
'tours.run.timeEstimated': 'Running time (planned)',
'tours.run.timeEstimatedShort': 'Running time',
'tours.run.timeEstimatedInfoLabel': 'How the running time is planned',
'tours.run.timeEstimatedInfo':
'Distance × your pace and nothing else: climbs, descents and difficulty are not included. A plan, not a recorded time.',
'tours.run.timeSum': '{distance} at {pace} min/km is {time}',
'tours.run.timeSum': '{distance} at {pace} is {time}',
'tours.run.profile.trail': 'Trail running',
'tours.run.profileHint.trail': 'Prefers forest tracks and natural paths, up to mountain paths (T2).',
'tours.run.ways.gradeTop': 'Hardest graded path: {grade}',
+3 -1
View File
@@ -445,14 +445,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Tu propio ritmo por kilómetro. El tiempo de carrera es distancia × ritmo; el desnivel y la dificultad técnica no se tienen en cuenta.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Introduce el ritmo como min:s por km, de {fastest} a {slowest}.',
'tours.run.paceInvalidMi': 'Introduce el ritmo como min:s por milla, de {fastest} a {slowest}.',
'tours.run.paceNeeded': 'Introduce tu ritmo para planificar el tiempo de carrera y guardar esta carrera.',
'tours.run.timeEstimated': 'Tiempo de carrera (previsto)',
'tours.run.timeEstimatedShort': 'Tiempo de carrera',
'tours.run.timeEstimatedInfoLabel': 'Cómo se planifica el tiempo de carrera',
'tours.run.timeEstimatedInfo':
'Distancia × tu ritmo y nada más: el desnivel y la dificultad no se tienen en cuenta. Una planificación, no un tiempo registrado.',
'tours.run.timeSum': '{distance} a {pace} min/km son {time}',
'tours.run.timeSum': '{distance} a {pace} son {time}',
'tours.run.profile.trail': 'Carrera de montaña',
'tours.run.profileHint.trail': 'Prefiere pistas forestales y senderos naturales, hasta senderos de montaña (T2).',
'tours.run.ways.gradeTop': 'Camino graduado más difícil: {grade}',
+3 -1
View File
@@ -440,14 +440,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Sinu enda tempo kilomeetri kohta. Jooksuaeg on vahemaa × tempo; tõusud, langused ja tehniline raskus ei lähe arvesse.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Sisesta tempo kujul min:s kilomeetri kohta, vahemikus {fastest} kuni {slowest}.',
'tours.run.paceInvalidMi': 'Sisesta tempo kujul min:s miili kohta, vahemikus {fastest} kuni {slowest}.',
'tours.run.paceNeeded': 'Sisesta oma tempo, et jooksuaeg planeerida ja see jooks salvestada.',
'tours.run.timeEstimated': 'Jooksuaeg (planeeritud)',
'tours.run.timeEstimatedShort': 'Jooksuaeg',
'tours.run.timeEstimatedInfoLabel': 'Kuidas jooksuaeg planeeritakse',
'tours.run.timeEstimatedInfo':
'Vahemaa × sinu tempo ja ei midagi muud: tõusud, langused ja raskus ei lähe arvesse. Plaan, mitte salvestatud aeg.',
'tours.run.timeSum': '{distance} tempos {pace} min/km teeb {time}',
'tours.run.timeSum': '{distance} tempos {pace} teeb {time}',
'tours.run.profile.trail': 'Rajajooks',
'tours.run.profileHint.trail': 'Eelistab metsateid ja looduslikke radu, kuni mägiradadeni (T2).',
'tours.run.ways.gradeTop': 'Raskeim hinnatud rada: {grade}',
+3 -1
View File
@@ -451,14 +451,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Ton allure à toi, par kilomètre. Le temps de course est distance × allure ; dénivelé et difficulté technique ne sont pas pris en compte.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Saisis l’allure en min:s par km, de {fastest} à {slowest}.',
'tours.run.paceInvalidMi': 'Saisis l’allure en min:s par mile, de {fastest} à {slowest}.',
'tours.run.paceNeeded': 'Saisis ton allure pour planifier le temps de course et enregistrer cette course.',
'tours.run.timeEstimated': 'Temps de course (prévu)',
'tours.run.timeEstimatedShort': 'Temps de course',
'tours.run.timeEstimatedInfoLabel': 'Comment le temps de course est planifié',
'tours.run.timeEstimatedInfo':
'Distance × ton allure, rien d’autre : dénivelé et difficulté ne sont pas pris en compte. Une prévision, pas un temps enregistré.',
'tours.run.timeSum': '{distance} à {pace} min/km font {time}',
'tours.run.timeSum': '{distance} à {pace} font {time}',
'tours.run.profile.trail': 'Trail',
'tours.run.profileHint.trail':
'Privilégie les chemins forestiers et les sentiers naturels, jusqu’aux sentiers de montagne (T2).',
+3 -1
View File
@@ -453,7 +453,9 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Ο δικός σου ρυθμός ανά χιλιόμετρο. Ο χρόνος τρεξίματος είναι απόσταση × ρυθμός· ανηφόρες, κατηφόρες και τεχνική δυσκολία δεν λαμβάνονται υπόψη.',
'tours.run.paceUnit': 'λεπ/km',
'tours.run.paceUnitMi': 'λεπ/mi',
'tours.run.paceInvalid': 'Δώσε τον ρυθμό ως λεπ:δευτ ανά km, από {fastest} έως {slowest}.',
'tours.run.paceInvalidMi': 'Δώσε τον ρυθμό ως λεπ:δευτ ανά μίλι, από {fastest} έως {slowest}.',
'tours.run.paceNeeded':
'Δώσε τον ρυθμό σου για να προγραμματιστεί ο χρόνος τρεξίματος και να αποθηκευτεί αυτό το τρέξιμο.',
'tours.run.timeEstimated': 'Χρόνος τρεξίματος (προγραμματισμένος)',
@@ -461,7 +463,7 @@ const tours: TranslationStrings = {
'tours.run.timeEstimatedInfoLabel': 'Πώς προγραμματίζεται ο χρόνος τρεξίματος',
'tours.run.timeEstimatedInfo':
'Απόσταση × ο ρυθμός σου και τίποτα άλλο: ανηφόρες, κατηφόρες και δυσκολία δεν λαμβάνονται υπόψη. Πρόγραμμα, όχι καταγεγραμμένος χρόνος.',
'tours.run.timeSum': '{distance} με {pace} λεπ/km κάνει {time}',
'tours.run.timeSum': '{distance} με {pace} κάνει {time}',
'tours.run.profile.trail': 'Ορεινό τρέξιμο',
'tours.run.profileHint.trail': 'Προτιμά δασικούς δρόμους και φυσικά μονοπάτια, έως ορεινά μονοπάτια (T2).',
'tours.run.ways.gradeTop': 'Δυσκολότερο βαθμολογημένο μονοπάτι: {grade}',
+3 -1
View File
@@ -445,14 +445,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'A saját tempód kilométerenként. A futásidő távolság × tempó; a szintemelkedés és a technikai nehézség nem számít bele.',
'tours.run.paceUnit': 'perc/km',
'tours.run.paceUnitMi': 'perc/mi',
'tours.run.paceInvalid': 'Add meg a tempót perc:mp/km formában, {fastest} és {slowest} között.',
'tours.run.paceInvalidMi': 'Add meg a tempót perc:mp/mérföld formában, {fastest} és {slowest} között.',
'tours.run.paceNeeded': 'Add meg a tempódat a futásidő tervezéséhez és a futás mentéséhez.',
'tours.run.timeEstimated': 'Futásidő (tervezett)',
'tours.run.timeEstimatedShort': 'Futásidő',
'tours.run.timeEstimatedInfoLabel': 'Hogyan tervezzük a futásidőt',
'tours.run.timeEstimatedInfo':
'Távolság × a tempód, semmi más: a szintemelkedés és a nehézség nem számít bele. Terv, nem rögzített idő.',
'tours.run.timeSum': '{distance} {pace} perc/km tempóval: {time}',
'tours.run.timeSum': '{distance} {pace} tempóval: {time}',
'tours.run.profile.trail': 'Terepfutás',
'tours.run.profileHint.trail':
'Előnyben részesíti az erdei utakat és a természetes ösvényeket, a hegyi ösvényekig (T2).',
+3 -1
View File
@@ -440,14 +440,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Pace milikmu sendiri per kilometer. Waktu lari adalah jarak × pace; tanjakan, turunan, dan tingkat kesulitan teknis tidak dihitung.',
'tours.run.paceUnit': 'mnt/km',
'tours.run.paceUnitMi': 'mnt/mi',
'tours.run.paceInvalid': 'Masukkan pace sebagai mnt:dtk per km, dari {fastest} sampai {slowest}.',
'tours.run.paceInvalidMi': 'Masukkan pace sebagai mnt:dtk per mil, dari {fastest} sampai {slowest}.',
'tours.run.paceNeeded': 'Masukkan pace-mu untuk merencanakan waktu lari dan menyimpan lari ini.',
'tours.run.timeEstimated': 'Waktu lari (rencana)',
'tours.run.timeEstimatedShort': 'Waktu lari',
'tours.run.timeEstimatedInfoLabel': 'Cara waktu lari direncanakan',
'tours.run.timeEstimatedInfo':
'Jarak × pace-mu, tidak ada yang lain: tanjakan, turunan, dan kesulitan tidak dihitung. Sebuah rencana, bukan waktu yang direkam.',
'tours.run.timeSum': '{distance} dengan pace {pace} mnt/km menjadi {time}',
'tours.run.timeSum': '{distance} dengan pace {pace} menjadi {time}',
'tours.run.profile.trail': 'Lari trail',
'tours.run.profileHint.trail': 'Mengutamakan jalan hutan dan jalur alami, hingga jalur gunung (T2).',
'tours.run.ways.gradeTop': 'Jalur bertingkat tersulit: {grade}',
+3 -1
View File
@@ -448,14 +448,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Il tuo passo al chilometro. Il tempo di corsa è distanza × passo; dislivello e difficoltà tecnica non sono considerati.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Inserisci il passo come min:s al km, da {fastest} a {slowest}.',
'tours.run.paceInvalidMi': 'Inserisci il passo come min:s al miglio, da {fastest} a {slowest}.',
'tours.run.paceNeeded': 'Inserisci il tuo passo per pianificare il tempo di corsa e salvare questa corsa.',
'tours.run.timeEstimated': 'Tempo di corsa (previsto)',
'tours.run.timeEstimatedShort': 'Tempo di corsa',
'tours.run.timeEstimatedInfoLabel': 'Come si pianifica il tempo di corsa',
'tours.run.timeEstimatedInfo':
'Distanza × il tuo passo e nient’altro: dislivello e difficoltà non sono considerati. Una pianificazione, non un tempo registrato.',
'tours.run.timeSum': '{distance} a {pace} min/km fanno {time}',
'tours.run.timeSum': '{distance} a {pace} fanno {time}',
'tours.run.profile.trail': 'Corsa su sentiero',
'tours.run.profileHint.trail': 'Preferisce strade forestali e sentieri naturali, fino ai sentieri di montagna (T2).',
'tours.run.ways.gradeTop': 'Sentiero classificato più difficile: {grade}',
+3 -1
View File
@@ -428,13 +428,15 @@ const tours: TranslationStrings = {
'tours.run.paceInfoLabel': '走行時間の計算方法',
'tours.run.paceInfo': '1 kmあたりのあなた自身のペースです。走行時間は距離 × ペースで、登り、下り、技術的な難しさは含みません。',
'tours.run.paceUnit': '分/km',
'tours.run.paceUnitMi': '分/mi',
'tours.run.paceInvalid': 'ペースは km あたり 分:秒 で、{fastest} から {slowest} の間で入力してください。',
'tours.run.paceInvalidMi': 'ペースは マイルあたり 分:秒 で、{fastest} から {slowest} の間で入力してください。',
'tours.run.paceNeeded': '走行時間を計画してこのランを保存するには、ペースを入力してください。',
'tours.run.timeEstimated': '走行時間 (予定)',
'tours.run.timeEstimatedShort': '走行時間',
'tours.run.timeEstimatedInfoLabel': '走行時間の計画方法',
'tours.run.timeEstimatedInfo': '距離 × あなたのペースのみで、登り、下り、難しさは含みません。記録された時間ではなく計画です。',
'tours.run.timeSum': '{distance} を {pace} 分/km で {time}',
'tours.run.timeSum': '{distance} を {pace} で {time}',
'tours.run.profile.trail': 'トレイルランニング',
'tours.run.profileHint.trail': '林道や自然の小道を優先し、山道 (T2) まで含みます。',
'tours.run.ways.gradeTop': '最も難しい格付けの道: {grade}',
+3 -1
View File
@@ -426,13 +426,15 @@ const tours: TranslationStrings = {
'tours.run.paceInfoLabel': '달리기 시간 계산 방법',
'tours.run.paceInfo': '킬로미터당 나만의 페이스입니다. 달리기 시간은 거리 × 페이스이며, 오르막, 내리막, 기술적 난이도는 포함되지 않습니다.',
'tours.run.paceUnit': '분/km',
'tours.run.paceUnitMi': '분/mi',
'tours.run.paceInvalid': '페이스를 km당 분:초로, {fastest}에서 {slowest} 사이로 입력하세요.',
'tours.run.paceInvalidMi': '페이스를 마일당 분:초로, {fastest}에서 {slowest} 사이로 입력하세요.',
'tours.run.paceNeeded': '달리기 시간을 계획하고 이 달리기를 저장하려면 페이스를 입력하세요.',
'tours.run.timeEstimated': '달리기 시간 (계획)',
'tours.run.timeEstimatedShort': '달리기 시간',
'tours.run.timeEstimatedInfoLabel': '달리기 시간 계획 방법',
'tours.run.timeEstimatedInfo': '거리 × 나의 페이스뿐이며, 오르막, 내리막, 난이도는 포함되지 않습니다. 기록된 시간이 아니라 계획입니다.',
'tours.run.timeSum': '{distance}, {pace}분/km 페이스로 {time}',
'tours.run.timeSum': '{distance}, {pace} 페이스로 {time}',
'tours.run.profile.trail': '트레일 러닝',
'tours.run.profileHint.trail': '임도와 자연 오솔길을 우선하며, 산길(T2)까지 포함합니다.',
'tours.run.ways.gradeTop': '가장 어려운 등급의 길: {grade}',
+3 -1
View File
@@ -447,14 +447,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Je eigen tempo per kilometer. De looptijd is afstand × tempo; hoogtemeters en technische moeilijkheid tellen niet mee.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Vul het tempo in als min:sec per km, van {fastest} tot {slowest}.',
'tours.run.paceInvalidMi': 'Vul het tempo in als min:sec per mijl, van {fastest} tot {slowest}.',
'tours.run.paceNeeded': 'Vul je tempo in om de looptijd te plannen en deze looptocht op te slaan.',
'tours.run.timeEstimated': 'Looptijd (gepland)',
'tours.run.timeEstimatedShort': 'Looptijd',
'tours.run.timeEstimatedInfoLabel': 'Hoe de looptijd wordt gepland',
'tours.run.timeEstimatedInfo':
'Afstand × je tempo en verder niets: hoogtemeters en moeilijkheid tellen niet mee. Een planning, geen opgenomen tijd.',
'tours.run.timeSum': '{distance} bij {pace} min/km is {time}',
'tours.run.timeSum': '{distance} bij {pace} is {time}',
'tours.run.profile.trail': 'Trailrunning',
'tours.run.profileHint.trail': 'Geeft de voorkeur aan bospaden en onverharde paden, tot bergpaden (T2).',
'tours.run.ways.gradeTop': 'Moeilijkste gegradeerde pad: {grade}',
+3 -1
View File
@@ -454,14 +454,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Twoje własne tempo na kilometr. Czas biegu to dystans × tempo; przewyższenia i trudność techniczna nie są uwzględniane.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Wpisz tempo jako min:s na km, od {fastest} do {slowest}.',
'tours.run.paceInvalidMi': 'Wpisz tempo jako min:s na milę, od {fastest} do {slowest}.',
'tours.run.paceNeeded': 'Wpisz swoje tempo, aby zaplanować czas biegu i zapisać ten bieg.',
'tours.run.timeEstimated': 'Czas biegu (planowany)',
'tours.run.timeEstimatedShort': 'Czas biegu',
'tours.run.timeEstimatedInfoLabel': 'Jak planowany jest czas biegu',
'tours.run.timeEstimatedInfo':
'Dystans × Twoje tempo i nic więcej: przewyższenia i trudność nie są uwzględniane. Plan, a nie zarejestrowany czas.',
'tours.run.timeSum': '{distance} przy {pace} min/km to {time}',
'tours.run.timeSum': '{distance} przy {pace} to {time}',
'tours.run.profile.trail': 'Bieganie terenowe',
'tours.run.profileHint.trail': 'Preferuje drogi leśne i naturalne ścieżki, aż po ścieżki górskie (T2).',
'tours.run.ways.gradeTop': 'Najtrudniejsza oceniona ścieżka: {grade}',
+3 -1
View File
@@ -458,14 +458,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Ваш собственный темп на километр. Время бега равно расстоянию × темп; набор высоты и техническая сложность не учитываются.',
'tours.run.paceUnit': 'мин/км',
'tours.run.paceUnitMi': 'мин/миля',
'tours.run.paceInvalid': 'Введите темп как мин:с на км, от {fastest} до {slowest}.',
'tours.run.paceInvalidMi': 'Введите темп как мин:с на милю, от {fastest} до {slowest}.',
'tours.run.paceNeeded': 'Введите свой темп, чтобы рассчитать время бега и сохранить эту пробежку.',
'tours.run.timeEstimated': 'Время бега (план)',
'tours.run.timeEstimatedShort': 'Время бега',
'tours.run.timeEstimatedInfoLabel': 'Как планируется время бега',
'tours.run.timeEstimatedInfo':
'Расстояние × ваш темп и ничего больше: набор высоты и сложность не учитываются. Это план, а не записанное время.',
'tours.run.timeSum': '{distance} в темпе {pace} мин/км: {time}',
'tours.run.timeSum': '{distance} в темпе {pace}: {time}',
'tours.run.profile.trail': 'Трейлраннинг',
'tours.run.profileHint.trail': 'Предпочитает лесные дороги и природные тропы, вплоть до горных троп (T2).',
'tours.run.ways.gradeTop': 'Самая сложная оцененная тропа: {grade}',
+3 -1
View File
@@ -439,14 +439,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Vaše vlastné tempo na kilometer. Čas behu je vzdialenosť × tempo; prevýšenie a technická náročnosť sa nezapočítavajú.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Zadajte tempo ako min:s na km, od {fastest} do {slowest}.',
'tours.run.paceInvalidMi': 'Zadajte tempo ako min:s na míľu, od {fastest} do {slowest}.',
'tours.run.paceNeeded': 'Zadajte svoje tempo, aby sa naplánoval čas behu a beh sa dal uložiť.',
'tours.run.timeEstimated': 'Čas behu (plánovaný)',
'tours.run.timeEstimatedShort': 'Čas behu',
'tours.run.timeEstimatedInfoLabel': 'Ako sa plánuje čas behu',
'tours.run.timeEstimatedInfo':
'Vzdialenosť × vaše tempo a nič iné: prevýšenie a náročnosť sa nezapočítavajú. Plán, nie zaznamenaný čas.',
'tours.run.timeSum': '{distance} pri {pace} min/km dáva {time}',
'tours.run.timeSum': '{distance} pri {pace} dáva {time}',
'tours.run.profile.trail': 'Trailový beh',
'tours.run.profileHint.trail': 'Uprednostňuje lesné cesty a prírodné chodníky, až po horské chodníky (T2).',
'tours.run.ways.gradeTop': 'Najnáročnejšia hodnotená cesta: {grade}',
+3 -1
View File
@@ -436,14 +436,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Ditt eget tempo per kilometer. Löptiden är sträcka × tempo; stigningar och teknisk svårighet räknas inte in.',
'tours.run.paceUnit': 'min/km',
'tours.run.paceUnitMi': 'min/mi',
'tours.run.paceInvalid': 'Ange tempot som min:sek per km, från {fastest} till {slowest}.',
'tours.run.paceInvalidMi': 'Ange tempot som min:sek per engelsk mil, från {fastest} till {slowest}.',
'tours.run.paceNeeded': 'Ange ditt tempo för att planera löptiden och spara löpturen.',
'tours.run.timeEstimated': 'Löptid (planerad)',
'tours.run.timeEstimatedShort': 'Löptid',
'tours.run.timeEstimatedInfoLabel': 'Så planeras löptiden',
'tours.run.timeEstimatedInfo':
'Sträcka × ditt tempo och inget annat: stigningar och svårighet räknas inte in. En planering, ingen inspelad tid.',
'tours.run.timeSum': '{distance} i {pace} min/km blir {time}',
'tours.run.timeSum': '{distance} i {pace} blir {time}',
'tours.run.profile.trail': 'Traillöpning',
'tours.run.profileHint.trail': 'Föredrar skogsvägar och naturstigar, upp till fjällstigar (T2).',
'tours.run.ways.gradeTop': 'Svåraste graderade led: {grade}',
+3 -1
View File
@@ -417,14 +417,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfoLabel': 'วิธีคำนวณเวลาวิ่ง',
'tours.run.paceInfo': 'เพซของคุณเองต่อกิโลเมตร เวลาวิ่งคือระยะทาง × เพซ ไม่รวมทางขึ้น ทางลง และความยากทางเทคนิค',
'tours.run.paceUnit': 'นาที/km',
'tours.run.paceUnitMi': 'นาที/mi',
'tours.run.paceInvalid': 'ใส่เพซในรูปแบบ นาที:วินาที ต่อ km ตั้งแต่ {fastest} ถึง {slowest}',
'tours.run.paceInvalidMi': 'ใส่เพซในรูปแบบ นาที:วินาที ต่อไมล์ ตั้งแต่ {fastest} ถึง {slowest}',
'tours.run.paceNeeded': 'ใส่เพซของคุณเพื่อวางแผนเวลาวิ่งและบันทึกการวิ่งนี้',
'tours.run.timeEstimated': 'เวลาวิ่ง (ตามแผน)',
'tours.run.timeEstimatedShort': 'เวลาวิ่ง',
'tours.run.timeEstimatedInfoLabel': 'วิธีวางแผนเวลาวิ่ง',
'tours.run.timeEstimatedInfo':
'ระยะทาง × เพซของคุณเท่านั้น ไม่รวมทางขึ้น ทางลง และความยาก เป็นแผน ไม่ใช่เวลาที่บันทึกไว้',
'tours.run.timeSum': '{distance} ที่เพซ {pace} นาที/km เท่ากับ {time}',
'tours.run.timeSum': '{distance} ที่เพซ {pace} เท่ากับ {time}',
'tours.run.profile.trail': 'วิ่งเทรล',
'tours.run.profileHint.trail': 'เน้นถนนในป่าและทางเดินธรรมชาติ จนถึงทางเดินบนภูเขา (T2)',
'tours.run.ways.gradeTop': 'ทางที่มีระดับยากที่สุด: {grade}',
+1 -1
View File
@@ -20,7 +20,7 @@ const toursModules = import.meta.glob<{ default: TranslationStrings }>('./*/tour
const TOUR_DOMAINS: { file: string; generalKeyCount: number; modules: DomainModules }[] = [
{
file: 'tours',
generalKeyCount: 1173,
generalKeyCount: 1175,
modules: toursModules,
},
{
+3 -1
View File
@@ -439,14 +439,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Kilometre başına kendi tempon. Koşu süresi mesafe × tempodur; tırmanışlar, inişler ve teknik zorluk hesaba katılmaz.',
'tours.run.paceUnit': 'dk/km',
'tours.run.paceUnitMi': 'dk/mi',
'tours.run.paceInvalid': 'Tempoyu km başına dk:sn olarak, {fastest} ile {slowest} arasında gir.',
'tours.run.paceInvalidMi': 'Tempoyu mil başına dk:sn olarak, {fastest} ile {slowest} arasında gir.',
'tours.run.paceNeeded': 'Koşu süresini planlamak ve bu koşuyu kaydetmek için temponu gir.',
'tours.run.timeEstimated': 'Koşu süresi (planlanan)',
'tours.run.timeEstimatedShort': 'Koşu süresi',
'tours.run.timeEstimatedInfoLabel': 'Koşu süresi nasıl planlanır',
'tours.run.timeEstimatedInfo':
'Mesafe × tempon, başka hiçbir şey değil: tırmanışlar, inişler ve zorluk hesaba katılmaz. Kayıtlı bir süre değil, bir plan.',
'tours.run.timeSum': '{distance}, {pace} dk/km tempoyla {time}',
'tours.run.timeSum': '{distance}, {pace} tempoyla {time}',
'tours.run.profile.trail': 'Patika koşusu',
'tours.run.profileHint.trail': 'Orman yollarını ve doğal patikaları tercih eder, dağ patikalarına (T2) kadar.',
'tours.run.ways.gradeTop': 'En zor derecelendirilmiş patika: {grade}',
+3 -1
View File
@@ -453,14 +453,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Ваш власний темп на кілометр. Час бігу дорівнює відстані × темп; набір висоти й технічна складність не враховуються.',
'tours.run.paceUnit': 'хв/км',
'tours.run.paceUnitMi': 'хв/миля',
'tours.run.paceInvalid': 'Введіть темп як хв:с на км, від {fastest} до {slowest}.',
'tours.run.paceInvalidMi': 'Введіть темп як хв:с на милю, від {fastest} до {slowest}.',
'tours.run.paceNeeded': 'Введіть свій темп, щоб розрахувати час бігу й зберегти цю пробіжку.',
'tours.run.timeEstimated': 'Час бігу (план)',
'tours.run.timeEstimatedShort': 'Час бігу',
'tours.run.timeEstimatedInfoLabel': 'Як планується час бігу',
'tours.run.timeEstimatedInfo':
'Відстань × ваш темп і нічого більше: набір висоти й складність не враховуються. Це план, а не записаний час.',
'tours.run.timeSum': '{distance} у темпі {pace} хв/км: {time}',
'tours.run.timeSum': '{distance} у темпі {pace}: {time}',
'tours.run.profile.trail': 'Трейлранінг',
'tours.run.profileHint.trail': 'Надає перевагу лісовим дорогам і природним стежкам, аж до гірських стежок (T2).',
'tours.run.ways.gradeTop': 'Найскладніша оцінена стежка: {grade}',
+3 -1
View File
@@ -437,14 +437,16 @@ const tours: TranslationStrings = {
'tours.run.paceInfo':
'Nhịp chạy của riêng bạn trên mỗi kilômét. Thời gian chạy là quãng đường × nhịp chạy; độ dốc lên xuống và độ khó kỹ thuật không được tính.',
'tours.run.paceUnit': 'phút/km',
'tours.run.paceUnitMi': 'phút/mi',
'tours.run.paceInvalid': 'Nhập nhịp chạy dạng phút:giây mỗi km, từ {fastest} đến {slowest}.',
'tours.run.paceInvalidMi': 'Nhập nhịp chạy dạng phút:giây mỗi dặm, từ {fastest} đến {slowest}.',
'tours.run.paceNeeded': 'Nhập nhịp chạy của bạn để lên kế hoạch thời gian chạy và lưu buổi chạy này.',
'tours.run.timeEstimated': 'Thời gian chạy (dự kiến)',
'tours.run.timeEstimatedShort': 'Thời gian chạy',
'tours.run.timeEstimatedInfoLabel': 'Cách lên kế hoạch thời gian chạy',
'tours.run.timeEstimatedInfo':
'Quãng đường × nhịp chạy của bạn, không gì khác: độ dốc lên xuống và độ khó không được tính. Đây là kế hoạch, không phải thời gian đã ghi.',
'tours.run.timeSum': '{distance} với nhịp {pace} phút/km là {time}',
'tours.run.timeSum': '{distance} với nhịp {pace} là {time}',
'tours.run.profile.trail': 'Chạy địa hình',
'tours.run.profileHint.trail': 'Ưu tiên đường rừng và lối mòn tự nhiên, đến cả đường núi (T2).',
'tours.run.ways.gradeTop': 'Đường được xếp cấp khó nhất: {grade}',
+3 -1
View File
@@ -398,13 +398,15 @@ const tours: TranslationStrings = {
'tours.run.paceInfoLabel': '跑步時間如何計算',
'tours.run.paceInfo': '你自己的每公里配速。跑步時間等於距離 × 配速;爬升、下降與技術難度不計入。',
'tours.run.paceUnit': '分/公里',
'tours.run.paceUnitMi': '分/英里',
'tours.run.paceInvalid': '請以每公里 分:秒 輸入配速,範圍 {fastest} 至 {slowest}。',
'tours.run.paceInvalidMi': '請以每英里 分:秒 輸入配速,範圍 {fastest} 至 {slowest}。',
'tours.run.paceNeeded': '輸入你的配速,以規劃跑步時間並儲存這次跑步。',
'tours.run.timeEstimated': '跑步時間(計畫)',
'tours.run.timeEstimatedShort': '跑步時間',
'tours.run.timeEstimatedInfoLabel': '跑步時間如何規劃',
'tours.run.timeEstimatedInfo': '僅為距離 × 你的配速:爬升、下降與難度皆不計入。這是計畫,不是記錄的時間。',
'tours.run.timeSum': '{distance},配速 {pace} 分/公里,用時 {time}',
'tours.run.timeSum': '{distance},配速 {pace},用時 {time}',
'tours.run.profile.trail': '越野跑',
'tours.run.profileHint.trail': '優先選擇林道與自然小徑,最高到山地步道 (T2)。',
'tours.run.ways.gradeTop': '最難的已評級路徑:{grade}',
+3 -1
View File
@@ -399,13 +399,15 @@ const tours: TranslationStrings = {
'tours.run.paceInfoLabel': '跑步时间如何计算',
'tours.run.paceInfo': '你自己的每公里配速。跑步时间等于距离 × 配速;爬升、下降和技术难度不计入。',
'tours.run.paceUnit': '分/公里',
'tours.run.paceUnitMi': '分/英里',
'tours.run.paceInvalid': '请按每公里 分:秒 输入配速,范围 {fastest} 至 {slowest}。',
'tours.run.paceInvalidMi': '请按每英里 分:秒 输入配速,范围 {fastest} 至 {slowest}。',
'tours.run.paceNeeded': '输入你的配速,以计划跑步时间并保存这次跑步。',
'tours.run.timeEstimated': '跑步时间(计划)',
'tours.run.timeEstimatedShort': '跑步时间',
'tours.run.timeEstimatedInfoLabel': '跑步时间如何计划',
'tours.run.timeEstimatedInfo': '仅为距离 × 你的配速:爬升、下降和难度均不计入。这是计划,不是记录的时间。',
'tours.run.timeSum': '{distance},配速 {pace} 分/公里,用时 {time}',
'tours.run.timeSum': '{distance},配速 {pace},用时 {time}',
'tours.run.profile.trail': '越野跑',
'tours.run.profileHint.trail': '优先选择林道和自然小径,最高到山地步道 (T2)。',
'tours.run.ways.gradeTop': '最难的已评级路径:{grade}',