1505 Commits

Author SHA1 Message Date
vcoppe
cfb629dcfb New translations elevation.mdx (Serbian (Latin)) 2025-11-12 18:00:29 +01:00
vcoppe
268afe5f84 New translations elevation.mdx (Chinese Traditional, Hong Kong) 2025-11-12 18:00:27 +01:00
vcoppe
6832b7b1ca New translations elevation.mdx (Latvian) 2025-11-12 18:00:26 +01:00
vcoppe
042c900f80 New translations elevation.mdx (Thai) 2025-11-12 18:00:24 +01:00
vcoppe
6524864dea New translations elevation.mdx (Indonesian) 2025-11-12 18:00:23 +01:00
vcoppe
3ad29a2f8b New translations elevation.mdx (Portuguese, Brazilian) 2025-11-12 18:00:22 +01:00
vcoppe
f40c01109a New translations elevation.mdx (Vietnamese) 2025-11-12 18:00:20 +01:00
vcoppe
2c875bd96b New translations elevation.mdx (Chinese Simplified) 2025-11-12 18:00:18 +01:00
vcoppe
ace184c5f4 New translations elevation.mdx (Ukrainian) 2025-11-12 18:00:17 +01:00
vcoppe
3857649baa New translations elevation.mdx (Turkish) 2025-11-12 18:00:16 +01:00
vcoppe
db32681bcf New translations elevation.mdx (Swedish) 2025-11-12 18:00:14 +01:00
vcoppe
0c551a5991 New translations elevation.mdx (Russian) 2025-11-12 18:00:13 +01:00
vcoppe
be39bc80a0 New translations elevation.mdx (Portuguese) 2025-11-12 18:00:11 +01:00
vcoppe
1798cbcc1f New translations elevation.mdx (Polish) 2025-11-12 18:00:09 +01:00
vcoppe
054abd8555 New translations elevation.mdx (Norwegian) 2025-11-12 18:00:08 +01:00
vcoppe
febf949e83 New translations elevation.mdx (Dutch) 2025-11-12 18:00:06 +01:00
vcoppe
712ca963d2 New translations elevation.mdx (Lithuanian) 2025-11-12 18:00:05 +01:00
vcoppe
41c06e9c98 New translations elevation.mdx (Korean) 2025-11-12 18:00:03 +01:00
vcoppe
7bf411672d New translations elevation.mdx (Italian) 2025-11-12 18:00:02 +01:00
vcoppe
3d7f98903f New translations elevation.mdx (Hungarian) 2025-11-12 18:00:00 +01:00
vcoppe
ea744f4979 New translations elevation.mdx (Hebrew) 2025-11-12 17:59:59 +01:00
vcoppe
3ffec22e5e New translations elevation.mdx (Finnish) 2025-11-12 17:59:58 +01:00
vcoppe
243b6cbe79 New translations elevation.mdx (Basque) 2025-11-12 17:59:57 +01:00
vcoppe
79872ac8e4 New translations elevation.mdx (Greek) 2025-11-12 17:59:56 +01:00
vcoppe
0214cad3dd New translations elevation.mdx (German) 2025-11-12 17:59:54 +01:00
vcoppe
818b3e0d44 New translations elevation.mdx (Danish) 2025-11-12 17:59:53 +01:00
vcoppe
b61313ce6c New translations elevation.mdx (Czech) 2025-11-12 17:59:52 +01:00
vcoppe
f511da811b New translations elevation.mdx (Catalan) 2025-11-12 17:59:51 +01:00
vcoppe
b7b1662383 New translations elevation.mdx (Belarusian) 2025-11-12 17:59:50 +01:00
vcoppe
716a28352d New translations elevation.mdx (Spanish) 2025-11-12 17:59:48 +01:00
vcoppe
bf05c28e32 New translations elevation.mdx (French) 2025-11-12 17:59:47 +01:00
vcoppe
0c8f516e39 New translations elevation.mdx (Romanian) 2025-11-12 17:59:46 +01:00
vcoppe
ad7355e976 New translations time.mdx (Serbian (Latin)) 2025-11-12 17:59:31 +01:00
vcoppe
9734bb251b New translations time.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:59:30 +01:00
vcoppe
b1986f68d6 New translations time.mdx (Latvian) 2025-11-12 17:59:29 +01:00
vcoppe
957faced0a New translations time.mdx (Thai) 2025-11-12 17:59:28 +01:00
vcoppe
4e43867ab3 New translations time.mdx (Indonesian) 2025-11-12 17:59:26 +01:00
vcoppe
0b53016675 New translations time.mdx (Portuguese, Brazilian) 2025-11-12 17:59:25 +01:00
vcoppe
04c2411432 New translations time.mdx (Vietnamese) 2025-11-12 17:59:24 +01:00
vcoppe
237e673963 New translations time.mdx (Chinese Simplified) 2025-11-12 17:59:22 +01:00
vcoppe
d48b7c1927 New translations time.mdx (Ukrainian) 2025-11-12 17:59:21 +01:00
vcoppe
39602fa0e9 New translations time.mdx (Turkish) 2025-11-12 17:59:20 +01:00
vcoppe
08704e2eff New translations time.mdx (Swedish) 2025-11-12 17:59:19 +01:00
vcoppe
79178a6514 New translations time.mdx (Russian) 2025-11-12 17:59:17 +01:00
vcoppe
05cfeca5b4 New translations time.mdx (Portuguese) 2025-11-12 17:59:16 +01:00
vcoppe
5b93749051 New translations time.mdx (Polish) 2025-11-12 17:59:15 +01:00
vcoppe
355ef01a38 New translations time.mdx (Norwegian) 2025-11-12 17:59:13 +01:00
vcoppe
c382298c7e New translations time.mdx (Dutch) 2025-11-12 17:59:12 +01:00
vcoppe
b083c6e0a2 New translations time.mdx (Lithuanian) 2025-11-12 17:59:11 +01:00
vcoppe
96275b9e8e New translations time.mdx (Korean) 2025-11-12 17:59:10 +01:00
vcoppe
91ae587254 New translations time.mdx (Italian) 2025-11-12 17:59:09 +01:00
vcoppe
18a8c02e7c New translations time.mdx (Hungarian) 2025-11-12 17:59:07 +01:00
vcoppe
daa2d662de New translations time.mdx (Hebrew) 2025-11-12 17:59:06 +01:00
vcoppe
c0d310fc1d New translations time.mdx (Finnish) 2025-11-12 17:59:05 +01:00
vcoppe
78c4553171 New translations time.mdx (Basque) 2025-11-12 17:59:03 +01:00
vcoppe
3906fe45e0 New translations time.mdx (Greek) 2025-11-12 17:59:02 +01:00
vcoppe
b449397914 New translations time.mdx (German) 2025-11-12 17:59:01 +01:00
vcoppe
c0fa6e34c5 New translations time.mdx (Danish) 2025-11-12 17:59:00 +01:00
vcoppe
c5bb302a15 New translations time.mdx (Czech) 2025-11-12 17:58:58 +01:00
vcoppe
05012179f9 New translations time.mdx (Catalan) 2025-11-12 17:58:57 +01:00
vcoppe
a53c934a1e New translations time.mdx (Belarusian) 2025-11-12 17:58:55 +01:00
vcoppe
da73f9426a New translations time.mdx (Spanish) 2025-11-12 17:58:54 +01:00
vcoppe
b5383122bc New translations time.mdx (French) 2025-11-12 17:58:53 +01:00
vcoppe
0a059c345b New translations time.mdx (Romanian) 2025-11-12 17:58:52 +01:00
vcoppe
07c979df5d New translations scissors.mdx (Serbian (Latin)) 2025-11-12 17:58:50 +01:00
vcoppe
52a5654923 New translations scissors.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:58:49 +01:00
vcoppe
3a86303865 New translations scissors.mdx (Latvian) 2025-11-12 17:58:48 +01:00
vcoppe
7cc06efda1 New translations scissors.mdx (Thai) 2025-11-12 17:58:47 +01:00
vcoppe
b467b69478 New translations scissors.mdx (Indonesian) 2025-11-12 17:58:45 +01:00
vcoppe
c289c316d7 New translations scissors.mdx (Portuguese, Brazilian) 2025-11-12 17:58:44 +01:00
vcoppe
a30c9ae81a New translations scissors.mdx (Vietnamese) 2025-11-12 17:58:43 +01:00
vcoppe
cb9e316a8c New translations scissors.mdx (Chinese Simplified) 2025-11-12 17:58:42 +01:00
vcoppe
f6abbab561 New translations scissors.mdx (Ukrainian) 2025-11-12 17:58:40 +01:00
vcoppe
aae0626a22 New translations scissors.mdx (Turkish) 2025-11-12 17:58:39 +01:00
vcoppe
b39f30fec6 New translations scissors.mdx (Swedish) 2025-11-12 17:58:38 +01:00
vcoppe
f323bc2e25 New translations scissors.mdx (Russian) 2025-11-12 17:58:37 +01:00
vcoppe
67abd55120 New translations scissors.mdx (Portuguese) 2025-11-12 17:58:35 +01:00
vcoppe
5f52964672 New translations scissors.mdx (Polish) 2025-11-12 17:58:34 +01:00
vcoppe
084587fcc1 New translations scissors.mdx (Norwegian) 2025-11-12 17:58:33 +01:00
vcoppe
619f129f0c New translations scissors.mdx (Dutch) 2025-11-12 17:58:32 +01:00
vcoppe
acd2962427 New translations scissors.mdx (Lithuanian) 2025-11-12 17:58:31 +01:00
vcoppe
c9ddb5f3d1 New translations scissors.mdx (Korean) 2025-11-12 17:58:29 +01:00
vcoppe
480056ce86 New translations scissors.mdx (Italian) 2025-11-12 17:58:28 +01:00
vcoppe
cc47635a60 New translations scissors.mdx (Hungarian) 2025-11-12 17:58:27 +01:00
vcoppe
631b0cd3d3 New translations scissors.mdx (Hebrew) 2025-11-12 17:58:26 +01:00
vcoppe
447d416bbb New translations scissors.mdx (Finnish) 2025-11-12 17:58:25 +01:00
vcoppe
05b8dff0a6 New translations scissors.mdx (Basque) 2025-11-12 17:58:24 +01:00
vcoppe
c44e9be42d New translations scissors.mdx (Greek) 2025-11-12 17:58:22 +01:00
vcoppe
366f26ff0b New translations scissors.mdx (German) 2025-11-12 17:58:21 +01:00
vcoppe
dd1865a2cf New translations scissors.mdx (Danish) 2025-11-12 17:58:20 +01:00
vcoppe
359ce2fedc New translations scissors.mdx (Czech) 2025-11-12 17:58:19 +01:00
vcoppe
dc94a33e64 New translations scissors.mdx (Catalan) 2025-11-12 17:58:17 +01:00
vcoppe
8be176b57e New translations scissors.mdx (Belarusian) 2025-11-12 17:58:16 +01:00
vcoppe
984e518163 New translations scissors.mdx (Spanish) 2025-11-12 17:58:15 +01:00
vcoppe
219afd5f1c New translations scissors.mdx (French) 2025-11-12 17:58:14 +01:00
vcoppe
4d0e0c8c95 New translations scissors.mdx (Romanian) 2025-11-12 17:58:13 +01:00
vcoppe
1bfc19cc74 New translations routing.mdx (Serbian (Latin)) 2025-11-12 17:58:11 +01:00
vcoppe
48f0466e5e New translations routing.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:58:10 +01:00
vcoppe
6c004740c9 New translations routing.mdx (Latvian) 2025-11-12 17:58:09 +01:00
vcoppe
4582ddd1db New translations routing.mdx (Thai) 2025-11-12 17:58:08 +01:00
vcoppe
ea254f89a5 New translations routing.mdx (Indonesian) 2025-11-12 17:58:07 +01:00
vcoppe
6d5880ee09 New translations routing.mdx (Portuguese, Brazilian) 2025-11-12 17:58:06 +01:00
vcoppe
f230c5fd84 New translations routing.mdx (Vietnamese) 2025-11-12 17:58:05 +01:00
vcoppe
2326fb3809 New translations routing.mdx (Chinese Simplified) 2025-11-12 17:58:03 +01:00
vcoppe
270df17c67 New translations routing.mdx (Ukrainian) 2025-11-12 17:58:02 +01:00
vcoppe
cbb386e799 New translations routing.mdx (Turkish) 2025-11-12 17:58:01 +01:00
vcoppe
6c7f4933eb New translations routing.mdx (Swedish) 2025-11-12 17:58:00 +01:00
vcoppe
c572570517 New translations routing.mdx (Russian) 2025-11-12 17:57:58 +01:00
vcoppe
72264b76aa New translations routing.mdx (Portuguese) 2025-11-12 17:57:57 +01:00
vcoppe
3cfe444f8d New translations routing.mdx (Polish) 2025-11-12 17:57:56 +01:00
vcoppe
d9b38bab4c New translations routing.mdx (Norwegian) 2025-11-12 17:57:55 +01:00
vcoppe
b39e09bd0c New translations routing.mdx (Dutch) 2025-11-12 17:57:54 +01:00
vcoppe
cd32631a71 New translations routing.mdx (Lithuanian) 2025-11-12 17:57:52 +01:00
vcoppe
ec8e1455a1 New translations routing.mdx (Korean) 2025-11-12 17:57:51 +01:00
vcoppe
cfd9b9946b New translations routing.mdx (Italian) 2025-11-12 17:57:50 +01:00
vcoppe
961daf76f0 New translations routing.mdx (Hungarian) 2025-11-12 17:57:49 +01:00
vcoppe
256ca75851 New translations routing.mdx (Hebrew) 2025-11-12 17:57:47 +01:00
vcoppe
6fa63cca0d New translations routing.mdx (Finnish) 2025-11-12 17:57:46 +01:00
vcoppe
a8730722dd New translations routing.mdx (Basque) 2025-11-12 17:57:45 +01:00
vcoppe
ededaebd39 New translations routing.mdx (Greek) 2025-11-12 17:57:44 +01:00
vcoppe
80d43a85cd New translations routing.mdx (German) 2025-11-12 17:57:42 +01:00
vcoppe
6b2a946ed7 New translations routing.mdx (Danish) 2025-11-12 17:57:41 +01:00
vcoppe
6822e3bed3 New translations routing.mdx (Catalan) 2025-11-12 17:57:40 +01:00
vcoppe
5c9ae9cde6 New translations routing.mdx (Belarusian) 2025-11-12 17:57:39 +01:00
vcoppe
335505bb6e New translations routing.mdx (Spanish) 2025-11-12 17:57:38 +01:00
vcoppe
a153b3379f New translations routing.mdx (French) 2025-11-12 17:57:37 +01:00
vcoppe
04c280560e New translations routing.mdx (Romanian) 2025-11-12 17:57:35 +01:00
vcoppe
31385fb34f New translations poi.mdx (Serbian (Latin)) 2025-11-12 17:57:34 +01:00
vcoppe
3844360614 New translations poi.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:57:33 +01:00
vcoppe
e9f4487035 New translations poi.mdx (Latvian) 2025-11-12 17:57:32 +01:00
vcoppe
b5c50fbbdf New translations poi.mdx (Thai) 2025-11-12 17:57:31 +01:00
vcoppe
ace8928c99 New translations poi.mdx (Indonesian) 2025-11-12 17:57:29 +01:00
vcoppe
de8c3dbcce New translations poi.mdx (Portuguese, Brazilian) 2025-11-12 17:57:28 +01:00
vcoppe
b6df3ad67f New translations poi.mdx (Vietnamese) 2025-11-12 17:57:26 +01:00
vcoppe
edb9950e38 New translations poi.mdx (Chinese Simplified) 2025-11-12 17:57:25 +01:00
vcoppe
2dc06ddbd4 New translations poi.mdx (Ukrainian) 2025-11-12 17:57:24 +01:00
vcoppe
2cca65730f New translations poi.mdx (Turkish) 2025-11-12 17:57:22 +01:00
vcoppe
ff6090f859 New translations poi.mdx (Swedish) 2025-11-12 17:57:21 +01:00
vcoppe
105a705302 New translations poi.mdx (Russian) 2025-11-12 17:57:20 +01:00
vcoppe
2620dcd2f8 New translations poi.mdx (Portuguese) 2025-11-12 17:57:19 +01:00
vcoppe
74e0f260ae New translations poi.mdx (Polish) 2025-11-12 17:57:18 +01:00
vcoppe
64beb4cc52 New translations poi.mdx (Norwegian) 2025-11-12 17:57:16 +01:00
vcoppe
b531fc5df4 New translations poi.mdx (Dutch) 2025-11-12 17:57:15 +01:00
vcoppe
300ea4b77f New translations poi.mdx (Lithuanian) 2025-11-12 17:57:14 +01:00
vcoppe
309d60fb46 New translations poi.mdx (Korean) 2025-11-12 17:57:13 +01:00
vcoppe
f8606a7453 New translations poi.mdx (Italian) 2025-11-12 17:57:11 +01:00
vcoppe
d7fc332d91 New translations poi.mdx (Hungarian) 2025-11-12 17:57:10 +01:00
vcoppe
bc14935be3 New translations poi.mdx (Hebrew) 2025-11-12 17:57:09 +01:00
vcoppe
6b38430822 New translations poi.mdx (Finnish) 2025-11-12 17:57:08 +01:00
vcoppe
4fb2cd1805 New translations poi.mdx (Basque) 2025-11-12 17:57:07 +01:00
vcoppe
e265d6b561 New translations poi.mdx (Greek) 2025-11-12 17:57:05 +01:00
vcoppe
b6cb891858 New translations poi.mdx (German) 2025-11-12 17:57:04 +01:00
vcoppe
09a9c2ef17 New translations poi.mdx (Danish) 2025-11-12 17:57:03 +01:00
vcoppe
3cb9498c9b New translations poi.mdx (Czech) 2025-11-12 17:57:02 +01:00
vcoppe
1991d88c03 New translations poi.mdx (Catalan) 2025-11-12 17:57:00 +01:00
vcoppe
9e5fa15630 New translations poi.mdx (Belarusian) 2025-11-12 17:56:59 +01:00
vcoppe
d3be51fdcd New translations poi.mdx (Spanish) 2025-11-12 17:56:58 +01:00
vcoppe
8ae62558be New translations poi.mdx (French) 2025-11-12 17:56:57 +01:00
vcoppe
cf0d82a4d2 New translations poi.mdx (Romanian) 2025-11-12 17:56:56 +01:00
vcoppe
b6aa4738cf New translations minify.mdx (Serbian (Latin)) 2025-11-12 17:56:54 +01:00
vcoppe
f73de02f87 New translations minify.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:56:53 +01:00
vcoppe
383bf649c3 New translations minify.mdx (Latvian) 2025-11-12 17:56:52 +01:00
vcoppe
7fc4826f43 New translations minify.mdx (Thai) 2025-11-12 17:56:51 +01:00
vcoppe
4b8fd2f25c New translations minify.mdx (Indonesian) 2025-11-12 17:56:50 +01:00
vcoppe
9dee07a534 New translations minify.mdx (Portuguese, Brazilian) 2025-11-12 17:56:49 +01:00
vcoppe
d476f0991e New translations minify.mdx (Vietnamese) 2025-11-12 17:56:47 +01:00
vcoppe
8da76ebf95 New translations minify.mdx (Chinese Simplified) 2025-11-12 17:56:46 +01:00
vcoppe
0db93bf1f7 New translations minify.mdx (Ukrainian) 2025-11-12 17:56:45 +01:00
vcoppe
7480d5e2d5 New translations minify.mdx (Turkish) 2025-11-12 17:56:44 +01:00
vcoppe
88da695b9f New translations minify.mdx (Swedish) 2025-11-12 17:56:43 +01:00
vcoppe
54cc725550 New translations minify.mdx (Russian) 2025-11-12 17:56:42 +01:00
vcoppe
bf299220fe New translations minify.mdx (Portuguese) 2025-11-12 17:56:40 +01:00
vcoppe
c726aefa87 New translations minify.mdx (Polish) 2025-11-12 17:56:39 +01:00
vcoppe
15a5d9a457 New translations minify.mdx (Norwegian) 2025-11-12 17:56:38 +01:00
vcoppe
e01223f985 New translations minify.mdx (Dutch) 2025-11-12 17:56:37 +01:00
vcoppe
ab6389901e New translations minify.mdx (Lithuanian) 2025-11-12 17:56:35 +01:00
vcoppe
281fec42cb New translations minify.mdx (Korean) 2025-11-12 17:56:34 +01:00
vcoppe
ec0f463be9 New translations minify.mdx (Italian) 2025-11-12 17:56:33 +01:00
vcoppe
a51a616181 New translations minify.mdx (Hungarian) 2025-11-12 17:56:32 +01:00
vcoppe
11a8ed3388 New translations minify.mdx (Hebrew) 2025-11-12 17:56:31 +01:00
vcoppe
1870ca9a6c New translations minify.mdx (Finnish) 2025-11-12 17:56:29 +01:00
vcoppe
4bc6ec4eea New translations minify.mdx (Basque) 2025-11-12 17:56:28 +01:00
vcoppe
82ba54e592 New translations minify.mdx (Greek) 2025-11-12 17:56:27 +01:00
vcoppe
3ef4a6d9b7 New translations minify.mdx (German) 2025-11-12 17:56:26 +01:00
vcoppe
d724799258 New translations minify.mdx (Danish) 2025-11-12 17:56:24 +01:00
vcoppe
e0cda2a2c0 New translations minify.mdx (Czech) 2025-11-12 17:56:23 +01:00
vcoppe
118584debb New translations minify.mdx (Catalan) 2025-11-12 17:56:22 +01:00
vcoppe
fb9c242972 New translations minify.mdx (Belarusian) 2025-11-12 17:56:20 +01:00
vcoppe
448838fde4 New translations minify.mdx (Spanish) 2025-11-12 17:56:19 +01:00
vcoppe
a6c4332854 New translations minify.mdx (French) 2025-11-12 17:56:18 +01:00
vcoppe
f55e2eb1f3 New translations minify.mdx (Romanian) 2025-11-12 17:56:17 +01:00
vcoppe
cfc5a00248 New translations merge.mdx (Serbian (Latin)) 2025-11-12 17:56:15 +01:00
vcoppe
8c508b0ca5 New translations merge.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:56:14 +01:00
vcoppe
67d6fe326f New translations merge.mdx (Latvian) 2025-11-12 17:56:12 +01:00
vcoppe
eadc9a62ce New translations merge.mdx (Thai) 2025-11-12 17:56:11 +01:00
vcoppe
b2b68316f2 New translations merge.mdx (Indonesian) 2025-11-12 17:56:10 +01:00
vcoppe
fac8938d0a New translations merge.mdx (Portuguese, Brazilian) 2025-11-12 17:56:09 +01:00
vcoppe
0f8c37ff70 New translations merge.mdx (Vietnamese) 2025-11-12 17:56:08 +01:00
vcoppe
fc004fd212 New translations merge.mdx (Chinese Simplified) 2025-11-12 17:56:06 +01:00
vcoppe
680a18f737 New translations merge.mdx (Ukrainian) 2025-11-12 17:56:05 +01:00
vcoppe
9b8533352b New translations merge.mdx (Turkish) 2025-11-12 17:56:04 +01:00
vcoppe
0761e6249d New translations merge.mdx (Swedish) 2025-11-12 17:56:03 +01:00
vcoppe
6b6636bc16 New translations merge.mdx (Russian) 2025-11-12 17:56:02 +01:00
vcoppe
f2da0e9baf New translations merge.mdx (Portuguese) 2025-11-12 17:56:00 +01:00
vcoppe
e82683a5e5 New translations merge.mdx (Polish) 2025-11-12 17:55:59 +01:00
vcoppe
9c59f27849 New translations merge.mdx (Norwegian) 2025-11-12 17:55:58 +01:00
vcoppe
6113af1ff5 New translations merge.mdx (Dutch) 2025-11-12 17:55:57 +01:00
vcoppe
1d535c4bba New translations merge.mdx (Lithuanian) 2025-11-12 17:55:56 +01:00
vcoppe
8cc022db94 New translations merge.mdx (Korean) 2025-11-12 17:55:54 +01:00
vcoppe
4a6e795595 New translations merge.mdx (Italian) 2025-11-12 17:55:53 +01:00
vcoppe
9453f65991 New translations merge.mdx (Hungarian) 2025-11-12 17:55:52 +01:00
vcoppe
7d5fa67aba New translations merge.mdx (Hebrew) 2025-11-12 17:55:50 +01:00
vcoppe
c5488d2f9b New translations merge.mdx (Finnish) 2025-11-12 17:55:49 +01:00
vcoppe
f1740d122e New translations merge.mdx (Basque) 2025-11-12 17:55:48 +01:00
vcoppe
d54eb810ae New translations merge.mdx (Greek) 2025-11-12 17:55:47 +01:00
vcoppe
cc77fbf7d1 New translations merge.mdx (German) 2025-11-12 17:55:46 +01:00
vcoppe
7e79a1af45 New translations merge.mdx (Danish) 2025-11-12 17:55:44 +01:00
vcoppe
0fd5929d33 New translations merge.mdx (Czech) 2025-11-12 17:55:43 +01:00
vcoppe
bfc2e87cbd New translations merge.mdx (Catalan) 2025-11-12 17:55:42 +01:00
vcoppe
6de7b71e5e New translations merge.mdx (Belarusian) 2025-11-12 17:55:41 +01:00
vcoppe
6773b21e70 New translations merge.mdx (Spanish) 2025-11-12 17:55:40 +01:00
vcoppe
da2538de26 New translations merge.mdx (French) 2025-11-12 17:55:38 +01:00
vcoppe
5f055d3c5e New translations merge.mdx (Romanian) 2025-11-12 17:55:37 +01:00
vcoppe
a53df616be New translations extract.mdx (Serbian (Latin)) 2025-11-12 17:55:36 +01:00
vcoppe
7d55a86209 New translations extract.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:55:34 +01:00
vcoppe
d0d0115ee7 New translations extract.mdx (Latvian) 2025-11-12 17:55:33 +01:00
vcoppe
630efd4ca2 New translations extract.mdx (Thai) 2025-11-12 17:55:32 +01:00
vcoppe
95f956e12c New translations extract.mdx (Indonesian) 2025-11-12 17:55:31 +01:00
vcoppe
26602696ce New translations extract.mdx (Portuguese, Brazilian) 2025-11-12 17:55:30 +01:00
vcoppe
65c5d4d950 New translations extract.mdx (Vietnamese) 2025-11-12 17:55:28 +01:00
vcoppe
765f79b7fd New translations extract.mdx (Chinese Simplified) 2025-11-12 17:55:27 +01:00
vcoppe
f822939633 New translations extract.mdx (Ukrainian) 2025-11-12 17:55:26 +01:00
vcoppe
d186843ba2 New translations extract.mdx (Turkish) 2025-11-12 17:55:25 +01:00
vcoppe
ba7ec47a07 New translations extract.mdx (Swedish) 2025-11-12 17:55:23 +01:00
vcoppe
7ce0adc245 New translations extract.mdx (Russian) 2025-11-12 17:55:22 +01:00
vcoppe
6c9a93bc75 New translations extract.mdx (Portuguese) 2025-11-12 17:55:21 +01:00
vcoppe
a09bf4dfa9 New translations extract.mdx (Polish) 2025-11-12 17:55:20 +01:00
vcoppe
6de834b55a New translations extract.mdx (Norwegian) 2025-11-12 17:55:19 +01:00
vcoppe
b2447718e3 New translations extract.mdx (Dutch) 2025-11-12 17:55:18 +01:00
vcoppe
e3e1746d42 New translations extract.mdx (Lithuanian) 2025-11-12 17:55:16 +01:00
vcoppe
e013c3aa92 New translations extract.mdx (Korean) 2025-11-12 17:55:15 +01:00
vcoppe
8d38cc7efe New translations extract.mdx (Italian) 2025-11-12 17:55:14 +01:00
vcoppe
37f17968b7 New translations extract.mdx (Hungarian) 2025-11-12 17:55:13 +01:00
vcoppe
2a2789dcba New translations extract.mdx (Hebrew) 2025-11-12 17:55:11 +01:00
vcoppe
b36e2b8d82 New translations extract.mdx (Finnish) 2025-11-12 17:55:10 +01:00
vcoppe
22dbe9d07d New translations extract.mdx (Basque) 2025-11-12 17:55:09 +01:00
vcoppe
2fb28a5a4a New translations extract.mdx (Greek) 2025-11-12 17:55:08 +01:00
vcoppe
0ea0e9992e New translations extract.mdx (German) 2025-11-12 17:55:07 +01:00
vcoppe
0f2f9703b2 New translations extract.mdx (Danish) 2025-11-12 17:55:05 +01:00
vcoppe
403f018b43 New translations extract.mdx (Czech) 2025-11-12 17:55:04 +01:00
vcoppe
35033bf439 New translations extract.mdx (Catalan) 2025-11-12 17:55:03 +01:00
vcoppe
be30315718 New translations extract.mdx (Belarusian) 2025-11-12 17:55:01 +01:00
vcoppe
046e7382cf New translations extract.mdx (Spanish) 2025-11-12 17:55:00 +01:00
vcoppe
ad4a0485c4 New translations extract.mdx (French) 2025-11-12 17:54:59 +01:00
vcoppe
c6aface832 New translations extract.mdx (Romanian) 2025-11-12 17:54:58 +01:00
vcoppe
651e1e9203 New translations clean.mdx (Serbian (Latin)) 2025-11-12 17:54:57 +01:00
vcoppe
e3dcf6eb87 New translations clean.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:54:56 +01:00
vcoppe
15d8b22025 New translations clean.mdx (Latvian) 2025-11-12 17:54:54 +01:00
vcoppe
700c8b565f New translations clean.mdx (Thai) 2025-11-12 17:54:53 +01:00
vcoppe
a0a888b31f New translations clean.mdx (Indonesian) 2025-11-12 17:54:52 +01:00
vcoppe
32f99c6c73 New translations clean.mdx (Portuguese, Brazilian) 2025-11-12 17:54:51 +01:00
vcoppe
d646a05222 New translations clean.mdx (Vietnamese) 2025-11-12 17:54:49 +01:00
vcoppe
3b2e49b524 New translations clean.mdx (Chinese Simplified) 2025-11-12 17:54:48 +01:00
vcoppe
5b1d415149 New translations clean.mdx (Ukrainian) 2025-11-12 17:54:47 +01:00
vcoppe
03cb33ed04 New translations clean.mdx (Turkish) 2025-11-12 17:54:46 +01:00
vcoppe
3aff104429 New translations clean.mdx (Swedish) 2025-11-12 17:54:44 +01:00
vcoppe
50c4a27ce8 New translations clean.mdx (Russian) 2025-11-12 17:54:43 +01:00
vcoppe
00ca6db8fc New translations clean.mdx (Portuguese) 2025-11-12 17:54:42 +01:00
vcoppe
1f43ccd2f2 New translations clean.mdx (Polish) 2025-11-12 17:54:40 +01:00
vcoppe
d86bd0a26b New translations clean.mdx (Norwegian) 2025-11-12 17:54:39 +01:00
vcoppe
f85bc96f73 New translations clean.mdx (Dutch) 2025-11-12 17:54:38 +01:00
vcoppe
96f89e5329 New translations clean.mdx (Lithuanian) 2025-11-12 17:54:36 +01:00
vcoppe
d2fea652e4 New translations clean.mdx (Korean) 2025-11-12 17:54:35 +01:00
vcoppe
ffb1e23ac0 New translations clean.mdx (Italian) 2025-11-12 17:54:34 +01:00
vcoppe
5ef0316099 New translations clean.mdx (Hungarian) 2025-11-12 17:54:33 +01:00
vcoppe
42168c2cd5 New translations clean.mdx (Hebrew) 2025-11-12 17:54:31 +01:00
vcoppe
5133bf3768 New translations clean.mdx (Finnish) 2025-11-12 17:54:30 +01:00
vcoppe
f1497a0caa New translations clean.mdx (Basque) 2025-11-12 17:54:29 +01:00
vcoppe
0fd7cd5c07 New translations clean.mdx (Greek) 2025-11-12 17:54:28 +01:00
vcoppe
48c35dcf17 New translations clean.mdx (German) 2025-11-12 17:54:27 +01:00
vcoppe
c5982f725c New translations clean.mdx (Danish) 2025-11-12 17:54:26 +01:00
vcoppe
063e567a09 New translations clean.mdx (Czech) 2025-11-12 17:54:24 +01:00
vcoppe
a0b067d92e New translations clean.mdx (Catalan) 2025-11-12 17:54:23 +01:00
vcoppe
47d4c9cb20 New translations clean.mdx (Belarusian) 2025-11-12 17:54:22 +01:00
vcoppe
585ef679f4 New translations clean.mdx (Spanish) 2025-11-12 17:54:21 +01:00
vcoppe
62f5851e25 New translations clean.mdx (French) 2025-11-12 17:54:19 +01:00
vcoppe
a6f81c03d2 New translations clean.mdx (Romanian) 2025-11-12 17:54:18 +01:00
vcoppe
70192b2cb9 New translations view.mdx (Serbian (Latin)) 2025-11-12 17:54:03 +01:00
vcoppe
9578aee988 New translations view.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:54:02 +01:00
vcoppe
f91dee0074 New translations view.mdx (Latvian) 2025-11-12 17:54:01 +01:00
vcoppe
fd2ebd10f5 New translations view.mdx (Thai) 2025-11-12 17:53:59 +01:00
vcoppe
d8db4d2a73 New translations view.mdx (Indonesian) 2025-11-12 17:53:58 +01:00
vcoppe
ebccd487be New translations view.mdx (Portuguese, Brazilian) 2025-11-12 17:53:57 +01:00
vcoppe
42a1a73dbf New translations view.mdx (Vietnamese) 2025-11-12 17:53:56 +01:00
vcoppe
7b543b65a6 New translations view.mdx (Chinese Simplified) 2025-11-12 17:53:54 +01:00
vcoppe
9d870faa1e New translations view.mdx (Ukrainian) 2025-11-12 17:53:53 +01:00
vcoppe
b514595b24 New translations view.mdx (Turkish) 2025-11-12 17:53:52 +01:00
vcoppe
ea6d9783fd New translations view.mdx (Swedish) 2025-11-12 17:53:50 +01:00
vcoppe
6686d48249 New translations view.mdx (Russian) 2025-11-12 17:53:49 +01:00
vcoppe
94a243b3b9 New translations view.mdx (Portuguese) 2025-11-12 17:53:48 +01:00
vcoppe
284fec9857 New translations view.mdx (Polish) 2025-11-12 17:53:47 +01:00
vcoppe
36b0b82625 New translations view.mdx (Norwegian) 2025-11-12 17:53:45 +01:00
vcoppe
0f0628024f New translations view.mdx (Dutch) 2025-11-12 17:53:44 +01:00
vcoppe
3633448af5 New translations view.mdx (Lithuanian) 2025-11-12 17:53:43 +01:00
vcoppe
ef500ac6f7 New translations view.mdx (Korean) 2025-11-12 17:53:41 +01:00
vcoppe
2678ce6948 New translations view.mdx (Italian) 2025-11-12 17:53:40 +01:00
vcoppe
1f2bca7271 New translations view.mdx (Hungarian) 2025-11-12 17:53:39 +01:00
vcoppe
ff31897603 New translations view.mdx (Hebrew) 2025-11-12 17:53:38 +01:00
vcoppe
930930fc43 New translations view.mdx (Finnish) 2025-11-12 17:53:37 +01:00
vcoppe
158120e396 New translations view.mdx (Basque) 2025-11-12 17:53:35 +01:00
vcoppe
ae61d76e3f New translations view.mdx (Greek) 2025-11-12 17:53:34 +01:00
vcoppe
3df66d5653 New translations view.mdx (German) 2025-11-12 17:53:33 +01:00
vcoppe
f10a27f882 New translations view.mdx (Danish) 2025-11-12 17:53:32 +01:00
vcoppe
88b776b415 New translations view.mdx (Czech) 2025-11-12 17:53:31 +01:00
vcoppe
d3e0f65e9e New translations view.mdx (Catalan) 2025-11-12 17:53:29 +01:00
vcoppe
666554bf18 New translations view.mdx (Belarusian) 2025-11-12 17:53:28 +01:00
vcoppe
fc722177e8 New translations view.mdx (Spanish) 2025-11-12 17:53:27 +01:00
vcoppe
d5a3f32811 New translations view.mdx (French) 2025-11-12 17:53:26 +01:00
vcoppe
7bbbe8d861 New translations view.mdx (Romanian) 2025-11-12 17:53:25 +01:00
vcoppe
ddc6f2e10b New translations settings.mdx (Serbian (Latin)) 2025-11-12 17:53:24 +01:00
vcoppe
ff78e98ae2 New translations settings.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:53:22 +01:00
vcoppe
ecf54ea6da New translations settings.mdx (Latvian) 2025-11-12 17:53:21 +01:00
vcoppe
ca0e1110ca New translations settings.mdx (Thai) 2025-11-12 17:53:20 +01:00
vcoppe
ca128d332f New translations settings.mdx (Indonesian) 2025-11-12 17:53:19 +01:00
vcoppe
ad027cb58c New translations settings.mdx (Portuguese, Brazilian) 2025-11-12 17:53:18 +01:00
vcoppe
ff7f1d7f67 New translations settings.mdx (Vietnamese) 2025-11-12 17:53:16 +01:00
vcoppe
ded9f33313 New translations settings.mdx (Chinese Simplified) 2025-11-12 17:53:15 +01:00
vcoppe
ccbc9211ac New translations settings.mdx (Ukrainian) 2025-11-12 17:53:14 +01:00
vcoppe
85684fe415 New translations settings.mdx (Turkish) 2025-11-12 17:53:13 +01:00
vcoppe
aeb60c50ee New translations settings.mdx (Swedish) 2025-11-12 17:53:12 +01:00
vcoppe
f81405e409 New translations settings.mdx (Russian) 2025-11-12 17:53:11 +01:00
vcoppe
294d85e33b New translations settings.mdx (Portuguese) 2025-11-12 17:53:09 +01:00
vcoppe
2d52865487 New translations settings.mdx (Polish) 2025-11-12 17:53:08 +01:00
vcoppe
cdba2179ff New translations settings.mdx (Norwegian) 2025-11-12 17:53:07 +01:00
vcoppe
60a521373d New translations settings.mdx (Dutch) 2025-11-12 17:53:06 +01:00
vcoppe
1bb65ea66f New translations settings.mdx (Lithuanian) 2025-11-12 17:53:05 +01:00
vcoppe
d0ceecbf51 New translations settings.mdx (Korean) 2025-11-12 17:53:03 +01:00
vcoppe
57af9b9ece New translations settings.mdx (Italian) 2025-11-12 17:53:02 +01:00
vcoppe
27d10bf9fe New translations settings.mdx (Hungarian) 2025-11-12 17:53:01 +01:00
vcoppe
0ee0814779 New translations settings.mdx (Hebrew) 2025-11-12 17:53:00 +01:00
vcoppe
384af64599 New translations settings.mdx (Finnish) 2025-11-12 17:52:58 +01:00
vcoppe
a4f06e5196 New translations settings.mdx (Basque) 2025-11-12 17:52:57 +01:00
vcoppe
150b7878fc New translations settings.mdx (Greek) 2025-11-12 17:52:56 +01:00
vcoppe
da83cc1649 New translations settings.mdx (German) 2025-11-12 17:52:54 +01:00
vcoppe
5be4e6816d New translations settings.mdx (Danish) 2025-11-12 17:52:53 +01:00
vcoppe
9769d7f9ec New translations settings.mdx (Czech) 2025-11-12 17:52:52 +01:00
vcoppe
d7b843db26 New translations settings.mdx (Catalan) 2025-11-12 17:52:50 +01:00
vcoppe
23ee01a8e9 New translations settings.mdx (Belarusian) 2025-11-12 17:52:49 +01:00
vcoppe
adc2197d98 New translations settings.mdx (Spanish) 2025-11-12 17:52:48 +01:00
vcoppe
f5c95fe0f4 New translations settings.mdx (French) 2025-11-12 17:52:47 +01:00
vcoppe
38523f1c42 New translations settings.mdx (Romanian) 2025-11-12 17:52:45 +01:00
vcoppe
747d3b28ce New translations file.mdx (Serbian (Latin)) 2025-11-12 17:52:44 +01:00
vcoppe
a63683d894 New translations file.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:52:43 +01:00
vcoppe
13907f2b5c New translations file.mdx (Latvian) 2025-11-12 17:52:41 +01:00
vcoppe
9d448cb8d4 New translations file.mdx (Thai) 2025-11-12 17:52:40 +01:00
vcoppe
7647ca0762 New translations file.mdx (Indonesian) 2025-11-12 17:52:39 +01:00
vcoppe
da9021c3ff New translations file.mdx (Portuguese, Brazilian) 2025-11-12 17:52:38 +01:00
vcoppe
5a8232167a New translations file.mdx (Vietnamese) 2025-11-12 17:52:37 +01:00
vcoppe
f937c9750d New translations file.mdx (Chinese Simplified) 2025-11-12 17:52:35 +01:00
vcoppe
65a10b2daa New translations file.mdx (Ukrainian) 2025-11-12 17:52:34 +01:00
vcoppe
20c87a14aa New translations file.mdx (Turkish) 2025-11-12 17:52:33 +01:00
vcoppe
f77571b7ef New translations file.mdx (Swedish) 2025-11-12 17:52:32 +01:00
vcoppe
9e95c4b3e1 New translations file.mdx (Russian) 2025-11-12 17:52:30 +01:00
vcoppe
92e46ec540 New translations file.mdx (Portuguese) 2025-11-12 17:52:29 +01:00
vcoppe
1713437813 New translations file.mdx (Polish) 2025-11-12 17:52:28 +01:00
vcoppe
5bf88a34f7 New translations file.mdx (Norwegian) 2025-11-12 17:52:26 +01:00
vcoppe
75c6a2facf New translations file.mdx (Dutch) 2025-11-12 17:52:25 +01:00
vcoppe
3c728115b8 New translations file.mdx (Lithuanian) 2025-11-12 17:52:24 +01:00
vcoppe
51d56dcf7a New translations file.mdx (Korean) 2025-11-12 17:52:23 +01:00
vcoppe
6fe0aa3ee6 New translations file.mdx (Italian) 2025-11-12 17:52:21 +01:00
vcoppe
670bccd2aa New translations file.mdx (Hungarian) 2025-11-12 17:52:20 +01:00
vcoppe
7cb253565e New translations file.mdx (Hebrew) 2025-11-12 17:52:19 +01:00
vcoppe
54e1900e6d New translations file.mdx (Finnish) 2025-11-12 17:52:18 +01:00
vcoppe
d83194b225 New translations file.mdx (Basque) 2025-11-12 17:52:16 +01:00
vcoppe
82eb9a1dc9 New translations file.mdx (Greek) 2025-11-12 17:52:15 +01:00
vcoppe
d298e51a6c New translations file.mdx (German) 2025-11-12 17:52:14 +01:00
vcoppe
2b7e3a2b5d New translations file.mdx (Danish) 2025-11-12 17:52:12 +01:00
vcoppe
0644952db3 New translations file.mdx (Czech) 2025-11-12 17:52:11 +01:00
vcoppe
08e5dc382f New translations file.mdx (Catalan) 2025-11-12 17:52:10 +01:00
vcoppe
afd3d5e8dc New translations file.mdx (Belarusian) 2025-11-12 17:52:09 +01:00
vcoppe
b2fa2d8685 New translations file.mdx (Spanish) 2025-11-12 17:52:07 +01:00
vcoppe
f8e5ccc5bb New translations file.mdx (French) 2025-11-12 17:52:06 +01:00
vcoppe
63b56166cb New translations file.mdx (Romanian) 2025-11-12 17:52:05 +01:00
vcoppe
357791d17c New translations edit.mdx (Serbian (Latin)) 2025-11-12 17:52:04 +01:00
vcoppe
d095f6734d New translations edit.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:52:03 +01:00
vcoppe
ea8771d6f5 New translations edit.mdx (Latvian) 2025-11-12 17:52:01 +01:00
vcoppe
c8e28127c3 New translations edit.mdx (Thai) 2025-11-12 17:52:00 +01:00
vcoppe
cae013a68d New translations edit.mdx (Indonesian) 2025-11-12 17:51:59 +01:00
vcoppe
caa56ee16b New translations edit.mdx (Portuguese, Brazilian) 2025-11-12 17:51:57 +01:00
vcoppe
80e11a7723 New translations edit.mdx (Vietnamese) 2025-11-12 17:51:56 +01:00
vcoppe
87b3126eb0 New translations edit.mdx (Chinese Simplified) 2025-11-12 17:51:55 +01:00
vcoppe
8388c8db23 New translations edit.mdx (Ukrainian) 2025-11-12 17:51:54 +01:00
vcoppe
2bce1dca84 New translations edit.mdx (Turkish) 2025-11-12 17:51:52 +01:00
vcoppe
749be1eb73 New translations edit.mdx (Swedish) 2025-11-12 17:51:51 +01:00
vcoppe
23b74a1d63 New translations edit.mdx (Russian) 2025-11-12 17:51:50 +01:00
vcoppe
b2f10c2f68 New translations edit.mdx (Portuguese) 2025-11-12 17:51:49 +01:00
vcoppe
2aa7f12514 New translations edit.mdx (Polish) 2025-11-12 17:51:47 +01:00
vcoppe
cea8cfbfa6 New translations edit.mdx (Norwegian) 2025-11-12 17:51:46 +01:00
vcoppe
e15275d96d New translations edit.mdx (Dutch) 2025-11-12 17:51:44 +01:00
vcoppe
3b6fb4d170 New translations edit.mdx (Lithuanian) 2025-11-12 17:51:43 +01:00
vcoppe
05c83c63e7 New translations edit.mdx (Korean) 2025-11-12 17:51:42 +01:00
vcoppe
470ca982a1 New translations edit.mdx (Italian) 2025-11-12 17:51:41 +01:00
vcoppe
ec62477937 New translations edit.mdx (Hungarian) 2025-11-12 17:51:40 +01:00
vcoppe
fed8474dce New translations edit.mdx (Hebrew) 2025-11-12 17:51:38 +01:00
vcoppe
7dc93302bf New translations edit.mdx (Finnish) 2025-11-12 17:51:37 +01:00
vcoppe
3105c444ce New translations edit.mdx (Basque) 2025-11-12 17:51:36 +01:00
vcoppe
a2cae2e086 New translations edit.mdx (Greek) 2025-11-12 17:51:35 +01:00
vcoppe
cd4320fe08 New translations edit.mdx (German) 2025-11-12 17:51:33 +01:00
vcoppe
32b07b0e32 New translations edit.mdx (Danish) 2025-11-12 17:51:32 +01:00
vcoppe
3d6234d5b9 New translations edit.mdx (Czech) 2025-11-12 17:51:31 +01:00
vcoppe
2463c8b40a New translations edit.mdx (Catalan) 2025-11-12 17:51:30 +01:00
vcoppe
0a9206b1d3 New translations edit.mdx (Belarusian) 2025-11-12 17:51:28 +01:00
vcoppe
7cd1c0dd69 New translations edit.mdx (Spanish) 2025-11-12 17:51:27 +01:00
vcoppe
3111927f02 New translations edit.mdx (French) 2025-11-12 17:51:26 +01:00
vcoppe
c471eb160c New translations edit.mdx (Romanian) 2025-11-12 17:51:24 +01:00
vcoppe
c983abb49f New translations map-controls.mdx (Serbian (Latin)) 2025-11-12 17:51:10 +01:00
vcoppe
5195e4f591 New translations map-controls.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:51:08 +01:00
vcoppe
538d04824d New translations map-controls.mdx (Latvian) 2025-11-12 17:51:06 +01:00
vcoppe
4666fa418d New translations map-controls.mdx (Thai) 2025-11-12 17:51:05 +01:00
vcoppe
87ce67c326 New translations map-controls.mdx (Indonesian) 2025-11-12 17:51:04 +01:00
vcoppe
6eaec189e6 New translations map-controls.mdx (Portuguese, Brazilian) 2025-11-12 17:51:03 +01:00
vcoppe
6f342951ce New translations map-controls.mdx (Vietnamese) 2025-11-12 17:51:01 +01:00
vcoppe
aacab6e62c New translations map-controls.mdx (Chinese Simplified) 2025-11-12 17:51:00 +01:00
vcoppe
cc7a07e872 New translations map-controls.mdx (Ukrainian) 2025-11-12 17:50:59 +01:00
vcoppe
9127031ac9 New translations map-controls.mdx (Turkish) 2025-11-12 17:50:58 +01:00
vcoppe
9aa654e69d New translations map-controls.mdx (Swedish) 2025-11-12 17:50:56 +01:00
vcoppe
573d4e58bb New translations map-controls.mdx (Russian) 2025-11-12 17:50:55 +01:00
vcoppe
e7c96e3b19 New translations map-controls.mdx (Portuguese) 2025-11-12 17:50:54 +01:00
vcoppe
c9bd41e58e New translations map-controls.mdx (Polish) 2025-11-12 17:50:53 +01:00
vcoppe
5027903820 New translations map-controls.mdx (Norwegian) 2025-11-12 17:50:51 +01:00
vcoppe
767ebc573a New translations map-controls.mdx (Dutch) 2025-11-12 17:50:50 +01:00
vcoppe
5a3d66c885 New translations map-controls.mdx (Lithuanian) 2025-11-12 17:50:49 +01:00
vcoppe
e84e6bd6c5 New translations map-controls.mdx (Korean) 2025-11-12 17:50:48 +01:00
vcoppe
bee6a7dacc New translations map-controls.mdx (Italian) 2025-11-12 17:50:47 +01:00
vcoppe
9876c7506c New translations map-controls.mdx (Hungarian) 2025-11-12 17:50:45 +01:00
vcoppe
afb477d155 New translations map-controls.mdx (Hebrew) 2025-11-12 17:50:44 +01:00
vcoppe
2c9a8491ca New translations map-controls.mdx (Finnish) 2025-11-12 17:50:43 +01:00
vcoppe
f6a9b37cc2 New translations map-controls.mdx (Basque) 2025-11-12 17:50:41 +01:00
vcoppe
1852214758 New translations map-controls.mdx (Greek) 2025-11-12 17:50:40 +01:00
vcoppe
a404591c57 New translations map-controls.mdx (German) 2025-11-12 17:50:39 +01:00
vcoppe
406f8012fc New translations map-controls.mdx (Danish) 2025-11-12 17:50:38 +01:00
vcoppe
9de5fa1819 New translations map-controls.mdx (Czech) 2025-11-12 17:50:36 +01:00
vcoppe
da7862546c New translations map-controls.mdx (Catalan) 2025-11-12 17:50:35 +01:00
vcoppe
8314ee0c30 New translations map-controls.mdx (Belarusian) 2025-11-12 17:50:34 +01:00
vcoppe
ba0da25c6c New translations map-controls.mdx (Spanish) 2025-11-12 17:50:32 +01:00
vcoppe
9b432cef1b New translations map-controls.mdx (French) 2025-11-12 17:50:31 +01:00
vcoppe
ada6adaadf New translations map-controls.mdx (Romanian) 2025-11-12 17:50:30 +01:00
vcoppe
4fe7d72e65 New translations translation.mdx (Serbian (Latin)) 2025-11-12 17:50:16 +01:00
vcoppe
aafc5df915 New translations translation.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:50:15 +01:00
vcoppe
d4cbaeb1e7 New translations translation.mdx (Latvian) 2025-11-12 17:50:13 +01:00
vcoppe
36300aaa45 New translations translation.mdx (Thai) 2025-11-12 17:50:12 +01:00
vcoppe
693800e0b8 New translations translation.mdx (Indonesian) 2025-11-12 17:50:11 +01:00
vcoppe
245c0eecc6 New translations translation.mdx (Portuguese, Brazilian) 2025-11-12 17:50:10 +01:00
vcoppe
25fe02602f New translations translation.mdx (Vietnamese) 2025-11-12 17:50:09 +01:00
vcoppe
5524ddc6a0 New translations translation.mdx (Chinese Simplified) 2025-11-12 17:50:07 +01:00
vcoppe
2fabbec217 New translations translation.mdx (Ukrainian) 2025-11-12 17:50:06 +01:00
vcoppe
3c080873e5 New translations translation.mdx (Turkish) 2025-11-12 17:50:05 +01:00
vcoppe
f252afb29f New translations translation.mdx (Swedish) 2025-11-12 17:50:04 +01:00
vcoppe
3109c0002f New translations translation.mdx (Russian) 2025-11-12 17:50:02 +01:00
vcoppe
059d7d6ae5 New translations translation.mdx (Portuguese) 2025-11-12 17:50:01 +01:00
vcoppe
58f159c0c9 New translations translation.mdx (Polish) 2025-11-12 17:50:00 +01:00
vcoppe
5a6c115121 New translations translation.mdx (Norwegian) 2025-11-12 17:49:59 +01:00
vcoppe
4cb85a543c New translations translation.mdx (Dutch) 2025-11-12 17:49:58 +01:00
vcoppe
d99c3e8f43 New translations translation.mdx (Lithuanian) 2025-11-12 17:49:56 +01:00
vcoppe
a2908b00ef New translations translation.mdx (Korean) 2025-11-12 17:49:55 +01:00
vcoppe
7b88e161e4 New translations translation.mdx (Italian) 2025-11-12 17:49:54 +01:00
vcoppe
96ea0d6ec0 New translations translation.mdx (Hungarian) 2025-11-12 17:49:53 +01:00
vcoppe
bbfed01367 New translations translation.mdx (Hebrew) 2025-11-12 17:49:52 +01:00
vcoppe
65d81a0d58 New translations translation.mdx (Finnish) 2025-11-12 17:49:50 +01:00
vcoppe
186908fd3b New translations translation.mdx (Basque) 2025-11-12 17:49:49 +01:00
vcoppe
ab43314a53 New translations translation.mdx (Greek) 2025-11-12 17:49:48 +01:00
vcoppe
ed1c76879d New translations translation.mdx (German) 2025-11-12 17:49:47 +01:00
vcoppe
38af47f2eb New translations translation.mdx (Danish) 2025-11-12 17:49:46 +01:00
vcoppe
302d84baf7 New translations translation.mdx (Czech) 2025-11-12 17:49:45 +01:00
vcoppe
2b43f5f74f New translations translation.mdx (Catalan) 2025-11-12 17:49:43 +01:00
vcoppe
e4630052d1 New translations translation.mdx (Belarusian) 2025-11-12 17:49:42 +01:00
vcoppe
8711f942c0 New translations translation.mdx (Spanish) 2025-11-12 17:49:41 +01:00
vcoppe
5282a6e045 New translations translation.mdx (French) 2025-11-12 17:49:40 +01:00
vcoppe
55cbbfa920 New translations translation.mdx (Romanian) 2025-11-12 17:49:39 +01:00
vcoppe
68371d716b New translations funding.mdx (Serbian (Latin)) 2025-11-12 17:49:25 +01:00
vcoppe
027c0b856f New translations funding.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:49:24 +01:00
vcoppe
422978e404 New translations funding.mdx (Latvian) 2025-11-12 17:49:22 +01:00
vcoppe
8b5ce045e4 New translations funding.mdx (Thai) 2025-11-12 17:49:21 +01:00
vcoppe
86f126e735 New translations funding.mdx (Indonesian) 2025-11-12 17:49:20 +01:00
vcoppe
0e3898c065 New translations funding.mdx (Portuguese, Brazilian) 2025-11-12 17:49:19 +01:00
vcoppe
1e3a74643e New translations funding.mdx (Vietnamese) 2025-11-12 17:49:18 +01:00
vcoppe
4792689998 New translations funding.mdx (Chinese Simplified) 2025-11-12 17:49:17 +01:00
vcoppe
0eec7f0813 New translations funding.mdx (Ukrainian) 2025-11-12 17:49:15 +01:00
vcoppe
2c1bf56638 New translations funding.mdx (Turkish) 2025-11-12 17:49:14 +01:00
vcoppe
57a4279647 New translations funding.mdx (Swedish) 2025-11-12 17:49:13 +01:00
vcoppe
f9146d62cf New translations funding.mdx (Russian) 2025-11-12 17:49:12 +01:00
vcoppe
694528a294 New translations funding.mdx (Portuguese) 2025-11-12 17:49:11 +01:00
vcoppe
f11968d2c6 New translations funding.mdx (Polish) 2025-11-12 17:49:09 +01:00
vcoppe
b64b354f36 New translations funding.mdx (Norwegian) 2025-11-12 17:49:08 +01:00
vcoppe
0cb7b61043 New translations funding.mdx (Dutch) 2025-11-12 17:49:07 +01:00
vcoppe
b45fb37b02 New translations funding.mdx (Lithuanian) 2025-11-12 17:49:06 +01:00
vcoppe
bfbb25c71e New translations funding.mdx (Korean) 2025-11-12 17:49:04 +01:00
vcoppe
8a0e89c14d New translations funding.mdx (Italian) 2025-11-12 17:49:03 +01:00
vcoppe
8c7573b669 New translations funding.mdx (Hungarian) 2025-11-12 17:49:02 +01:00
vcoppe
0cae837e07 New translations funding.mdx (Hebrew) 2025-11-12 17:49:01 +01:00
vcoppe
ef36fb567a New translations funding.mdx (Finnish) 2025-11-12 17:48:59 +01:00
vcoppe
5431fe1eac New translations funding.mdx (Basque) 2025-11-12 17:48:58 +01:00
vcoppe
07b4e4b59d New translations funding.mdx (Greek) 2025-11-12 17:48:57 +01:00
vcoppe
fa06da7f03 New translations funding.mdx (German) 2025-11-12 17:48:56 +01:00
vcoppe
6cc6a34b74 New translations funding.mdx (Danish) 2025-11-12 17:48:55 +01:00
vcoppe
7187c9ce71 New translations funding.mdx (Czech) 2025-11-12 17:48:54 +01:00
vcoppe
b209ce2524 New translations funding.mdx (Catalan) 2025-11-12 17:48:52 +01:00
vcoppe
c37dd605cc New translations funding.mdx (Belarusian) 2025-11-12 17:48:51 +01:00
vcoppe
d0a3ea99bd New translations funding.mdx (Spanish) 2025-11-12 17:48:50 +01:00
vcoppe
80077b92c4 New translations funding.mdx (French) 2025-11-12 17:48:49 +01:00
vcoppe
dcf919e440 New translations funding.mdx (Romanian) 2025-11-12 17:48:48 +01:00
vcoppe
48569e05e8 New translations gpx.mdx (Serbian (Latin)) 2025-11-12 17:48:46 +01:00
vcoppe
60b24e9c3b New translations gpx.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:48:45 +01:00
vcoppe
fd4a97f547 New translations gpx.mdx (Latvian) 2025-11-12 17:48:44 +01:00
vcoppe
8b348aeea9 New translations gpx.mdx (Thai) 2025-11-12 17:48:43 +01:00
vcoppe
6d0400e85e New translations gpx.mdx (Indonesian) 2025-11-12 17:48:41 +01:00
vcoppe
d60cb68430 New translations gpx.mdx (Portuguese, Brazilian) 2025-11-12 17:48:40 +01:00
vcoppe
5834940965 New translations gpx.mdx (Vietnamese) 2025-11-12 17:48:39 +01:00
vcoppe
2680d3eb5c New translations gpx.mdx (Chinese Simplified) 2025-11-12 17:48:38 +01:00
vcoppe
149a1e5b0d New translations gpx.mdx (Ukrainian) 2025-11-12 17:48:36 +01:00
vcoppe
1e4a0bfb8e New translations gpx.mdx (Turkish) 2025-11-12 17:48:35 +01:00
vcoppe
ba4c29c44d New translations gpx.mdx (Swedish) 2025-11-12 17:48:33 +01:00
vcoppe
a632bedb4c New translations gpx.mdx (Russian) 2025-11-12 17:48:32 +01:00
vcoppe
fc8deeb0c5 New translations gpx.mdx (Portuguese) 2025-11-12 17:48:31 +01:00
vcoppe
e9e462b020 New translations gpx.mdx (Polish) 2025-11-12 17:48:30 +01:00
vcoppe
713d2cd7ee New translations gpx.mdx (Norwegian) 2025-11-12 17:48:28 +01:00
vcoppe
dea37b2e83 New translations gpx.mdx (Dutch) 2025-11-12 17:48:27 +01:00
vcoppe
95e02956fb New translations gpx.mdx (Lithuanian) 2025-11-12 17:48:26 +01:00
vcoppe
87ef568de0 New translations gpx.mdx (Korean) 2025-11-12 17:48:25 +01:00
vcoppe
a4c407e3ab New translations gpx.mdx (Italian) 2025-11-12 17:48:23 +01:00
vcoppe
543af45daa New translations gpx.mdx (Hungarian) 2025-11-12 17:48:22 +01:00
vcoppe
aca0442928 New translations gpx.mdx (Hebrew) 2025-11-12 17:48:21 +01:00
vcoppe
af1cc42b0d New translations gpx.mdx (Finnish) 2025-11-12 17:48:19 +01:00
vcoppe
1bca40eb6a New translations gpx.mdx (Basque) 2025-11-12 17:48:18 +01:00
vcoppe
2c099c3a2a New translations gpx.mdx (Greek) 2025-11-12 17:48:17 +01:00
vcoppe
1afd9fdab2 New translations gpx.mdx (German) 2025-11-12 17:48:16 +01:00
vcoppe
522eea1371 New translations gpx.mdx (Danish) 2025-11-12 17:48:15 +01:00
vcoppe
e9fc115bdd New translations gpx.mdx (Czech) 2025-11-12 17:48:13 +01:00
vcoppe
711e4a84f8 New translations gpx.mdx (Catalan) 2025-11-12 17:48:12 +01:00
vcoppe
ac6b9c855a New translations gpx.mdx (Belarusian) 2025-11-12 17:48:11 +01:00
vcoppe
eb1a9f8b9f New translations gpx.mdx (Spanish) 2025-11-12 17:48:09 +01:00
vcoppe
4fc08faa48 New translations gpx.mdx (French) 2025-11-12 17:48:08 +01:00
vcoppe
4dcf6b672c New translations gpx.mdx (Romanian) 2025-11-12 17:48:07 +01:00
vcoppe
36c8d8668a New translations files-and-stats.mdx (Serbian (Latin)) 2025-11-12 17:47:53 +01:00
vcoppe
ffe3d1f5bb New translations files-and-stats.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:47:51 +01:00
vcoppe
260cad776e New translations files-and-stats.mdx (Latvian) 2025-11-12 17:47:50 +01:00
vcoppe
3cb6fd1f44 New translations files-and-stats.mdx (Thai) 2025-11-12 17:47:49 +01:00
vcoppe
8f0d0b24c6 New translations files-and-stats.mdx (Indonesian) 2025-11-12 17:47:48 +01:00
vcoppe
f80a06b258 New translations files-and-stats.mdx (Portuguese, Brazilian) 2025-11-12 17:47:46 +01:00
vcoppe
de1ce49ba3 New translations files-and-stats.mdx (Vietnamese) 2025-11-12 17:47:45 +01:00
vcoppe
f20b26809d New translations files-and-stats.mdx (Chinese Simplified) 2025-11-12 17:47:44 +01:00
vcoppe
dbf788976f New translations files-and-stats.mdx (Ukrainian) 2025-11-12 17:47:43 +01:00
vcoppe
2f8cd71c51 New translations files-and-stats.mdx (Turkish) 2025-11-12 17:47:41 +01:00
vcoppe
ea9757a288 New translations files-and-stats.mdx (Swedish) 2025-11-12 17:47:40 +01:00
vcoppe
782ca0c11a New translations files-and-stats.mdx (Russian) 2025-11-12 17:47:39 +01:00
vcoppe
56d5474bd7 New translations files-and-stats.mdx (Portuguese) 2025-11-12 17:47:37 +01:00
vcoppe
683c3a6122 New translations files-and-stats.mdx (Polish) 2025-11-12 17:47:36 +01:00
vcoppe
b89dc25959 New translations files-and-stats.mdx (Norwegian) 2025-11-12 17:47:35 +01:00
vcoppe
ab09fe19d8 New translations routing.mdx (Czech) 2025-11-12 17:47:33 +01:00
vcoppe
d16cd4bf8e New translations files-and-stats.mdx (Dutch) 2025-11-12 17:47:32 +01:00
vcoppe
2e853006a5 New translations files-and-stats.mdx (Lithuanian) 2025-11-12 17:47:31 +01:00
vcoppe
fbfaf858e6 New translations files-and-stats.mdx (Korean) 2025-11-12 17:47:30 +01:00
vcoppe
70511c3ece New translations files-and-stats.mdx (Italian) 2025-11-12 17:47:28 +01:00
vcoppe
7c529eefb3 New translations files-and-stats.mdx (Hungarian) 2025-11-12 17:47:27 +01:00
vcoppe
bb794cf8f3 New translations files-and-stats.mdx (Hebrew) 2025-11-12 17:47:26 +01:00
vcoppe
20ae712719 New translations files-and-stats.mdx (Finnish) 2025-11-12 17:47:25 +01:00
vcoppe
607cd21f41 New translations files-and-stats.mdx (Basque) 2025-11-12 17:47:24 +01:00
vcoppe
754394e1a8 New translations files-and-stats.mdx (Greek) 2025-11-12 17:47:22 +01:00
vcoppe
7a8fcb46c7 New translations files-and-stats.mdx (German) 2025-11-12 17:47:21 +01:00
vcoppe
59b5a5068d New translations files-and-stats.mdx (Danish) 2025-11-12 17:47:20 +01:00
vcoppe
d65bdc103d New translations files-and-stats.mdx (Czech) 2025-11-12 17:47:19 +01:00
vcoppe
aefd924f05 New translations files-and-stats.mdx (Catalan) 2025-11-12 17:47:17 +01:00
vcoppe
96018b9e6a New translations files-and-stats.mdx (Belarusian) 2025-11-12 17:47:16 +01:00
vcoppe
3865d52c29 New translations files-and-stats.mdx (Spanish) 2025-11-12 17:47:15 +01:00
vcoppe
20c8133301 New translations files-and-stats.mdx (French) 2025-11-12 17:47:14 +01:00
vcoppe
9705f56e2f New translations files-and-stats.mdx (Romanian) 2025-11-12 17:47:12 +01:00
vcoppe
f4d2459a10 New translations elevation.mdx (Serbian (Latin)) 2025-11-12 17:43:59 +01:00
vcoppe
7073baea6e New translations elevation.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:43:58 +01:00
vcoppe
7834e6294c New translations elevation.mdx (Latvian) 2025-11-12 17:43:56 +01:00
vcoppe
a2d2d79ba4 New translations elevation.mdx (Thai) 2025-11-12 17:43:55 +01:00
vcoppe
087132a8b3 New translations elevation.mdx (Indonesian) 2025-11-12 17:43:54 +01:00
vcoppe
e62eca1fdf New translations elevation.mdx (Portuguese, Brazilian) 2025-11-12 17:43:53 +01:00
vcoppe
f0333d0f68 New translations elevation.mdx (Vietnamese) 2025-11-12 17:43:52 +01:00
vcoppe
08e39ae6e0 New translations elevation.mdx (Chinese Simplified) 2025-11-12 17:43:50 +01:00
vcoppe
128484dd73 New translations elevation.mdx (Ukrainian) 2025-11-12 17:43:49 +01:00
vcoppe
1e2d14af4c New translations elevation.mdx (Turkish) 2025-11-12 17:43:48 +01:00
vcoppe
37e3b86a36 New translations elevation.mdx (Swedish) 2025-11-12 17:43:47 +01:00
vcoppe
13e49b7f2c New translations elevation.mdx (Russian) 2025-11-12 17:43:45 +01:00
vcoppe
8c3e556071 New translations elevation.mdx (Portuguese) 2025-11-12 17:43:44 +01:00
vcoppe
7a24f95566 New translations elevation.mdx (Polish) 2025-11-12 17:43:43 +01:00
vcoppe
dbc14f071a New translations elevation.mdx (Norwegian) 2025-11-12 17:43:42 +01:00
vcoppe
b75b5dc107 New translations elevation.mdx (Dutch) 2025-11-12 17:43:41 +01:00
vcoppe
f1f6f3b0a8 New translations elevation.mdx (Lithuanian) 2025-11-12 17:43:39 +01:00
vcoppe
a3f556a136 New translations elevation.mdx (Korean) 2025-11-12 17:43:38 +01:00
vcoppe
04ec1d3df4 New translations elevation.mdx (Italian) 2025-11-12 17:43:37 +01:00
vcoppe
da8000787d New translations elevation.mdx (Hungarian) 2025-11-12 17:43:36 +01:00
vcoppe
faa4e917f1 New translations elevation.mdx (Hebrew) 2025-11-12 17:43:34 +01:00
vcoppe
99dc05f55b New translations elevation.mdx (Finnish) 2025-11-12 17:43:33 +01:00
vcoppe
0f2c0b8cc7 New translations elevation.mdx (Basque) 2025-11-12 17:43:32 +01:00
vcoppe
c4b1e75b0c New translations elevation.mdx (Greek) 2025-11-12 17:43:31 +01:00
vcoppe
b2eb932a06 New translations elevation.mdx (German) 2025-11-12 17:43:29 +01:00
vcoppe
aac6b15c77 New translations elevation.mdx (Danish) 2025-11-12 17:43:28 +01:00
vcoppe
2b4f1e3203 New translations elevation.mdx (Czech) 2025-11-12 17:43:27 +01:00
vcoppe
386320e12c New translations elevation.mdx (Catalan) 2025-11-12 17:43:26 +01:00
vcoppe
e775e7918d New translations elevation.mdx (Belarusian) 2025-11-12 17:43:24 +01:00
vcoppe
e0f60ddf28 New translations elevation.mdx (Spanish) 2025-11-12 17:43:23 +01:00
vcoppe
7e9b9500d5 New translations elevation.mdx (French) 2025-11-12 17:43:22 +01:00
vcoppe
29b143fb50 New translations elevation.mdx (Romanian) 2025-11-12 17:43:21 +01:00
vcoppe
0a6f454bb4 New translations time.mdx (Serbian (Latin)) 2025-11-12 17:43:06 +01:00
vcoppe
ad21ab0a45 New translations time.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:43:05 +01:00
vcoppe
7958d32b90 New translations time.mdx (Latvian) 2025-11-12 17:43:04 +01:00
vcoppe
81bfb6184e New translations time.mdx (Thai) 2025-11-12 17:43:03 +01:00
vcoppe
79bc30cbe0 New translations time.mdx (Indonesian) 2025-11-12 17:43:01 +01:00
vcoppe
076d741453 New translations time.mdx (Portuguese, Brazilian) 2025-11-12 17:43:00 +01:00
vcoppe
dc36e2e9d7 New translations time.mdx (Vietnamese) 2025-11-12 17:42:58 +01:00
vcoppe
0a3da42af9 New translations time.mdx (Chinese Simplified) 2025-11-12 17:42:57 +01:00
vcoppe
019d2b0c1b New translations time.mdx (Ukrainian) 2025-11-12 17:42:56 +01:00
vcoppe
cf5c4bcd32 New translations time.mdx (Turkish) 2025-11-12 17:42:54 +01:00
vcoppe
8c813103f2 New translations time.mdx (Swedish) 2025-11-12 17:42:53 +01:00
vcoppe
ed97c83a4c New translations time.mdx (Russian) 2025-11-12 17:42:52 +01:00
vcoppe
a7dfb0b6d3 New translations time.mdx (Portuguese) 2025-11-12 17:42:51 +01:00
vcoppe
be544e6051 New translations time.mdx (Polish) 2025-11-12 17:42:49 +01:00
vcoppe
e457526e8f New translations time.mdx (Norwegian) 2025-11-12 17:42:48 +01:00
vcoppe
539e0a2045 New translations time.mdx (Dutch) 2025-11-12 17:42:47 +01:00
vcoppe
fdcfca7633 New translations time.mdx (Lithuanian) 2025-11-12 17:42:46 +01:00
vcoppe
bd0f0cabca New translations time.mdx (Korean) 2025-11-12 17:42:44 +01:00
vcoppe
a94edc6fe2 New translations time.mdx (Italian) 2025-11-12 17:42:43 +01:00
vcoppe
295ea93340 New translations time.mdx (Hungarian) 2025-11-12 17:42:42 +01:00
vcoppe
4af58fa882 New translations time.mdx (Hebrew) 2025-11-12 17:42:41 +01:00
vcoppe
9b4b10e3b6 New translations time.mdx (Finnish) 2025-11-12 17:42:40 +01:00
vcoppe
24f27fcfb4 New translations time.mdx (Basque) 2025-11-12 17:42:38 +01:00
vcoppe
725a8b7959 New translations time.mdx (Greek) 2025-11-12 17:42:37 +01:00
vcoppe
c7b5b4b7dd New translations time.mdx (German) 2025-11-12 17:42:36 +01:00
vcoppe
04ffafb3b7 New translations time.mdx (Danish) 2025-11-12 17:42:34 +01:00
vcoppe
485b6903e5 New translations time.mdx (Czech) 2025-11-12 17:42:33 +01:00
vcoppe
b55d79564c New translations time.mdx (Catalan) 2025-11-12 17:42:32 +01:00
vcoppe
e23de3adbc New translations time.mdx (Belarusian) 2025-11-12 17:42:31 +01:00
vcoppe
b97aa933a2 New translations time.mdx (Spanish) 2025-11-12 17:42:29 +01:00
vcoppe
f0f7d5ea2f New translations time.mdx (French) 2025-11-12 17:42:28 +01:00
vcoppe
30af13cf67 New translations time.mdx (Romanian) 2025-11-12 17:42:27 +01:00
vcoppe
45e8a2ffd8 New translations scissors.mdx (Serbian (Latin)) 2025-11-12 17:42:26 +01:00
vcoppe
b41bb21fba New translations scissors.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:42:25 +01:00
vcoppe
e828d82a5a New translations scissors.mdx (Latvian) 2025-11-12 17:42:23 +01:00
vcoppe
563ac5e44c New translations scissors.mdx (Thai) 2025-11-12 17:42:22 +01:00
vcoppe
fbc46cf7bc New translations scissors.mdx (Indonesian) 2025-11-12 17:42:21 +01:00
vcoppe
5efc0dc11b New translations scissors.mdx (Portuguese, Brazilian) 2025-11-12 17:42:20 +01:00
vcoppe
d16ce01efd New translations scissors.mdx (Vietnamese) 2025-11-12 17:42:18 +01:00
vcoppe
8ff0ca1855 New translations scissors.mdx (Chinese Simplified) 2025-11-12 17:42:17 +01:00
vcoppe
6e49714e80 New translations scissors.mdx (Ukrainian) 2025-11-12 17:42:16 +01:00
vcoppe
f402ef278f New translations scissors.mdx (Turkish) 2025-11-12 17:42:15 +01:00
vcoppe
4fb32bc11b New translations scissors.mdx (Swedish) 2025-11-12 17:42:13 +01:00
vcoppe
684b29d479 New translations scissors.mdx (Russian) 2025-11-12 17:42:12 +01:00
vcoppe
5b2f253f10 New translations scissors.mdx (Portuguese) 2025-11-12 17:42:11 +01:00
vcoppe
c88ad2c97b New translations scissors.mdx (Polish) 2025-11-12 17:42:10 +01:00
vcoppe
3e0f670e02 New translations scissors.mdx (Norwegian) 2025-11-12 17:42:08 +01:00
vcoppe
737e8167b4 New translations scissors.mdx (Dutch) 2025-11-12 17:42:07 +01:00
vcoppe
23be635323 New translations scissors.mdx (Lithuanian) 2025-11-12 17:42:06 +01:00
vcoppe
8d7d63a7fa New translations scissors.mdx (Korean) 2025-11-12 17:42:05 +01:00
vcoppe
8ad6c77c92 New translations scissors.mdx (Italian) 2025-11-12 17:42:03 +01:00
vcoppe
7d1f862185 New translations scissors.mdx (Hungarian) 2025-11-12 17:42:02 +01:00
vcoppe
2d9561de38 New translations scissors.mdx (Hebrew) 2025-11-12 17:42:01 +01:00
vcoppe
1c09d67846 New translations scissors.mdx (Finnish) 2025-11-12 17:42:00 +01:00
vcoppe
5f5775d201 New translations scissors.mdx (Basque) 2025-11-12 17:41:59 +01:00
vcoppe
db64d97ef1 New translations scissors.mdx (Greek) 2025-11-12 17:41:57 +01:00
vcoppe
19b0f7e572 New translations scissors.mdx (German) 2025-11-12 17:41:56 +01:00
vcoppe
2fb0ccc752 New translations scissors.mdx (Danish) 2025-11-12 17:41:54 +01:00
vcoppe
e6f1abad32 New translations scissors.mdx (Czech) 2025-11-12 17:41:53 +01:00
vcoppe
816f36de50 New translations scissors.mdx (Catalan) 2025-11-12 17:41:52 +01:00
vcoppe
193056531c New translations scissors.mdx (Belarusian) 2025-11-12 17:41:51 +01:00
vcoppe
ce356ce3ff New translations scissors.mdx (Spanish) 2025-11-12 17:41:50 +01:00
vcoppe
334b6d7588 New translations scissors.mdx (French) 2025-11-12 17:41:48 +01:00
vcoppe
2c38082cec New translations scissors.mdx (Romanian) 2025-11-12 17:41:47 +01:00
vcoppe
5dfc5d35ea New translations routing.mdx (Serbian (Latin)) 2025-11-12 17:41:46 +01:00
vcoppe
9d6782e4eb New translations routing.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:41:45 +01:00
vcoppe
81cf903e5f New translations routing.mdx (Latvian) 2025-11-12 17:41:43 +01:00
vcoppe
3d02a377ba New translations routing.mdx (Thai) 2025-11-12 17:41:42 +01:00
vcoppe
e80659cf38 New translations routing.mdx (Indonesian) 2025-11-12 17:41:41 +01:00
vcoppe
3c0eeba130 New translations routing.mdx (Portuguese, Brazilian) 2025-11-12 17:41:39 +01:00
vcoppe
6911bc624f New translations routing.mdx (Vietnamese) 2025-11-12 17:41:38 +01:00
vcoppe
d732745e2d New translations routing.mdx (Chinese Simplified) 2025-11-12 17:41:37 +01:00
vcoppe
9cd0fb8420 New translations routing.mdx (Ukrainian) 2025-11-12 17:41:35 +01:00
vcoppe
e3ed704119 New translations routing.mdx (Turkish) 2025-11-12 17:41:34 +01:00
vcoppe
7ff575de97 New translations routing.mdx (Swedish) 2025-11-12 17:41:33 +01:00
vcoppe
c0083be420 New translations routing.mdx (Russian) 2025-11-12 17:41:32 +01:00
vcoppe
4272a25ffd New translations routing.mdx (Portuguese) 2025-11-12 17:41:30 +01:00
vcoppe
3e39e93999 New translations routing.mdx (Polish) 2025-11-12 17:41:29 +01:00
vcoppe
b0dce71d1e New translations routing.mdx (Norwegian) 2025-11-12 17:41:28 +01:00
vcoppe
fd66246ec5 New translations routing.mdx (Dutch) 2025-11-12 17:41:26 +01:00
vcoppe
0dc1598eca New translations routing.mdx (Lithuanian) 2025-11-12 17:41:25 +01:00
vcoppe
d5108acc99 New translations routing.mdx (Korean) 2025-11-12 17:41:24 +01:00
vcoppe
d87facb618 New translations routing.mdx (Italian) 2025-11-12 17:41:22 +01:00
vcoppe
e4c4b3f07d New translations routing.mdx (Hungarian) 2025-11-12 17:41:21 +01:00
vcoppe
70c4a52321 New translations routing.mdx (Hebrew) 2025-11-12 17:41:20 +01:00
vcoppe
7102ef0d26 New translations routing.mdx (Finnish) 2025-11-12 17:41:18 +01:00
vcoppe
e91383d558 New translations routing.mdx (Basque) 2025-11-12 17:41:17 +01:00
vcoppe
a4708a5fce New translations routing.mdx (Greek) 2025-11-12 17:41:16 +01:00
vcoppe
3d06200b71 New translations routing.mdx (German) 2025-11-12 17:41:14 +01:00
vcoppe
4d173d7d76 New translations routing.mdx (Danish) 2025-11-12 17:41:13 +01:00
vcoppe
1fb2f03835 New translations routing.mdx (Catalan) 2025-11-12 17:41:11 +01:00
vcoppe
15d010d895 New translations routing.mdx (Belarusian) 2025-11-12 17:41:10 +01:00
vcoppe
7367b19515 New translations routing.mdx (Spanish) 2025-11-12 17:41:09 +01:00
vcoppe
9202ee5289 New translations routing.mdx (French) 2025-11-12 17:41:07 +01:00
vcoppe
a4bb04786d New translations routing.mdx (Romanian) 2025-11-12 17:41:06 +01:00
vcoppe
94591fdd4b New translations poi.mdx (Serbian (Latin)) 2025-11-12 17:41:04 +01:00
vcoppe
3df7b27d9b New translations poi.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:41:03 +01:00
vcoppe
bbf1bcbf58 New translations poi.mdx (Latvian) 2025-11-12 17:41:02 +01:00
vcoppe
383c3ec00c New translations poi.mdx (Thai) 2025-11-12 17:41:00 +01:00
vcoppe
e328cdc21a New translations poi.mdx (Indonesian) 2025-11-12 17:40:59 +01:00
vcoppe
c80b48e9c8 New translations poi.mdx (Portuguese, Brazilian) 2025-11-12 17:40:58 +01:00
vcoppe
4128ae3aed New translations poi.mdx (Vietnamese) 2025-11-12 17:40:57 +01:00
vcoppe
e34f5b01bb New translations poi.mdx (Chinese Simplified) 2025-11-12 17:40:55 +01:00
vcoppe
ef80df69ca New translations poi.mdx (Ukrainian) 2025-11-12 17:40:54 +01:00
vcoppe
4a34d6c599 New translations poi.mdx (Turkish) 2025-11-12 17:40:53 +01:00
vcoppe
55bb191268 New translations poi.mdx (Swedish) 2025-11-12 17:40:52 +01:00
vcoppe
36ae02a44d New translations poi.mdx (Russian) 2025-11-12 17:40:51 +01:00
vcoppe
43f282dded New translations poi.mdx (Portuguese) 2025-11-12 17:40:49 +01:00
vcoppe
639418692e New translations poi.mdx (Polish) 2025-11-12 17:40:48 +01:00
vcoppe
12e3a0cddd New translations poi.mdx (Norwegian) 2025-11-12 17:40:47 +01:00
vcoppe
c6a6e9511f New translations poi.mdx (Dutch) 2025-11-12 17:40:45 +01:00
vcoppe
5f34726b22 New translations poi.mdx (Lithuanian) 2025-11-12 17:40:44 +01:00
vcoppe
f9f16ec609 New translations poi.mdx (Korean) 2025-11-12 17:40:42 +01:00
vcoppe
ae7f14a975 New translations poi.mdx (Italian) 2025-11-12 17:40:41 +01:00
vcoppe
1a2652855a New translations poi.mdx (Hungarian) 2025-11-12 17:40:40 +01:00
vcoppe
bfb52060e7 New translations poi.mdx (Hebrew) 2025-11-12 17:40:39 +01:00
vcoppe
371998cef4 New translations poi.mdx (Finnish) 2025-11-12 17:40:38 +01:00
vcoppe
af379947b8 New translations poi.mdx (Basque) 2025-11-12 17:40:36 +01:00
vcoppe
09bddc0958 New translations poi.mdx (Greek) 2025-11-12 17:40:35 +01:00
vcoppe
b4e971596d New translations poi.mdx (German) 2025-11-12 17:40:34 +01:00
vcoppe
57bc7251cf New translations poi.mdx (Danish) 2025-11-12 17:40:32 +01:00
vcoppe
576142a25a New translations poi.mdx (Czech) 2025-11-12 17:40:31 +01:00
vcoppe
76a7f168af New translations poi.mdx (Catalan) 2025-11-12 17:40:30 +01:00
vcoppe
5b1ae587c0 New translations poi.mdx (Belarusian) 2025-11-12 17:40:29 +01:00
vcoppe
c9cdcaeb94 New translations poi.mdx (Spanish) 2025-11-12 17:40:27 +01:00
vcoppe
39581801c6 New translations poi.mdx (French) 2025-11-12 17:40:26 +01:00
vcoppe
3983d39f0c New translations poi.mdx (Romanian) 2025-11-12 17:40:25 +01:00
vcoppe
4fc41816ca New translations minify.mdx (Serbian (Latin)) 2025-11-12 17:40:24 +01:00
vcoppe
d102ff4de7 New translations minify.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:40:22 +01:00
vcoppe
adf34ee85a New translations minify.mdx (Latvian) 2025-11-12 17:40:21 +01:00
vcoppe
870f1c11da New translations minify.mdx (Thai) 2025-11-12 17:40:20 +01:00
vcoppe
11aedd1274 New translations minify.mdx (Indonesian) 2025-11-12 17:40:18 +01:00
vcoppe
ef04c874f0 New translations minify.mdx (Portuguese, Brazilian) 2025-11-12 17:40:17 +01:00
vcoppe
d478f74252 New translations minify.mdx (Vietnamese) 2025-11-12 17:40:16 +01:00
vcoppe
3d140ae9c2 New translations minify.mdx (Chinese Simplified) 2025-11-12 17:40:15 +01:00
vcoppe
9dc652322f New translations minify.mdx (Ukrainian) 2025-11-12 17:40:13 +01:00
vcoppe
0ae7f59444 New translations minify.mdx (Turkish) 2025-11-12 17:40:12 +01:00
vcoppe
e215d473c6 New translations minify.mdx (Swedish) 2025-11-12 17:40:11 +01:00
vcoppe
96cbd24d4f New translations minify.mdx (Russian) 2025-11-12 17:40:10 +01:00
vcoppe
665f12785d New translations minify.mdx (Portuguese) 2025-11-12 17:40:08 +01:00
vcoppe
7a933ff297 New translations minify.mdx (Polish) 2025-11-12 17:40:07 +01:00
vcoppe
9f59cce7c9 New translations minify.mdx (Norwegian) 2025-11-12 17:40:05 +01:00
vcoppe
2c04528c12 New translations minify.mdx (Dutch) 2025-11-12 17:40:04 +01:00
vcoppe
aad22e2698 New translations minify.mdx (Lithuanian) 2025-11-12 17:40:03 +01:00
vcoppe
3714deac89 New translations minify.mdx (Korean) 2025-11-12 17:40:02 +01:00
vcoppe
37c4c9538b New translations minify.mdx (Italian) 2025-11-12 17:40:00 +01:00
vcoppe
5ca5473c1b New translations minify.mdx (Hungarian) 2025-11-12 17:39:59 +01:00
vcoppe
4b5bc7b2d7 New translations minify.mdx (Hebrew) 2025-11-12 17:39:57 +01:00
vcoppe
72aa9fe46f New translations minify.mdx (Finnish) 2025-11-12 17:39:56 +01:00
vcoppe
ce08c5165b New translations minify.mdx (Basque) 2025-11-12 17:39:55 +01:00
vcoppe
61e6b9476d New translations minify.mdx (Greek) 2025-11-12 17:39:54 +01:00
vcoppe
4466787c94 New translations minify.mdx (German) 2025-11-12 17:39:53 +01:00
vcoppe
0df0e891cb New translations minify.mdx (Danish) 2025-11-12 17:39:51 +01:00
vcoppe
89d608b2ac New translations minify.mdx (Czech) 2025-11-12 17:39:50 +01:00
vcoppe
5b0c09f75d New translations minify.mdx (Catalan) 2025-11-12 17:39:49 +01:00
vcoppe
a9e114388f New translations minify.mdx (Belarusian) 2025-11-12 17:39:47 +01:00
vcoppe
31c428ccbc New translations minify.mdx (Spanish) 2025-11-12 17:39:46 +01:00
vcoppe
9d4d70ee76 New translations minify.mdx (French) 2025-11-12 17:39:45 +01:00
vcoppe
5258327694 New translations minify.mdx (Romanian) 2025-11-12 17:39:44 +01:00
vcoppe
e80875e3b0 New translations merge.mdx (Serbian (Latin)) 2025-11-12 17:39:42 +01:00
vcoppe
cb42dcf2c4 New translations merge.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:39:41 +01:00
vcoppe
fe9f3f0a55 New translations merge.mdx (Latvian) 2025-11-12 17:39:40 +01:00
vcoppe
712d193f7c New translations merge.mdx (Thai) 2025-11-12 17:39:39 +01:00
vcoppe
c546185dda New translations merge.mdx (Indonesian) 2025-11-12 17:39:37 +01:00
vcoppe
5945a1c063 New translations merge.mdx (Portuguese, Brazilian) 2025-11-12 17:39:36 +01:00
vcoppe
79a17b4720 New translations merge.mdx (Vietnamese) 2025-11-12 17:39:35 +01:00
vcoppe
0c69eb2910 New translations merge.mdx (Chinese Simplified) 2025-11-12 17:39:34 +01:00
vcoppe
9f5b865949 New translations merge.mdx (Ukrainian) 2025-11-12 17:39:33 +01:00
vcoppe
fccbe1c700 New translations merge.mdx (Turkish) 2025-11-12 17:39:31 +01:00
vcoppe
a67f503b15 New translations merge.mdx (Swedish) 2025-11-12 17:39:30 +01:00
vcoppe
b9d37a87d3 New translations merge.mdx (Russian) 2025-11-12 17:39:29 +01:00
vcoppe
103580954b New translations merge.mdx (Portuguese) 2025-11-12 17:39:28 +01:00
vcoppe
a46a5cb723 New translations merge.mdx (Polish) 2025-11-12 17:39:27 +01:00
vcoppe
7c9bb0786f New translations merge.mdx (Norwegian) 2025-11-12 17:39:25 +01:00
vcoppe
a10e365fe3 New translations merge.mdx (Dutch) 2025-11-12 17:39:24 +01:00
vcoppe
bb11d417aa New translations merge.mdx (Lithuanian) 2025-11-12 17:39:23 +01:00
vcoppe
1014c5b8fc New translations merge.mdx (Korean) 2025-11-12 17:39:22 +01:00
vcoppe
0f52beeda0 New translations merge.mdx (Italian) 2025-11-12 17:39:21 +01:00
vcoppe
b34c87a4a8 New translations merge.mdx (Hungarian) 2025-11-12 17:39:19 +01:00
vcoppe
c4ea8c8902 New translations merge.mdx (Hebrew) 2025-11-12 17:39:18 +01:00
vcoppe
67f79e447e New translations merge.mdx (Finnish) 2025-11-12 17:39:17 +01:00
vcoppe
c55e2b5223 New translations merge.mdx (Basque) 2025-11-12 17:39:16 +01:00
vcoppe
58020a2ddd New translations merge.mdx (Greek) 2025-11-12 17:39:15 +01:00
vcoppe
263fb75e40 New translations merge.mdx (German) 2025-11-12 17:39:13 +01:00
vcoppe
e2253b07f8 New translations merge.mdx (Danish) 2025-11-12 17:39:12 +01:00
vcoppe
fd9a23a2b4 New translations merge.mdx (Czech) 2025-11-12 17:39:11 +01:00
vcoppe
1f175e3693 New translations merge.mdx (Catalan) 2025-11-12 17:39:09 +01:00
vcoppe
cb36ceff9e New translations merge.mdx (Belarusian) 2025-11-12 17:39:08 +01:00
vcoppe
3634071686 New translations merge.mdx (Spanish) 2025-11-12 17:39:07 +01:00
vcoppe
5e09d7cafd New translations merge.mdx (French) 2025-11-12 17:39:06 +01:00
vcoppe
9fc574a1da New translations merge.mdx (Romanian) 2025-11-12 17:39:04 +01:00
vcoppe
f57a3770e6 New translations extract.mdx (Serbian (Latin)) 2025-11-12 17:39:03 +01:00
vcoppe
f3de70d69e New translations extract.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:39:02 +01:00
vcoppe
5bf5ebc279 New translations extract.mdx (Latvian) 2025-11-12 17:39:01 +01:00
vcoppe
0ffbf02ba0 New translations extract.mdx (Thai) 2025-11-12 17:38:59 +01:00
vcoppe
73ffaaecd7 New translations extract.mdx (Indonesian) 2025-11-12 17:38:58 +01:00
vcoppe
b68e5f8b03 New translations extract.mdx (Portuguese, Brazilian) 2025-11-12 17:38:56 +01:00
vcoppe
79586b2d3f New translations extract.mdx (Vietnamese) 2025-11-12 17:38:55 +01:00
vcoppe
5307e9e4a6 New translations extract.mdx (Chinese Simplified) 2025-11-12 17:38:54 +01:00
vcoppe
900225fb8d New translations extract.mdx (Ukrainian) 2025-11-12 17:38:53 +01:00
vcoppe
0d2993ea16 New translations extract.mdx (Turkish) 2025-11-12 17:38:52 +01:00
vcoppe
019857bc61 New translations extract.mdx (Swedish) 2025-11-12 17:38:50 +01:00
vcoppe
aec642d6f3 New translations extract.mdx (Russian) 2025-11-12 17:38:49 +01:00
vcoppe
0ce25946d4 New translations extract.mdx (Portuguese) 2025-11-12 17:38:48 +01:00
vcoppe
d4d897b53a New translations extract.mdx (Polish) 2025-11-12 17:38:47 +01:00
vcoppe
e7967c441c New translations extract.mdx (Norwegian) 2025-11-12 17:38:45 +01:00
vcoppe
9122e8bf0e New translations extract.mdx (Dutch) 2025-11-12 17:38:44 +01:00
vcoppe
3ac4e30a5f New translations extract.mdx (Lithuanian) 2025-11-12 17:38:43 +01:00
vcoppe
668b77c91f New translations extract.mdx (Korean) 2025-11-12 17:38:42 +01:00
vcoppe
5dae658d4b New translations extract.mdx (Italian) 2025-11-12 17:38:41 +01:00
vcoppe
adc132609b New translations extract.mdx (Hungarian) 2025-11-12 17:38:39 +01:00
vcoppe
c9853a4058 New translations extract.mdx (Hebrew) 2025-11-12 17:38:38 +01:00
vcoppe
336ad925aa New translations extract.mdx (Finnish) 2025-11-12 17:38:37 +01:00
vcoppe
624dd408c1 New translations extract.mdx (Basque) 2025-11-12 17:38:36 +01:00
vcoppe
aa7ffbe88d New translations extract.mdx (Greek) 2025-11-12 17:38:35 +01:00
vcoppe
bb6ae3ec00 New translations extract.mdx (German) 2025-11-12 17:38:33 +01:00
vcoppe
e568f77de3 New translations extract.mdx (Danish) 2025-11-12 17:38:32 +01:00
vcoppe
ec23dfba68 New translations extract.mdx (Czech) 2025-11-12 17:38:31 +01:00
vcoppe
817a67a118 New translations extract.mdx (Catalan) 2025-11-12 17:38:30 +01:00
vcoppe
bf5aa1321c New translations extract.mdx (Belarusian) 2025-11-12 17:38:29 +01:00
vcoppe
85d1be85a3 New translations extract.mdx (Spanish) 2025-11-12 17:38:27 +01:00
vcoppe
ecc15cb462 New translations extract.mdx (French) 2025-11-12 17:38:26 +01:00
vcoppe
4bbf813617 New translations extract.mdx (Romanian) 2025-11-12 17:38:25 +01:00
vcoppe
28fd8b104c New translations clean.mdx (Serbian (Latin)) 2025-11-12 17:38:24 +01:00
vcoppe
578c3732b3 New translations clean.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:38:22 +01:00
vcoppe
7568f1581f New translations clean.mdx (Latvian) 2025-11-12 17:38:21 +01:00
vcoppe
0d07ef8eab New translations clean.mdx (Thai) 2025-11-12 17:38:20 +01:00
vcoppe
301bbf4206 New translations clean.mdx (Indonesian) 2025-11-12 17:38:19 +01:00
vcoppe
14057bf3b4 New translations clean.mdx (Portuguese, Brazilian) 2025-11-12 17:38:17 +01:00
vcoppe
01527c1dba New translations clean.mdx (Vietnamese) 2025-11-12 17:38:16 +01:00
vcoppe
3350b7e8e8 New translations clean.mdx (Chinese Simplified) 2025-11-12 17:38:15 +01:00
vcoppe
673d6d776e New translations clean.mdx (Ukrainian) 2025-11-12 17:38:13 +01:00
vcoppe
4169f3880c New translations clean.mdx (Turkish) 2025-11-12 17:38:12 +01:00
vcoppe
a21b17be89 New translations clean.mdx (Swedish) 2025-11-12 17:38:11 +01:00
vcoppe
dad9f4f790 New translations clean.mdx (Russian) 2025-11-12 17:38:10 +01:00
vcoppe
f6b5fa83af New translations clean.mdx (Portuguese) 2025-11-12 17:38:09 +01:00
vcoppe
a3bcef09d4 New translations clean.mdx (Polish) 2025-11-12 17:38:07 +01:00
vcoppe
545fb1368a New translations clean.mdx (Norwegian) 2025-11-12 17:38:06 +01:00
vcoppe
d737131f27 New translations clean.mdx (Dutch) 2025-11-12 17:38:05 +01:00
vcoppe
ead2477400 New translations clean.mdx (Lithuanian) 2025-11-12 17:38:04 +01:00
vcoppe
aa7cc66a81 New translations clean.mdx (Korean) 2025-11-12 17:38:02 +01:00
vcoppe
d82882c98a New translations clean.mdx (Italian) 2025-11-12 17:38:01 +01:00
vcoppe
4e0584177f New translations clean.mdx (Hungarian) 2025-11-12 17:38:00 +01:00
vcoppe
bca62140d0 New translations clean.mdx (Hebrew) 2025-11-12 17:37:59 +01:00
vcoppe
1e80cfe7b3 New translations clean.mdx (Finnish) 2025-11-12 17:37:57 +01:00
vcoppe
a27863e67a New translations clean.mdx (Basque) 2025-11-12 17:37:56 +01:00
vcoppe
d491eeb314 New translations clean.mdx (Greek) 2025-11-12 17:37:55 +01:00
vcoppe
c943d0c796 New translations clean.mdx (German) 2025-11-12 17:37:54 +01:00
vcoppe
bf749e02e2 New translations clean.mdx (Danish) 2025-11-12 17:37:53 +01:00
vcoppe
ea7a17fbc6 New translations clean.mdx (Czech) 2025-11-12 17:37:51 +01:00
vcoppe
7da23271b9 New translations clean.mdx (Catalan) 2025-11-12 17:37:50 +01:00
vcoppe
49204ac5b0 New translations clean.mdx (Belarusian) 2025-11-12 17:37:49 +01:00
vcoppe
d38fb3c30b New translations clean.mdx (Spanish) 2025-11-12 17:37:47 +01:00
vcoppe
9a2f75d33e New translations clean.mdx (French) 2025-11-12 17:37:46 +01:00
vcoppe
ce64e201ce New translations clean.mdx (Romanian) 2025-11-12 17:37:45 +01:00
vcoppe
49ac3859a6 New translations view.mdx (Serbian (Latin)) 2025-11-12 17:37:31 +01:00
vcoppe
5e63f53b4e New translations view.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:37:30 +01:00
vcoppe
a24a1d55fa New translations view.mdx (Latvian) 2025-11-12 17:37:28 +01:00
vcoppe
7e69310202 New translations view.mdx (Thai) 2025-11-12 17:37:27 +01:00
vcoppe
9d65bd006d New translations view.mdx (Indonesian) 2025-11-12 17:37:26 +01:00
vcoppe
552cb854e6 New translations view.mdx (Portuguese, Brazilian) 2025-11-12 17:37:25 +01:00
vcoppe
7cc9f23792 New translations view.mdx (Vietnamese) 2025-11-12 17:37:23 +01:00
vcoppe
8b3a852508 New translations view.mdx (Chinese Simplified) 2025-11-12 17:37:22 +01:00
vcoppe
1bbbbffd8d New translations view.mdx (Ukrainian) 2025-11-12 17:37:21 +01:00
vcoppe
52906e8041 New translations view.mdx (Turkish) 2025-11-12 17:37:20 +01:00
vcoppe
6e402f3c97 New translations view.mdx (Swedish) 2025-11-12 17:37:19 +01:00
vcoppe
549d8debb8 New translations view.mdx (Russian) 2025-11-12 17:37:17 +01:00
vcoppe
52027b028a New translations view.mdx (Portuguese) 2025-11-12 17:37:16 +01:00
vcoppe
8d922e55d9 New translations view.mdx (Polish) 2025-11-12 17:37:15 +01:00
vcoppe
5eea175b76 New translations view.mdx (Norwegian) 2025-11-12 17:37:14 +01:00
vcoppe
be843d056e New translations view.mdx (Dutch) 2025-11-12 17:37:12 +01:00
vcoppe
7a58268c25 New translations view.mdx (Lithuanian) 2025-11-12 17:37:11 +01:00
vcoppe
eb23baed5d New translations view.mdx (Korean) 2025-11-12 17:37:09 +01:00
vcoppe
afb699bc12 New translations view.mdx (Italian) 2025-11-12 17:37:08 +01:00
vcoppe
3112f4158b New translations view.mdx (Hungarian) 2025-11-12 17:37:07 +01:00
vcoppe
2cce3eb452 New translations view.mdx (Hebrew) 2025-11-12 17:37:06 +01:00
vcoppe
bf58de7c1f New translations view.mdx (Finnish) 2025-11-12 17:37:05 +01:00
vcoppe
fa797f219f New translations view.mdx (Basque) 2025-11-12 17:37:03 +01:00
vcoppe
f6f859a343 New translations view.mdx (Greek) 2025-11-12 17:37:02 +01:00
vcoppe
baf43cefe9 New translations view.mdx (German) 2025-11-12 17:37:01 +01:00
vcoppe
7c4e3cfe32 New translations view.mdx (Danish) 2025-11-12 17:37:00 +01:00
vcoppe
ed6b41823d New translations view.mdx (Czech) 2025-11-12 17:36:59 +01:00
vcoppe
32f141aa2b New translations view.mdx (Catalan) 2025-11-12 17:36:57 +01:00
vcoppe
9f76ece5cf New translations view.mdx (Belarusian) 2025-11-12 17:36:56 +01:00
vcoppe
ba08346083 New translations view.mdx (Spanish) 2025-11-12 17:36:55 +01:00
vcoppe
fe2d418b3a New translations view.mdx (French) 2025-11-12 17:36:54 +01:00
vcoppe
e4e6e0f4ad New translations view.mdx (Romanian) 2025-11-12 17:36:52 +01:00
vcoppe
cb2214ef9b New translations settings.mdx (Serbian (Latin)) 2025-11-12 17:36:51 +01:00
vcoppe
ca1289460d New translations settings.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:36:50 +01:00
vcoppe
0540b9eb12 New translations settings.mdx (Latvian) 2025-11-12 17:36:48 +01:00
vcoppe
0a6cf92f54 New translations settings.mdx (Thai) 2025-11-12 17:36:47 +01:00
vcoppe
835ff9ec19 New translations settings.mdx (Indonesian) 2025-11-12 17:36:46 +01:00
vcoppe
4ba0f4bba3 New translations settings.mdx (Portuguese, Brazilian) 2025-11-12 17:36:45 +01:00
vcoppe
654769b67b New translations settings.mdx (Vietnamese) 2025-11-12 17:36:43 +01:00
vcoppe
4d64ae5944 New translations settings.mdx (Chinese Simplified) 2025-11-12 17:36:41 +01:00
vcoppe
c6f1890055 New translations settings.mdx (Ukrainian) 2025-11-12 17:36:40 +01:00
vcoppe
55f4a6f021 New translations settings.mdx (Turkish) 2025-11-12 17:36:39 +01:00
vcoppe
f8c8b4d368 New translations settings.mdx (Swedish) 2025-11-12 17:36:38 +01:00
vcoppe
be5b248f21 New translations settings.mdx (Russian) 2025-11-12 17:36:37 +01:00
vcoppe
6a11f8bd41 New translations settings.mdx (Portuguese) 2025-11-12 17:36:35 +01:00
vcoppe
2cc6d5ad03 New translations settings.mdx (Polish) 2025-11-12 17:36:34 +01:00
vcoppe
a21c80f843 New translations settings.mdx (Norwegian) 2025-11-12 17:36:33 +01:00
vcoppe
90060a36c6 New translations settings.mdx (Dutch) 2025-11-12 17:36:32 +01:00
vcoppe
ac5c2372fa New translations settings.mdx (Lithuanian) 2025-11-12 17:36:31 +01:00
vcoppe
f6efcd160f New translations settings.mdx (Korean) 2025-11-12 17:36:29 +01:00
vcoppe
83ec2b97d6 New translations settings.mdx (Italian) 2025-11-12 17:36:28 +01:00
vcoppe
3220550633 New translations settings.mdx (Hungarian) 2025-11-12 17:36:26 +01:00
vcoppe
bafafc281a New translations settings.mdx (Hebrew) 2025-11-12 17:36:25 +01:00
vcoppe
b58a0d22c6 New translations settings.mdx (Finnish) 2025-11-12 17:36:24 +01:00
vcoppe
55c25efca7 New translations settings.mdx (Basque) 2025-11-12 17:36:23 +01:00
vcoppe
967a5bf612 New translations settings.mdx (Greek) 2025-11-12 17:36:21 +01:00
vcoppe
fcb762ee8f New translations settings.mdx (German) 2025-11-12 17:36:20 +01:00
vcoppe
38a941e986 New translations settings.mdx (Danish) 2025-11-12 17:36:18 +01:00
vcoppe
3544e1bac3 New translations settings.mdx (Czech) 2025-11-12 17:36:17 +01:00
vcoppe
9c81714568 New translations settings.mdx (Catalan) 2025-11-12 17:36:16 +01:00
vcoppe
dff93c491c New translations settings.mdx (Belarusian) 2025-11-12 17:36:14 +01:00
vcoppe
0b7ffdc45a New translations settings.mdx (Spanish) 2025-11-12 17:36:13 +01:00
vcoppe
71cfc65f68 New translations settings.mdx (French) 2025-11-12 17:36:12 +01:00
vcoppe
005548bdd0 New translations settings.mdx (Romanian) 2025-11-12 17:36:11 +01:00
vcoppe
aa285d8409 New translations file.mdx (Serbian (Latin)) 2025-11-12 17:36:09 +01:00
vcoppe
a801166692 New translations file.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:36:08 +01:00
vcoppe
8259c57fad New translations file.mdx (Latvian) 2025-11-12 17:36:06 +01:00
vcoppe
0b47fadef7 New translations file.mdx (Thai) 2025-11-12 17:36:05 +01:00
vcoppe
4453001b30 New translations file.mdx (Indonesian) 2025-11-12 17:36:04 +01:00
vcoppe
58999c28fe New translations file.mdx (Portuguese, Brazilian) 2025-11-12 17:36:03 +01:00
vcoppe
74d06e5aad New translations file.mdx (Vietnamese) 2025-11-12 17:36:01 +01:00
vcoppe
662c8f5ca2 New translations file.mdx (Chinese Simplified) 2025-11-12 17:36:00 +01:00
vcoppe
b5b5f82464 New translations file.mdx (Ukrainian) 2025-11-12 17:35:59 +01:00
vcoppe
7593ec5587 New translations file.mdx (Turkish) 2025-11-12 17:35:58 +01:00
vcoppe
94ac0ef90c New translations file.mdx (Swedish) 2025-11-12 17:35:56 +01:00
vcoppe
36093288f7 New translations file.mdx (Russian) 2025-11-12 17:35:55 +01:00
vcoppe
d226b3c3c1 New translations file.mdx (Portuguese) 2025-11-12 17:35:54 +01:00
vcoppe
13215e5c16 New translations file.mdx (Polish) 2025-11-12 17:35:53 +01:00
vcoppe
f6c6eb441f New translations file.mdx (Norwegian) 2025-11-12 17:35:51 +01:00
vcoppe
e883ce7c43 New translations file.mdx (Dutch) 2025-11-12 17:35:50 +01:00
vcoppe
4836a99fcd New translations file.mdx (Lithuanian) 2025-11-12 17:35:49 +01:00
vcoppe
a7075067bf New translations file.mdx (Korean) 2025-11-12 17:35:48 +01:00
vcoppe
c8edea25ae New translations file.mdx (Italian) 2025-11-12 17:35:46 +01:00
vcoppe
07731fc2de New translations file.mdx (Hungarian) 2025-11-12 17:35:45 +01:00
vcoppe
8c436d5c0e New translations file.mdx (Hebrew) 2025-11-12 17:35:44 +01:00
vcoppe
16a2b1979e New translations file.mdx (Finnish) 2025-11-12 17:35:43 +01:00
vcoppe
7753613cf4 New translations file.mdx (Basque) 2025-11-12 17:35:42 +01:00
vcoppe
d156a6ffee New translations file.mdx (Greek) 2025-11-12 17:35:41 +01:00
vcoppe
f54b614ece New translations file.mdx (German) 2025-11-12 17:35:39 +01:00
vcoppe
24ed769eac New translations file.mdx (Danish) 2025-11-12 17:35:38 +01:00
vcoppe
ef8387f7c7 New translations file.mdx (Czech) 2025-11-12 17:35:37 +01:00
vcoppe
c76c37c874 New translations file.mdx (Catalan) 2025-11-12 17:35:36 +01:00
vcoppe
bf41cb70f8 New translations file.mdx (Belarusian) 2025-11-12 17:35:34 +01:00
vcoppe
f7e759c69f New translations file.mdx (Spanish) 2025-11-12 17:35:33 +01:00
vcoppe
376360caac New translations file.mdx (French) 2025-11-12 17:35:32 +01:00
vcoppe
66fa02c90a New translations file.mdx (Romanian) 2025-11-12 17:35:31 +01:00
vcoppe
26cc2e3c9e New translations edit.mdx (Serbian (Latin)) 2025-11-12 17:35:29 +01:00
vcoppe
09ceb1b5ce New translations edit.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:35:28 +01:00
vcoppe
1204e971c2 New translations edit.mdx (Latvian) 2025-11-12 17:35:27 +01:00
vcoppe
9833df2110 New translations edit.mdx (Thai) 2025-11-12 17:35:26 +01:00
vcoppe
7843170978 New translations edit.mdx (Indonesian) 2025-11-12 17:35:25 +01:00
vcoppe
93c1535b76 New translations edit.mdx (Portuguese, Brazilian) 2025-11-12 17:35:23 +01:00
vcoppe
c3c6e2e757 New translations edit.mdx (Vietnamese) 2025-11-12 17:35:22 +01:00
vcoppe
2d4b3514bc New translations edit.mdx (Chinese Simplified) 2025-11-12 17:35:21 +01:00
vcoppe
a27fce18ed New translations edit.mdx (Ukrainian) 2025-11-12 17:35:20 +01:00
vcoppe
1adc1833dc New translations edit.mdx (Turkish) 2025-11-12 17:35:19 +01:00
vcoppe
9a1c721a46 New translations edit.mdx (Swedish) 2025-11-12 17:35:17 +01:00
vcoppe
9bd7d49fe7 New translations edit.mdx (Russian) 2025-11-12 17:35:16 +01:00
vcoppe
e94f128de8 New translations edit.mdx (Portuguese) 2025-11-12 17:35:15 +01:00
vcoppe
9005a58e43 New translations edit.mdx (Polish) 2025-11-12 17:35:14 +01:00
vcoppe
12f07c57f2 New translations edit.mdx (Norwegian) 2025-11-12 17:35:12 +01:00
vcoppe
3c6066f047 New translations edit.mdx (Dutch) 2025-11-12 17:35:11 +01:00
vcoppe
99d39c6d71 New translations edit.mdx (Lithuanian) 2025-11-12 17:35:09 +01:00
vcoppe
24d088ac79 New translations edit.mdx (Korean) 2025-11-12 17:35:08 +01:00
vcoppe
268ddd9aae New translations edit.mdx (Italian) 2025-11-12 17:35:07 +01:00
vcoppe
ace28b2051 New translations edit.mdx (Hungarian) 2025-11-12 17:35:06 +01:00
vcoppe
beb9b8c19c New translations edit.mdx (Hebrew) 2025-11-12 17:35:04 +01:00
vcoppe
9f1c23ea67 New translations edit.mdx (Finnish) 2025-11-12 17:35:03 +01:00
vcoppe
901a41dab1 New translations edit.mdx (Basque) 2025-11-12 17:35:02 +01:00
vcoppe
8777cb3a72 New translations edit.mdx (Greek) 2025-11-12 17:35:01 +01:00
vcoppe
c34a9719d9 New translations edit.mdx (German) 2025-11-12 17:34:59 +01:00
vcoppe
54a3535373 New translations edit.mdx (Danish) 2025-11-12 17:34:58 +01:00
vcoppe
7e2cecc838 New translations edit.mdx (Czech) 2025-11-12 17:34:57 +01:00
vcoppe
33a61c41d4 New translations edit.mdx (Catalan) 2025-11-12 17:34:56 +01:00
vcoppe
e21d5ea4ef New translations edit.mdx (Belarusian) 2025-11-12 17:34:54 +01:00
vcoppe
96c12b38ed New translations edit.mdx (Spanish) 2025-11-12 17:34:53 +01:00
vcoppe
2faa17a608 New translations edit.mdx (French) 2025-11-12 17:34:52 +01:00
vcoppe
5386d3b19f New translations edit.mdx (Romanian) 2025-11-12 17:34:50 +01:00
vcoppe
56b219ed0d New translations map-controls.mdx (Serbian (Latin)) 2025-11-12 17:34:36 +01:00
vcoppe
5bfd82272d New translations map-controls.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:34:35 +01:00
vcoppe
9af0480418 New translations map-controls.mdx (Latvian) 2025-11-12 17:34:33 +01:00
vcoppe
bcbd9535e5 New translations map-controls.mdx (Thai) 2025-11-12 17:34:32 +01:00
vcoppe
e4bf263fe2 New translations map-controls.mdx (Indonesian) 2025-11-12 17:34:31 +01:00
vcoppe
a5da416a54 New translations map-controls.mdx (Portuguese, Brazilian) 2025-11-12 17:34:30 +01:00
vcoppe
1dbc9c968f New translations map-controls.mdx (Vietnamese) 2025-11-12 17:34:29 +01:00
vcoppe
b5b6b293ca New translations map-controls.mdx (Chinese Simplified) 2025-11-12 17:34:27 +01:00
vcoppe
b7df027c05 New translations map-controls.mdx (Ukrainian) 2025-11-12 17:34:26 +01:00
vcoppe
6890c2b182 New translations map-controls.mdx (Turkish) 2025-11-12 17:34:25 +01:00
vcoppe
2f90728b82 New translations map-controls.mdx (Swedish) 2025-11-12 17:34:23 +01:00
vcoppe
d8e445315b New translations map-controls.mdx (Russian) 2025-11-12 17:34:22 +01:00
vcoppe
26d99cc790 New translations map-controls.mdx (Portuguese) 2025-11-12 17:34:21 +01:00
vcoppe
52c273d47f New translations map-controls.mdx (Polish) 2025-11-12 17:34:20 +01:00
vcoppe
9ebca23a96 New translations map-controls.mdx (Norwegian) 2025-11-12 17:34:19 +01:00
vcoppe
0775c745dd New translations map-controls.mdx (Dutch) 2025-11-12 17:34:17 +01:00
vcoppe
f4111a0185 New translations map-controls.mdx (Lithuanian) 2025-11-12 17:34:16 +01:00
vcoppe
6ad88accf4 New translations map-controls.mdx (Korean) 2025-11-12 17:34:15 +01:00
vcoppe
9669b7bd16 New translations map-controls.mdx (Italian) 2025-11-12 17:34:14 +01:00
vcoppe
a37e27a82f New translations map-controls.mdx (Hungarian) 2025-11-12 17:34:13 +01:00
vcoppe
85e347dfe2 New translations map-controls.mdx (Hebrew) 2025-11-12 17:34:11 +01:00
vcoppe
afcaa89cc3 New translations map-controls.mdx (Finnish) 2025-11-12 17:34:10 +01:00
vcoppe
c87c8952c4 New translations map-controls.mdx (Basque) 2025-11-12 17:34:08 +01:00
vcoppe
d08d8dacb4 New translations map-controls.mdx (Greek) 2025-11-12 17:34:07 +01:00
vcoppe
2fbad4fa91 New translations map-controls.mdx (German) 2025-11-12 17:34:06 +01:00
vcoppe
08731d2708 New translations map-controls.mdx (Danish) 2025-11-12 17:34:04 +01:00
vcoppe
a684b7c101 New translations map-controls.mdx (Czech) 2025-11-12 17:34:03 +01:00
vcoppe
6000d8c939 New translations map-controls.mdx (Catalan) 2025-11-12 17:34:02 +01:00
vcoppe
4a30157638 New translations map-controls.mdx (Belarusian) 2025-11-12 17:34:00 +01:00
vcoppe
16fc317543 New translations map-controls.mdx (Spanish) 2025-11-12 17:33:59 +01:00
vcoppe
40e6b801ef New translations map-controls.mdx (French) 2025-11-12 17:33:58 +01:00
vcoppe
e908b4d996 New translations map-controls.mdx (Romanian) 2025-11-12 17:33:56 +01:00
vcoppe
a4230df2be New translations translation.mdx (Serbian (Latin)) 2025-11-12 17:33:42 +01:00
vcoppe
37894a26e7 New translations translation.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:33:41 +01:00
vcoppe
d9ee36076c New translations translation.mdx (Latvian) 2025-11-12 17:33:40 +01:00
vcoppe
f9f07de8ef New translations translation.mdx (Thai) 2025-11-12 17:33:39 +01:00
vcoppe
141560e159 New translations translation.mdx (Indonesian) 2025-11-12 17:33:38 +01:00
vcoppe
f7a9697779 New translations translation.mdx (Portuguese, Brazilian) 2025-11-12 17:33:36 +01:00
vcoppe
e778e3e85f New translations translation.mdx (Vietnamese) 2025-11-12 17:33:35 +01:00
vcoppe
c227b5c37b New translations translation.mdx (Chinese Simplified) 2025-11-12 17:33:34 +01:00
vcoppe
bdf03ab962 New translations translation.mdx (Ukrainian) 2025-11-12 17:33:32 +01:00
vcoppe
07e2cdbac1 New translations translation.mdx (Turkish) 2025-11-12 17:33:31 +01:00
vcoppe
ec0c27c421 New translations translation.mdx (Swedish) 2025-11-12 17:33:30 +01:00
vcoppe
5670d47c5f New translations translation.mdx (Russian) 2025-11-12 17:33:28 +01:00
vcoppe
c8a3bf2d8a New translations translation.mdx (Portuguese) 2025-11-12 17:33:27 +01:00
vcoppe
cc28f877fd New translations translation.mdx (Polish) 2025-11-12 17:33:26 +01:00
vcoppe
39c746068a New translations translation.mdx (Norwegian) 2025-11-12 17:33:25 +01:00
vcoppe
f37ad668be New translations translation.mdx (Dutch) 2025-11-12 17:33:24 +01:00
vcoppe
e87561fb17 New translations translation.mdx (Lithuanian) 2025-11-12 17:33:22 +01:00
vcoppe
96084ca57f New translations translation.mdx (Korean) 2025-11-12 17:33:21 +01:00
vcoppe
186b60dcfe New translations translation.mdx (Italian) 2025-11-12 17:33:20 +01:00
vcoppe
aa10fbaf9f New translations translation.mdx (Hungarian) 2025-11-12 17:33:18 +01:00
vcoppe
2954b353c9 New translations translation.mdx (Hebrew) 2025-11-12 17:33:17 +01:00
vcoppe
63614e5e0c New translations translation.mdx (Finnish) 2025-11-12 17:33:15 +01:00
vcoppe
899ac29ec1 New translations translation.mdx (Basque) 2025-11-12 17:33:14 +01:00
vcoppe
722683fae2 New translations translation.mdx (Greek) 2025-11-12 17:33:13 +01:00
vcoppe
5eb82b5ef9 New translations translation.mdx (German) 2025-11-12 17:33:11 +01:00
vcoppe
89e8f0ba2d New translations translation.mdx (Danish) 2025-11-12 17:33:10 +01:00
vcoppe
f6cfde92c4 New translations translation.mdx (Czech) 2025-11-12 17:33:08 +01:00
vcoppe
35b7063ca8 New translations translation.mdx (Catalan) 2025-11-12 17:33:06 +01:00
vcoppe
23357de4db New translations translation.mdx (Belarusian) 2025-11-12 17:33:05 +01:00
vcoppe
0850db9f0d New translations translation.mdx (Spanish) 2025-11-12 17:33:03 +01:00
vcoppe
0206bb94be New translations translation.mdx (French) 2025-11-12 17:33:02 +01:00
vcoppe
d25bbd216d New translations translation.mdx (Romanian) 2025-11-12 17:33:01 +01:00
vcoppe
aedd584fbc New translations funding.mdx (Serbian (Latin)) 2025-11-12 17:32:46 +01:00
vcoppe
4a68e2c8be New translations funding.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:32:45 +01:00
vcoppe
926fbc7fe7 New translations funding.mdx (Latvian) 2025-11-12 17:32:44 +01:00
vcoppe
7cf4ced17d New translations funding.mdx (Thai) 2025-11-12 17:32:43 +01:00
vcoppe
b415c8b3da New translations funding.mdx (Indonesian) 2025-11-12 17:32:41 +01:00
vcoppe
b58273b98c New translations funding.mdx (Portuguese, Brazilian) 2025-11-12 17:32:40 +01:00
vcoppe
bd5f66b5d5 New translations funding.mdx (Vietnamese) 2025-11-12 17:32:39 +01:00
vcoppe
3abe640640 New translations funding.mdx (Chinese Simplified) 2025-11-12 17:32:37 +01:00
vcoppe
95bc685459 New translations funding.mdx (Ukrainian) 2025-11-12 17:32:36 +01:00
vcoppe
e65c8ceace New translations funding.mdx (Turkish) 2025-11-12 17:32:35 +01:00
vcoppe
d41080c4e3 New translations funding.mdx (Swedish) 2025-11-12 17:32:34 +01:00
vcoppe
95e4169108 New translations funding.mdx (Russian) 2025-11-12 17:32:33 +01:00
vcoppe
09ed3ffd3f New translations funding.mdx (Portuguese) 2025-11-12 17:32:31 +01:00
vcoppe
0cd8ac88d6 New translations funding.mdx (Polish) 2025-11-12 17:32:30 +01:00
vcoppe
99a022f647 New translations funding.mdx (Norwegian) 2025-11-12 17:32:29 +01:00
vcoppe
6aab3d842b New translations funding.mdx (Dutch) 2025-11-12 17:32:28 +01:00
vcoppe
430e004c60 New translations funding.mdx (Lithuanian) 2025-11-12 17:32:27 +01:00
vcoppe
022a2a9f78 New translations funding.mdx (Korean) 2025-11-12 17:32:25 +01:00
vcoppe
821657c447 New translations funding.mdx (Italian) 2025-11-12 17:32:24 +01:00
vcoppe
c03e7517a5 New translations funding.mdx (Hungarian) 2025-11-12 17:32:23 +01:00
vcoppe
a89032b5e4 New translations funding.mdx (Hebrew) 2025-11-12 17:32:21 +01:00
vcoppe
d6c55cf555 New translations funding.mdx (Finnish) 2025-11-12 17:32:20 +01:00
vcoppe
2da2f63503 New translations funding.mdx (Basque) 2025-11-12 17:32:19 +01:00
vcoppe
ab1bb2ee5d New translations funding.mdx (Greek) 2025-11-12 17:32:17 +01:00
vcoppe
326e9a82c9 New translations funding.mdx (German) 2025-11-12 17:32:16 +01:00
vcoppe
fbdba3ef69 New translations funding.mdx (Danish) 2025-11-12 17:32:15 +01:00
vcoppe
312c4e7211 New translations funding.mdx (Czech) 2025-11-12 17:32:13 +01:00
vcoppe
3386e1e6b0 New translations funding.mdx (Catalan) 2025-11-12 17:32:12 +01:00
vcoppe
a11565e94c New translations funding.mdx (Belarusian) 2025-11-12 17:32:10 +01:00
vcoppe
0c0e6d5017 New translations funding.mdx (Spanish) 2025-11-12 17:32:05 +01:00
vcoppe
4c33e88c63 New translations funding.mdx (French) 2025-11-12 17:32:04 +01:00
vcoppe
aa582fb0d8 New translations funding.mdx (Romanian) 2025-11-12 17:32:03 +01:00
vcoppe
c38fdf8ccc New translations gpx.mdx (Serbian (Latin)) 2025-11-12 17:32:01 +01:00
vcoppe
305c2743ba New translations gpx.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:32:00 +01:00
vcoppe
5cc085c6a6 New translations gpx.mdx (Latvian) 2025-11-12 17:31:59 +01:00
vcoppe
3859779f0a New translations gpx.mdx (Thai) 2025-11-12 17:31:58 +01:00
vcoppe
9857469cd1 New translations gpx.mdx (Indonesian) 2025-11-12 17:31:57 +01:00
vcoppe
ff805d32fb New translations gpx.mdx (Portuguese, Brazilian) 2025-11-12 17:31:55 +01:00
vcoppe
5a94e58236 New translations gpx.mdx (Vietnamese) 2025-11-12 17:31:54 +01:00
vcoppe
9ff7da58ea New translations gpx.mdx (Chinese Simplified) 2025-11-12 17:31:52 +01:00
vcoppe
30d974ff4a New translations gpx.mdx (Ukrainian) 2025-11-12 17:31:51 +01:00
vcoppe
11709dc594 New translations gpx.mdx (Turkish) 2025-11-12 17:31:50 +01:00
vcoppe
246c31bac4 New translations gpx.mdx (Swedish) 2025-11-12 17:31:49 +01:00
vcoppe
2a8cc9ad70 New translations gpx.mdx (Russian) 2025-11-12 17:31:47 +01:00
vcoppe
098844bb4a New translations gpx.mdx (Portuguese) 2025-11-12 17:31:46 +01:00
vcoppe
11a064a05b New translations gpx.mdx (Polish) 2025-11-12 17:31:45 +01:00
vcoppe
b2adf99c2a New translations gpx.mdx (Norwegian) 2025-11-12 17:31:44 +01:00
vcoppe
937d9fab36 New translations gpx.mdx (Dutch) 2025-11-12 17:31:42 +01:00
vcoppe
3c4d5a8324 New translations gpx.mdx (Lithuanian) 2025-11-12 17:31:41 +01:00
vcoppe
d66ae036e5 New translations gpx.mdx (Korean) 2025-11-12 17:31:40 +01:00
vcoppe
28dcb92f4c New translations gpx.mdx (Italian) 2025-11-12 17:31:38 +01:00
vcoppe
162059df91 New translations gpx.mdx (Hungarian) 2025-11-12 17:31:37 +01:00
vcoppe
439085c873 New translations gpx.mdx (Hebrew) 2025-11-12 17:31:36 +01:00
vcoppe
e3c34b6e8b New translations gpx.mdx (Finnish) 2025-11-12 17:31:35 +01:00
vcoppe
8a6e60f961 New translations gpx.mdx (Basque) 2025-11-12 17:31:33 +01:00
vcoppe
eedcf5b2e8 New translations gpx.mdx (Greek) 2025-11-12 17:31:32 +01:00
vcoppe
bad3cb5c7e New translations gpx.mdx (German) 2025-11-12 17:31:31 +01:00
vcoppe
2351a16fae New translations gpx.mdx (Danish) 2025-11-12 17:31:29 +01:00
vcoppe
5779c11097 New translations gpx.mdx (Czech) 2025-11-12 17:31:28 +01:00
vcoppe
ab34ab3429 New translations gpx.mdx (Catalan) 2025-11-12 17:31:27 +01:00
vcoppe
98487a18e5 New translations gpx.mdx (Belarusian) 2025-11-12 17:31:26 +01:00
vcoppe
fae46c2af0 New translations gpx.mdx (Spanish) 2025-11-12 17:31:24 +01:00
vcoppe
2725d3a386 New translations gpx.mdx (French) 2025-11-12 17:31:23 +01:00
vcoppe
4601d61385 New translations gpx.mdx (Romanian) 2025-11-12 17:31:21 +01:00
vcoppe
2b1081b523 New translations files-and-stats.mdx (Serbian (Latin)) 2025-11-12 17:31:05 +01:00
vcoppe
c2bcbd3597 New translations files-and-stats.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:31:03 +01:00
vcoppe
d4367475fb New translations files-and-stats.mdx (Latvian) 2025-11-12 17:31:02 +01:00
vcoppe
e62d7b2132 New translations files-and-stats.mdx (Thai) 2025-11-12 17:31:00 +01:00
vcoppe
13486e3dfa New translations files-and-stats.mdx (Indonesian) 2025-11-12 17:30:59 +01:00
vcoppe
34e4069b2f New translations files-and-stats.mdx (Portuguese, Brazilian) 2025-11-12 17:30:58 +01:00
vcoppe
5ed921d8dc New translations files-and-stats.mdx (Vietnamese) 2025-11-12 17:30:56 +01:00
vcoppe
6e9c2191eb New translations files-and-stats.mdx (Chinese Simplified) 2025-11-12 17:30:55 +01:00
vcoppe
afb9f1ba26 New translations files-and-stats.mdx (Ukrainian) 2025-11-12 17:30:54 +01:00
vcoppe
acc9fd8101 New translations files-and-stats.mdx (Turkish) 2025-11-12 17:30:53 +01:00
vcoppe
60f9b4ae33 New translations files-and-stats.mdx (Swedish) 2025-11-12 17:30:51 +01:00
vcoppe
8d3e241869 New translations files-and-stats.mdx (Russian) 2025-11-12 17:30:50 +01:00
vcoppe
0cfd35e74f New translations files-and-stats.mdx (Portuguese) 2025-11-12 17:30:49 +01:00
vcoppe
dba8fa3a42 New translations files-and-stats.mdx (Polish) 2025-11-12 17:30:47 +01:00
vcoppe
bf4d7101b8 New translations files-and-stats.mdx (Norwegian) 2025-11-12 17:30:46 +01:00
vcoppe
bff1d5fa6f New translations routing.mdx (Czech) 2025-11-12 17:30:45 +01:00
vcoppe
f8f5a59520 New translations files-and-stats.mdx (Dutch) 2025-11-12 17:30:43 +01:00
vcoppe
dc2bd3a5d6 New translations files-and-stats.mdx (Lithuanian) 2025-11-12 17:30:42 +01:00
vcoppe
5b7cf32314 New translations files-and-stats.mdx (Korean) 2025-11-12 17:30:41 +01:00
vcoppe
5f99680b01 New translations files-and-stats.mdx (Italian) 2025-11-12 17:30:39 +01:00
vcoppe
f76c4b0405 New translations files-and-stats.mdx (Hungarian) 2025-11-12 17:30:37 +01:00
vcoppe
d8425ce831 New translations files-and-stats.mdx (Hebrew) 2025-11-12 17:30:36 +01:00
vcoppe
7e42e5bb54 New translations files-and-stats.mdx (Finnish) 2025-11-12 17:30:34 +01:00
vcoppe
7c2533b6fb New translations files-and-stats.mdx (Basque) 2025-11-12 17:30:33 +01:00
vcoppe
ae05034927 New translations files-and-stats.mdx (Greek) 2025-11-12 17:30:31 +01:00
vcoppe
1c331d57b3 New translations files-and-stats.mdx (German) 2025-11-12 17:30:30 +01:00
vcoppe
db5cda70ad New translations files-and-stats.mdx (Danish) 2025-11-12 17:30:28 +01:00
vcoppe
8196ac451d New translations files-and-stats.mdx (Czech) 2025-11-12 17:30:27 +01:00
vcoppe
e2cfeec10a New translations files-and-stats.mdx (Catalan) 2025-11-12 17:30:26 +01:00
vcoppe
9923c1ae65 New translations files-and-stats.mdx (Belarusian) 2025-11-12 17:30:24 +01:00
vcoppe
957634f90c New translations files-and-stats.mdx (Spanish) 2025-11-12 17:30:23 +01:00
vcoppe
6be78b2a54 New translations files-and-stats.mdx (French) 2025-11-12 17:30:21 +01:00
vcoppe
f04972666c New translations files-and-stats.mdx (Romanian) 2025-11-12 17:30:20 +01:00
vcoppe
e25caa6e03 New translations elevation.mdx (Serbian (Latin)) 2025-11-12 17:27:56 +01:00
vcoppe
1525bc447d New translations elevation.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:27:55 +01:00
vcoppe
700dc9e81d New translations elevation.mdx (Latvian) 2025-11-12 17:27:54 +01:00
vcoppe
6aafd74ddf New translations elevation.mdx (Thai) 2025-11-12 17:27:53 +01:00
vcoppe
f519ff427b New translations elevation.mdx (Indonesian) 2025-11-12 17:27:52 +01:00
vcoppe
a89ec4df27 New translations elevation.mdx (Portuguese, Brazilian) 2025-11-12 17:27:51 +01:00
vcoppe
c4cb648a2a New translations elevation.mdx (Vietnamese) 2025-11-12 17:27:50 +01:00
vcoppe
0c8c3feb0c New translations elevation.mdx (Chinese Simplified) 2025-11-12 17:27:49 +01:00
vcoppe
5529eeaa72 New translations elevation.mdx (Ukrainian) 2025-11-12 17:27:48 +01:00
vcoppe
8b1055bdda New translations elevation.mdx (Turkish) 2025-11-12 17:27:47 +01:00
vcoppe
df23d9fe63 New translations elevation.mdx (Swedish) 2025-11-12 17:27:46 +01:00
vcoppe
18328711b2 New translations elevation.mdx (Russian) 2025-11-12 17:27:45 +01:00
vcoppe
dc996ed44c New translations elevation.mdx (Portuguese) 2025-11-12 17:27:44 +01:00
vcoppe
e2bc6e44e2 New translations elevation.mdx (Polish) 2025-11-12 17:27:43 +01:00
vcoppe
b667718fc2 New translations elevation.mdx (Norwegian) 2025-11-12 17:27:43 +01:00
vcoppe
4ae5707068 New translations elevation.mdx (Dutch) 2025-11-12 17:27:42 +01:00
vcoppe
a14e8dbc52 New translations elevation.mdx (Lithuanian) 2025-11-12 17:27:41 +01:00
vcoppe
a9344e0069 New translations elevation.mdx (Korean) 2025-11-12 17:27:40 +01:00
vcoppe
9cdf831dd1 New translations elevation.mdx (Italian) 2025-11-12 17:27:39 +01:00
vcoppe
0857e2fee3 New translations elevation.mdx (Hungarian) 2025-11-12 17:27:38 +01:00
vcoppe
69553c3bf3 New translations elevation.mdx (Hebrew) 2025-11-12 17:27:37 +01:00
vcoppe
5ce5da1443 New translations elevation.mdx (Finnish) 2025-11-12 17:27:36 +01:00
vcoppe
ede95247c7 New translations elevation.mdx (Basque) 2025-11-12 17:27:35 +01:00
vcoppe
9b7294c34c New translations elevation.mdx (Greek) 2025-11-12 17:27:34 +01:00
vcoppe
58ebe8c5eb New translations elevation.mdx (German) 2025-11-12 17:27:33 +01:00
vcoppe
67ed742858 New translations elevation.mdx (Danish) 2025-11-12 17:27:32 +01:00
vcoppe
538a6680e7 New translations elevation.mdx (Czech) 2025-11-12 17:27:31 +01:00
vcoppe
e16d42af87 New translations elevation.mdx (Catalan) 2025-11-12 17:27:30 +01:00
vcoppe
6ee62a7e1f New translations elevation.mdx (Belarusian) 2025-11-12 17:27:29 +01:00
vcoppe
3adfe54788 New translations elevation.mdx (Spanish) 2025-11-12 17:27:28 +01:00
vcoppe
673ac6503f New translations elevation.mdx (French) 2025-11-12 17:27:27 +01:00
vcoppe
b226b87b4b New translations elevation.mdx (Romanian) 2025-11-12 17:27:26 +01:00
vcoppe
a62c0c623a New translations time.mdx (Serbian (Latin)) 2025-11-12 17:27:11 +01:00
vcoppe
e7981f61bb New translations time.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:27:10 +01:00
vcoppe
cfe131815e New translations time.mdx (Latvian) 2025-11-12 17:27:09 +01:00
vcoppe
bee1b32f4c New translations time.mdx (Thai) 2025-11-12 17:27:08 +01:00
vcoppe
5861d61444 New translations time.mdx (Indonesian) 2025-11-12 17:27:07 +01:00
vcoppe
9852f41683 New translations time.mdx (Portuguese, Brazilian) 2025-11-12 17:27:06 +01:00
vcoppe
d1f8ae2c3d New translations time.mdx (Vietnamese) 2025-11-12 17:27:05 +01:00
vcoppe
bdc1c9d6c2 New translations time.mdx (Chinese Simplified) 2025-11-12 17:27:04 +01:00
vcoppe
2b7a7b1da8 New translations time.mdx (Ukrainian) 2025-11-12 17:27:02 +01:00
vcoppe
9ef115b74f New translations time.mdx (Turkish) 2025-11-12 17:27:01 +01:00
vcoppe
3e1395466c New translations time.mdx (Swedish) 2025-11-12 17:27:00 +01:00
vcoppe
54c667cbed New translations time.mdx (Russian) 2025-11-12 17:26:59 +01:00
vcoppe
1e224c588d New translations time.mdx (Portuguese) 2025-11-12 17:26:58 +01:00
vcoppe
5bd410cc4c New translations time.mdx (Polish) 2025-11-12 17:26:57 +01:00
vcoppe
67ad2676a1 New translations time.mdx (Norwegian) 2025-11-12 17:26:56 +01:00
vcoppe
0910f4c002 New translations time.mdx (Dutch) 2025-11-12 17:26:55 +01:00
vcoppe
ea8e91c0cc New translations time.mdx (Lithuanian) 2025-11-12 17:26:54 +01:00
vcoppe
db5517e0c9 New translations time.mdx (Korean) 2025-11-12 17:26:53 +01:00
vcoppe
77d787d5b3 New translations time.mdx (Italian) 2025-11-12 17:26:52 +01:00
vcoppe
69f3180b63 New translations time.mdx (Hungarian) 2025-11-12 17:26:51 +01:00
vcoppe
ee2abb0517 New translations time.mdx (Hebrew) 2025-11-12 17:26:50 +01:00
vcoppe
d7a938a90b New translations time.mdx (Finnish) 2025-11-12 17:26:49 +01:00
vcoppe
4d292a077a New translations time.mdx (Basque) 2025-11-12 17:26:48 +01:00
vcoppe
526c9d674d New translations time.mdx (Greek) 2025-11-12 17:26:47 +01:00
vcoppe
36f624da04 New translations time.mdx (German) 2025-11-12 17:26:46 +01:00
vcoppe
9d2a77599e New translations time.mdx (Danish) 2025-11-12 17:26:45 +01:00
vcoppe
e9fbe7d303 New translations time.mdx (Czech) 2025-11-12 17:26:44 +01:00
vcoppe
47757bde94 New translations time.mdx (Catalan) 2025-11-12 17:26:43 +01:00
vcoppe
f71c410d36 New translations time.mdx (Belarusian) 2025-11-12 17:26:42 +01:00
vcoppe
8f8a3f8de6 New translations time.mdx (Spanish) 2025-11-12 17:26:41 +01:00
vcoppe
a338e87b2e New translations time.mdx (French) 2025-11-12 17:26:40 +01:00
vcoppe
5801c5ddd0 New translations time.mdx (Romanian) 2025-11-12 17:26:40 +01:00
vcoppe
5b71da0223 New translations scissors.mdx (Serbian (Latin)) 2025-11-12 17:26:39 +01:00
vcoppe
ed85728729 New translations scissors.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:26:38 +01:00
vcoppe
9b5b661f1c New translations scissors.mdx (Latvian) 2025-11-12 17:26:37 +01:00
vcoppe
a5790aaba3 New translations scissors.mdx (Thai) 2025-11-12 17:26:36 +01:00
vcoppe
698b149271 New translations scissors.mdx (Indonesian) 2025-11-12 17:26:35 +01:00
vcoppe
15c28b0ecb New translations scissors.mdx (Portuguese, Brazilian) 2025-11-12 17:26:34 +01:00
vcoppe
b38f533ccc New translations scissors.mdx (Vietnamese) 2025-11-12 17:26:33 +01:00
vcoppe
5d61376486 New translations scissors.mdx (Chinese Simplified) 2025-11-12 17:26:32 +01:00
vcoppe
28c9e5ff78 New translations scissors.mdx (Ukrainian) 2025-11-12 17:26:31 +01:00
vcoppe
a35f978a08 New translations scissors.mdx (Turkish) 2025-11-12 17:26:30 +01:00
vcoppe
8fb8ecf9f9 New translations scissors.mdx (Swedish) 2025-11-12 17:26:29 +01:00
vcoppe
c8be4d1e4a New translations scissors.mdx (Russian) 2025-11-12 17:26:27 +01:00
vcoppe
8a13c908f8 New translations scissors.mdx (Portuguese) 2025-11-12 17:26:26 +01:00
vcoppe
0ab9cab46d New translations scissors.mdx (Polish) 2025-11-12 17:26:26 +01:00
vcoppe
ae9e31a127 New translations scissors.mdx (Norwegian) 2025-11-12 17:26:24 +01:00
vcoppe
ea74d066ac New translations scissors.mdx (Dutch) 2025-11-12 17:26:23 +01:00
vcoppe
3a8da3a818 New translations scissors.mdx (Lithuanian) 2025-11-12 17:26:23 +01:00
vcoppe
7b2273d180 New translations scissors.mdx (Korean) 2025-11-12 17:26:22 +01:00
vcoppe
2bba112bc0 New translations scissors.mdx (Italian) 2025-11-12 17:26:21 +01:00
vcoppe
f747ac2fc9 New translations scissors.mdx (Hungarian) 2025-11-12 17:26:19 +01:00
vcoppe
50112ef89f New translations scissors.mdx (Hebrew) 2025-11-12 17:26:18 +01:00
vcoppe
c5cce0a87b New translations scissors.mdx (Finnish) 2025-11-12 17:26:17 +01:00
vcoppe
bae21c87f4 New translations scissors.mdx (Basque) 2025-11-12 17:26:17 +01:00
vcoppe
edf77a6c0f New translations scissors.mdx (Greek) 2025-11-12 17:26:15 +01:00
vcoppe
232a0b2acf New translations scissors.mdx (German) 2025-11-12 17:26:14 +01:00
vcoppe
06381cb094 New translations scissors.mdx (Danish) 2025-11-12 17:26:13 +01:00
vcoppe
33a16bdd51 New translations scissors.mdx (Czech) 2025-11-12 17:26:12 +01:00
vcoppe
1b1276cc90 New translations scissors.mdx (Catalan) 2025-11-12 17:26:11 +01:00
vcoppe
8d2d7a8884 New translations scissors.mdx (Belarusian) 2025-11-12 17:26:10 +01:00
vcoppe
3894c12505 New translations scissors.mdx (Spanish) 2025-11-12 17:26:10 +01:00
vcoppe
504d952581 New translations scissors.mdx (French) 2025-11-12 17:26:09 +01:00
vcoppe
e3777ac18a New translations scissors.mdx (Romanian) 2025-11-12 17:26:08 +01:00
vcoppe
4b087d8943 New translations merge.mdx (Serbian (Latin)) 2025-11-12 17:25:26 +01:00
vcoppe
d383db5874 New translations merge.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:25:24 +01:00
vcoppe
fa75a76ed8 New translations merge.mdx (Latvian) 2025-11-12 17:25:23 +01:00
vcoppe
bc28507ea6 New translations merge.mdx (Thai) 2025-11-12 17:25:22 +01:00
vcoppe
e470df06ec New translations merge.mdx (Indonesian) 2025-11-12 17:25:21 +01:00
vcoppe
9b81157d5d New translations merge.mdx (Portuguese, Brazilian) 2025-11-12 17:25:20 +01:00
vcoppe
40bf919931 New translations merge.mdx (Vietnamese) 2025-11-12 17:25:19 +01:00
vcoppe
e55f7af9b3 New translations merge.mdx (Chinese Simplified) 2025-11-12 17:25:18 +01:00
vcoppe
40495dc86f New translations merge.mdx (Ukrainian) 2025-11-12 17:25:17 +01:00
vcoppe
75f01ca497 New translations merge.mdx (Turkish) 2025-11-12 17:25:16 +01:00
vcoppe
6ebdf327e3 New translations merge.mdx (Swedish) 2025-11-12 17:25:15 +01:00
vcoppe
9741c45e52 New translations merge.mdx (Russian) 2025-11-12 17:25:14 +01:00
vcoppe
d193929dd7 New translations merge.mdx (Portuguese) 2025-11-12 17:25:13 +01:00
vcoppe
19cdc8b3cf New translations merge.mdx (Polish) 2025-11-12 17:25:12 +01:00
vcoppe
8ac97c332d New translations merge.mdx (Norwegian) 2025-11-12 17:25:10 +01:00
vcoppe
1b61bbafa9 New translations merge.mdx (Dutch) 2025-11-12 17:25:09 +01:00
vcoppe
9745b308d2 New translations merge.mdx (Lithuanian) 2025-11-12 17:25:08 +01:00
vcoppe
3f2e162523 New translations merge.mdx (Korean) 2025-11-12 17:25:07 +01:00
vcoppe
9669afb772 New translations merge.mdx (Italian) 2025-11-12 17:25:06 +01:00
vcoppe
105842023b New translations merge.mdx (Hungarian) 2025-11-12 17:25:05 +01:00
vcoppe
224ce99d79 New translations merge.mdx (Hebrew) 2025-11-12 17:25:05 +01:00
vcoppe
1d696edf56 New translations merge.mdx (Finnish) 2025-11-12 17:25:03 +01:00
vcoppe
ae8e244418 New translations merge.mdx (Basque) 2025-11-12 17:25:02 +01:00
vcoppe
a1f51e9400 New translations merge.mdx (Greek) 2025-11-12 17:25:02 +01:00
vcoppe
d0c5392cf9 New translations merge.mdx (German) 2025-11-12 17:25:01 +01:00
vcoppe
998c597e17 New translations merge.mdx (Danish) 2025-11-12 17:25:00 +01:00
vcoppe
c943ffd30a New translations merge.mdx (Czech) 2025-11-12 17:24:59 +01:00
vcoppe
1f54884a38 New translations merge.mdx (Catalan) 2025-11-12 17:24:58 +01:00
vcoppe
df0c91a448 New translations merge.mdx (Belarusian) 2025-11-12 17:24:57 +01:00
vcoppe
cceaaa5106 New translations merge.mdx (Spanish) 2025-11-12 17:24:56 +01:00
vcoppe
380aa3d178 New translations merge.mdx (French) 2025-11-12 17:24:55 +01:00
vcoppe
e1624b4fe6 New translations merge.mdx (Romanian) 2025-11-12 17:24:54 +01:00
vcoppe
e3bf1e8ca1 New translations extract.mdx (Serbian (Latin)) 2025-11-12 17:24:53 +01:00
vcoppe
6cb2553cc3 New translations extract.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:24:52 +01:00
vcoppe
5ad4e86b8a New translations extract.mdx (Latvian) 2025-11-12 17:24:51 +01:00
vcoppe
4d392ff48e New translations extract.mdx (Thai) 2025-11-12 17:24:50 +01:00
vcoppe
16e542d302 New translations extract.mdx (Indonesian) 2025-11-12 17:24:50 +01:00
vcoppe
3a252c911c New translations extract.mdx (Portuguese, Brazilian) 2025-11-12 17:24:49 +01:00
vcoppe
128ce34a41 New translations extract.mdx (Vietnamese) 2025-11-12 17:24:48 +01:00
vcoppe
b65f7231f8 New translations extract.mdx (Chinese Simplified) 2025-11-12 17:24:47 +01:00
vcoppe
7412faf534 New translations extract.mdx (Ukrainian) 2025-11-12 17:24:46 +01:00
vcoppe
ac4dd28927 New translations extract.mdx (Turkish) 2025-11-12 17:24:45 +01:00
vcoppe
1672451def New translations extract.mdx (Swedish) 2025-11-12 17:24:44 +01:00
vcoppe
96269ba437 New translations extract.mdx (Russian) 2025-11-12 17:24:43 +01:00
vcoppe
320a6311c5 New translations extract.mdx (Portuguese) 2025-11-12 17:24:42 +01:00
vcoppe
7c2a96b04a New translations extract.mdx (Polish) 2025-11-12 17:24:41 +01:00
vcoppe
704359cdba New translations extract.mdx (Norwegian) 2025-11-12 17:24:40 +01:00
vcoppe
87a1a893bf New translations extract.mdx (Dutch) 2025-11-12 17:24:39 +01:00
vcoppe
66421250b4 New translations extract.mdx (Lithuanian) 2025-11-12 17:24:38 +01:00
vcoppe
374f2b22b5 New translations extract.mdx (Korean) 2025-11-12 17:24:37 +01:00
vcoppe
5b3a287bf4 New translations extract.mdx (Italian) 2025-11-12 17:24:36 +01:00
vcoppe
56bc9ca01a New translations extract.mdx (Hungarian) 2025-11-12 17:24:35 +01:00
vcoppe
18dcbf3568 New translations extract.mdx (Hebrew) 2025-11-12 17:24:34 +01:00
vcoppe
bfc8dd681d New translations extract.mdx (Finnish) 2025-11-12 17:24:33 +01:00
vcoppe
86412f34f3 New translations extract.mdx (Basque) 2025-11-12 17:24:32 +01:00
vcoppe
fc416db742 New translations extract.mdx (Greek) 2025-11-12 17:24:31 +01:00
vcoppe
a6a5092abd New translations extract.mdx (German) 2025-11-12 17:24:30 +01:00
vcoppe
522360bbe7 New translations extract.mdx (Danish) 2025-11-12 17:24:29 +01:00
vcoppe
ed5b6e4b1d New translations extract.mdx (Czech) 2025-11-12 17:24:28 +01:00
vcoppe
4224fe3211 New translations extract.mdx (Catalan) 2025-11-12 17:24:28 +01:00
vcoppe
cb99d5b007 New translations extract.mdx (Belarusian) 2025-11-12 17:24:27 +01:00
vcoppe
be1ba18f7f New translations extract.mdx (Spanish) 2025-11-12 17:24:26 +01:00
vcoppe
52b54d8584 New translations extract.mdx (French) 2025-11-12 17:24:25 +01:00
vcoppe
665822d2f8 New translations extract.mdx (Romanian) 2025-11-12 17:24:24 +01:00
vcoppe
fbfa3b2974 New translations clean.mdx (Serbian (Latin)) 2025-11-12 17:24:23 +01:00
vcoppe
b3b0ef8f0b New translations clean.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:24:22 +01:00
vcoppe
d255b29832 New translations clean.mdx (Latvian) 2025-11-12 17:24:21 +01:00
vcoppe
5a0198d10d New translations clean.mdx (Thai) 2025-11-12 17:24:20 +01:00
vcoppe
3781a118a1 New translations clean.mdx (Indonesian) 2025-11-12 17:24:19 +01:00
vcoppe
ca3262d554 New translations clean.mdx (Portuguese, Brazilian) 2025-11-12 17:24:18 +01:00
vcoppe
c5194b254a New translations clean.mdx (Vietnamese) 2025-11-12 17:24:17 +01:00
vcoppe
4a369b8af7 New translations clean.mdx (Chinese Simplified) 2025-11-12 17:24:16 +01:00
vcoppe
93bddd469d New translations clean.mdx (Ukrainian) 2025-11-12 17:24:15 +01:00
vcoppe
8cf3d028b7 New translations clean.mdx (Turkish) 2025-11-12 17:24:14 +01:00
vcoppe
23af660dc0 New translations clean.mdx (Swedish) 2025-11-12 17:24:13 +01:00
vcoppe
11119654eb New translations clean.mdx (Russian) 2025-11-12 17:24:12 +01:00
vcoppe
6f9d3b7fa9 New translations clean.mdx (Portuguese) 2025-11-12 17:24:11 +01:00
vcoppe
0c64ffd362 New translations clean.mdx (Polish) 2025-11-12 17:24:10 +01:00
vcoppe
5d316d4d94 New translations clean.mdx (Norwegian) 2025-11-12 17:24:09 +01:00
vcoppe
aa3df50a30 New translations clean.mdx (Dutch) 2025-11-12 17:24:08 +01:00
vcoppe
8308a78834 New translations clean.mdx (Lithuanian) 2025-11-12 17:24:07 +01:00
vcoppe
c02a0f27ca New translations clean.mdx (Korean) 2025-11-12 17:24:06 +01:00
vcoppe
d36d798c1b New translations clean.mdx (Italian) 2025-11-12 17:24:05 +01:00
vcoppe
7eb5ed7bcd New translations clean.mdx (Hungarian) 2025-11-12 17:24:04 +01:00
vcoppe
584aa6bf5f New translations clean.mdx (Hebrew) 2025-11-12 17:24:03 +01:00
vcoppe
d90915e25b New translations clean.mdx (Finnish) 2025-11-12 17:24:02 +01:00
vcoppe
e787abb6de New translations clean.mdx (Basque) 2025-11-12 17:24:01 +01:00
vcoppe
7e4c2f2cf3 New translations clean.mdx (Greek) 2025-11-12 17:24:00 +01:00
vcoppe
fb709389c4 New translations clean.mdx (German) 2025-11-12 17:23:59 +01:00
vcoppe
f22fe9eec1 New translations clean.mdx (Danish) 2025-11-12 17:23:58 +01:00
vcoppe
4352309f9f New translations clean.mdx (Czech) 2025-11-12 17:23:57 +01:00
vcoppe
687f3533c8 New translations clean.mdx (Catalan) 2025-11-12 17:23:56 +01:00
vcoppe
2b99240be3 New translations clean.mdx (Belarusian) 2025-11-12 17:23:55 +01:00
vcoppe
e03fa42d63 New translations clean.mdx (Spanish) 2025-11-12 17:23:54 +01:00
vcoppe
8639ed4d00 New translations clean.mdx (French) 2025-11-12 17:23:53 +01:00
vcoppe
cf9dca228a New translations clean.mdx (Romanian) 2025-11-12 17:23:52 +01:00
vcoppe
be66de7584 New translations view.mdx (Serbian (Latin)) 2025-11-12 17:23:37 +01:00
vcoppe
f47b73ee01 New translations view.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:23:36 +01:00
vcoppe
d5703b0e43 New translations view.mdx (Latvian) 2025-11-12 17:23:35 +01:00
vcoppe
293e10e944 New translations view.mdx (Thai) 2025-11-12 17:23:34 +01:00
vcoppe
328baea33b New translations view.mdx (Indonesian) 2025-11-12 17:23:34 +01:00
vcoppe
6c0b38d946 New translations view.mdx (Portuguese, Brazilian) 2025-11-12 17:23:32 +01:00
vcoppe
c0221fc0e5 New translations view.mdx (Vietnamese) 2025-11-12 17:23:32 +01:00
vcoppe
f1418ee99d New translations view.mdx (Chinese Simplified) 2025-11-12 17:23:31 +01:00
vcoppe
111bd40b94 New translations view.mdx (Ukrainian) 2025-11-12 17:23:30 +01:00
vcoppe
ef477f5642 New translations view.mdx (Turkish) 2025-11-12 17:23:28 +01:00
vcoppe
c785fc957c New translations view.mdx (Swedish) 2025-11-12 17:23:28 +01:00
vcoppe
eeaeed9d83 New translations view.mdx (Russian) 2025-11-12 17:23:27 +01:00
vcoppe
483d66b2cd New translations view.mdx (Portuguese) 2025-11-12 17:23:26 +01:00
vcoppe
e2332ee760 New translations view.mdx (Polish) 2025-11-12 17:23:25 +01:00
vcoppe
14e3cb049e New translations view.mdx (Norwegian) 2025-11-12 17:23:24 +01:00
vcoppe
c021d2fa5b New translations view.mdx (Dutch) 2025-11-12 17:23:23 +01:00
vcoppe
20e365b0c0 New translations view.mdx (Lithuanian) 2025-11-12 17:23:22 +01:00
vcoppe
1ef4c9878d New translations view.mdx (Korean) 2025-11-12 17:23:21 +01:00
vcoppe
7e47a68acf New translations view.mdx (Italian) 2025-11-12 17:23:20 +01:00
vcoppe
fe5502e0dd New translations view.mdx (Hungarian) 2025-11-12 17:23:19 +01:00
vcoppe
392cc2ba43 New translations view.mdx (Hebrew) 2025-11-12 17:23:18 +01:00
vcoppe
b93c15bc38 New translations view.mdx (Finnish) 2025-11-12 17:23:17 +01:00
vcoppe
5d86c6f0e1 New translations view.mdx (Basque) 2025-11-12 17:23:16 +01:00
vcoppe
dbfca7c677 New translations view.mdx (Greek) 2025-11-12 17:23:15 +01:00
vcoppe
714c8d2e1c New translations view.mdx (German) 2025-11-12 17:23:14 +01:00
vcoppe
e6e0f259f4 New translations view.mdx (Danish) 2025-11-12 17:23:13 +01:00
vcoppe
a03580a64d New translations view.mdx (Czech) 2025-11-12 17:23:12 +01:00
vcoppe
588f80c282 New translations view.mdx (Catalan) 2025-11-12 17:23:11 +01:00
vcoppe
4e3bedac31 New translations view.mdx (Belarusian) 2025-11-12 17:23:10 +01:00
vcoppe
953c1eb639 New translations view.mdx (Spanish) 2025-11-12 17:23:09 +01:00
vcoppe
aa4f14c461 New translations view.mdx (French) 2025-11-12 17:23:08 +01:00
vcoppe
6937c86e66 New translations view.mdx (Romanian) 2025-11-12 17:23:07 +01:00
vcoppe
c1ea4741eb New translations settings.mdx (Serbian (Latin)) 2025-11-12 17:23:05 +01:00
vcoppe
d4d5edd0f3 New translations settings.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:23:04 +01:00
vcoppe
3d8dd63fad New translations settings.mdx (Latvian) 2025-11-12 17:23:03 +01:00
vcoppe
8bc6477e88 New translations settings.mdx (Thai) 2025-11-12 17:23:02 +01:00
vcoppe
5743814d90 New translations settings.mdx (Indonesian) 2025-11-12 17:23:01 +01:00
vcoppe
d631b3d799 New translations settings.mdx (Portuguese, Brazilian) 2025-11-12 17:23:00 +01:00
vcoppe
6b4cf58454 New translations settings.mdx (Vietnamese) 2025-11-12 17:22:59 +01:00
vcoppe
e864e7c2bf New translations settings.mdx (Chinese Simplified) 2025-11-12 17:22:58 +01:00
vcoppe
ebca3f0f5f New translations settings.mdx (Ukrainian) 2025-11-12 17:22:57 +01:00
vcoppe
8216ba11b8 New translations settings.mdx (Turkish) 2025-11-12 17:22:56 +01:00
vcoppe
ae67f84476 New translations settings.mdx (Swedish) 2025-11-12 17:22:56 +01:00
vcoppe
59f1313bb2 New translations settings.mdx (Russian) 2025-11-12 17:22:54 +01:00
vcoppe
04341c8c3c New translations settings.mdx (Portuguese) 2025-11-12 17:22:53 +01:00
vcoppe
0a315b50b4 New translations settings.mdx (Polish) 2025-11-12 17:22:52 +01:00
vcoppe
36a453f6c0 New translations settings.mdx (Norwegian) 2025-11-12 17:22:51 +01:00
vcoppe
991f079deb New translations settings.mdx (Dutch) 2025-11-12 17:22:50 +01:00
vcoppe
5110b4449c New translations settings.mdx (Lithuanian) 2025-11-12 17:22:49 +01:00
vcoppe
0e8fb9d56d New translations settings.mdx (Korean) 2025-11-12 17:22:48 +01:00
vcoppe
868dd01952 New translations settings.mdx (Italian) 2025-11-12 17:22:47 +01:00
vcoppe
47be4ed8dc New translations settings.mdx (Hungarian) 2025-11-12 17:22:46 +01:00
vcoppe
392c00a635 New translations settings.mdx (Hebrew) 2025-11-12 17:22:45 +01:00
vcoppe
aaa99ded43 New translations settings.mdx (Finnish) 2025-11-12 17:22:44 +01:00
vcoppe
838a6be13d New translations settings.mdx (Basque) 2025-11-12 17:22:43 +01:00
vcoppe
b546e130bc New translations settings.mdx (Greek) 2025-11-12 17:22:42 +01:00
vcoppe
3254afce29 New translations settings.mdx (German) 2025-11-12 17:22:41 +01:00
vcoppe
417a113ca7 New translations settings.mdx (Danish) 2025-11-12 17:22:40 +01:00
vcoppe
aef814b094 New translations settings.mdx (Czech) 2025-11-12 17:22:39 +01:00
vcoppe
47075dadec New translations settings.mdx (Catalan) 2025-11-12 17:22:38 +01:00
vcoppe
c1e9e0c55f New translations settings.mdx (Belarusian) 2025-11-12 17:22:37 +01:00
vcoppe
70b0698504 New translations settings.mdx (Spanish) 2025-11-12 17:22:36 +01:00
vcoppe
3f00534549 New translations settings.mdx (French) 2025-11-12 17:22:34 +01:00
vcoppe
392fbf6af4 New translations settings.mdx (Romanian) 2025-11-12 17:22:33 +01:00
vcoppe
552b18148f New translations file.mdx (Spanish) 2025-11-12 17:22:20 +01:00
vcoppe
08007ad3b2 New translations edit.mdx (Serbian (Latin)) 2025-11-12 17:22:18 +01:00
vcoppe
e8890e6ba3 New translations edit.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:22:17 +01:00
vcoppe
b10e4fc487 New translations edit.mdx (Latvian) 2025-11-12 17:22:15 +01:00
vcoppe
1ce92bcfbc New translations edit.mdx (Thai) 2025-11-12 17:22:15 +01:00
vcoppe
7e9cd2f672 New translations edit.mdx (Indonesian) 2025-11-12 17:22:14 +01:00
vcoppe
bc1c06b01f New translations edit.mdx (Portuguese, Brazilian) 2025-11-12 17:22:13 +01:00
vcoppe
2be6d36fc6 New translations edit.mdx (Vietnamese) 2025-11-12 17:22:11 +01:00
vcoppe
75f7aeb588 New translations edit.mdx (Chinese Simplified) 2025-11-12 17:22:10 +01:00
vcoppe
2944694492 New translations edit.mdx (Ukrainian) 2025-11-12 17:22:09 +01:00
vcoppe
53b29b5bdd New translations edit.mdx (Turkish) 2025-11-12 17:22:08 +01:00
vcoppe
0d4f73b269 New translations edit.mdx (Swedish) 2025-11-12 17:22:07 +01:00
vcoppe
f5f2d2734d New translations edit.mdx (Russian) 2025-11-12 17:22:06 +01:00
vcoppe
4b5507333c New translations edit.mdx (Portuguese) 2025-11-12 17:22:05 +01:00
vcoppe
7d6d4d4cec New translations edit.mdx (Polish) 2025-11-12 17:22:04 +01:00
vcoppe
3c86cb0e69 New translations edit.mdx (Norwegian) 2025-11-12 17:22:03 +01:00
vcoppe
73f5d46e76 New translations edit.mdx (Dutch) 2025-11-12 17:22:01 +01:00
vcoppe
8cd02ac3ed New translations edit.mdx (Lithuanian) 2025-11-12 17:22:00 +01:00
vcoppe
8b208f9dbd New translations edit.mdx (Korean) 2025-11-12 17:21:59 +01:00
vcoppe
11065519aa New translations edit.mdx (Italian) 2025-11-12 17:21:58 +01:00
vcoppe
5124f9a054 New translations edit.mdx (Hungarian) 2025-11-12 17:21:57 +01:00
vcoppe
6c0646acc6 New translations edit.mdx (Hebrew) 2025-11-12 17:21:56 +01:00
vcoppe
60bfc8f856 New translations edit.mdx (Finnish) 2025-11-12 17:21:55 +01:00
vcoppe
c57f0f84af New translations edit.mdx (Basque) 2025-11-12 17:21:54 +01:00
vcoppe
cd1a5bc169 New translations edit.mdx (Greek) 2025-11-12 17:21:53 +01:00
vcoppe
27f0fe30e7 New translations edit.mdx (German) 2025-11-12 17:21:52 +01:00
vcoppe
c24e008865 New translations edit.mdx (Danish) 2025-11-12 17:21:51 +01:00
vcoppe
a852ed88f7 New translations edit.mdx (Czech) 2025-11-12 17:21:50 +01:00
vcoppe
57236e7010 New translations edit.mdx (Catalan) 2025-11-12 17:21:49 +01:00
vcoppe
1f7389d1e1 New translations edit.mdx (Belarusian) 2025-11-12 17:21:48 +01:00
vcoppe
3a213a273d New translations edit.mdx (Spanish) 2025-11-12 17:21:47 +01:00
vcoppe
7602c70d98 New translations edit.mdx (French) 2025-11-12 17:21:46 +01:00
vcoppe
54fda40fcf New translations edit.mdx (Romanian) 2025-11-12 17:21:45 +01:00
vcoppe
ce038fde8c New translations map-controls.mdx (Serbian (Latin)) 2025-11-12 17:21:30 +01:00
vcoppe
dbe2c2be9b New translations map-controls.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:21:29 +01:00
vcoppe
85c66f1b1c New translations map-controls.mdx (Latvian) 2025-11-12 17:21:28 +01:00
vcoppe
4b72468f0c New translations map-controls.mdx (Thai) 2025-11-12 17:21:27 +01:00
vcoppe
ecaedf15d0 New translations map-controls.mdx (Indonesian) 2025-11-12 17:21:26 +01:00
vcoppe
1d9bee9fe7 New translations map-controls.mdx (Portuguese, Brazilian) 2025-11-12 17:21:25 +01:00
vcoppe
22a30cef11 New translations map-controls.mdx (Vietnamese) 2025-11-12 17:21:24 +01:00
vcoppe
def7a36f70 New translations map-controls.mdx (Chinese Simplified) 2025-11-12 17:21:23 +01:00
vcoppe
064adcfc2a New translations map-controls.mdx (Ukrainian) 2025-11-12 17:21:22 +01:00
vcoppe
c84eedbce0 New translations map-controls.mdx (Turkish) 2025-11-12 17:21:21 +01:00
vcoppe
10babaa9e6 New translations map-controls.mdx (Swedish) 2025-11-12 17:21:20 +01:00
vcoppe
2f8f3264df New translations map-controls.mdx (Russian) 2025-11-12 17:21:19 +01:00
vcoppe
5f58762dda New translations map-controls.mdx (Portuguese) 2025-11-12 17:21:18 +01:00
vcoppe
e293000db9 New translations map-controls.mdx (Polish) 2025-11-12 17:21:17 +01:00
vcoppe
cff344cda5 New translations map-controls.mdx (Norwegian) 2025-11-12 17:21:16 +01:00
vcoppe
ba9c622d7a New translations map-controls.mdx (Dutch) 2025-11-12 17:21:15 +01:00
vcoppe
8b44770072 New translations map-controls.mdx (Lithuanian) 2025-11-12 17:21:14 +01:00
vcoppe
371c5c8c32 New translations map-controls.mdx (Korean) 2025-11-12 17:21:13 +01:00
vcoppe
ddac691e8d New translations map-controls.mdx (Italian) 2025-11-12 17:21:12 +01:00
vcoppe
71b658612c New translations map-controls.mdx (Hungarian) 2025-11-12 17:21:11 +01:00
vcoppe
6f3bba910e New translations map-controls.mdx (Hebrew) 2025-11-12 17:21:10 +01:00
vcoppe
4e197885c8 New translations map-controls.mdx (Finnish) 2025-11-12 17:21:09 +01:00
vcoppe
8802375dc0 New translations map-controls.mdx (Basque) 2025-11-12 17:21:08 +01:00
vcoppe
5298879240 New translations map-controls.mdx (Greek) 2025-11-12 17:21:07 +01:00
vcoppe
2f8f40df0d New translations map-controls.mdx (German) 2025-11-12 17:21:06 +01:00
vcoppe
3c3216f384 New translations map-controls.mdx (Danish) 2025-11-12 17:21:04 +01:00
vcoppe
346c9308f2 New translations map-controls.mdx (Czech) 2025-11-12 17:21:03 +01:00
vcoppe
7468c52865 New translations map-controls.mdx (Catalan) 2025-11-12 17:21:02 +01:00
vcoppe
49c7fb5d67 New translations map-controls.mdx (Belarusian) 2025-11-12 17:21:01 +01:00
vcoppe
ad3d11daf4 New translations map-controls.mdx (Spanish) 2025-11-12 17:21:00 +01:00
vcoppe
4e5d485157 New translations map-controls.mdx (French) 2025-11-12 17:20:59 +01:00
vcoppe
e9097670f8 New translations map-controls.mdx (Romanian) 2025-11-12 17:20:58 +01:00
vcoppe
53ba24a087 New translations gpx.mdx (Serbian (Latin)) 2025-11-12 17:20:03 +01:00
vcoppe
271d046248 New translations gpx.mdx (Chinese Traditional, Hong Kong) 2025-11-12 17:20:02 +01:00
vcoppe
beffe9cdcc New translations gpx.mdx (Latvian) 2025-11-12 17:20:01 +01:00
vcoppe
e7a375d912 New translations gpx.mdx (Thai) 2025-11-12 17:20:00 +01:00
vcoppe
663dd96019 New translations gpx.mdx (Indonesian) 2025-11-12 17:19:59 +01:00
vcoppe
7eeb33a973 New translations gpx.mdx (Portuguese, Brazilian) 2025-11-12 17:19:58 +01:00
vcoppe
e95195151d New translations gpx.mdx (Vietnamese) 2025-11-12 17:19:57 +01:00
vcoppe
d81c2e190a New translations gpx.mdx (Chinese Simplified) 2025-11-12 17:19:56 +01:00
vcoppe
6628a52b66 New translations gpx.mdx (Ukrainian) 2025-11-12 17:19:55 +01:00
vcoppe
e5ff306200 New translations gpx.mdx (Turkish) 2025-11-12 17:19:54 +01:00
vcoppe
43f4023370 New translations gpx.mdx (Swedish) 2025-11-12 17:19:53 +01:00
vcoppe
5f6956dd47 New translations gpx.mdx (Russian) 2025-11-12 17:19:52 +01:00
vcoppe
91247b1bb5 New translations gpx.mdx (Portuguese) 2025-11-12 17:19:51 +01:00
vcoppe
f4056986fa New translations gpx.mdx (Polish) 2025-11-12 17:19:50 +01:00
vcoppe
30a752928f New translations gpx.mdx (Norwegian) 2025-11-12 17:19:49 +01:00
vcoppe
bff66cf0dd New translations gpx.mdx (Dutch) 2025-11-12 17:19:48 +01:00
vcoppe
1abf0bc157 New translations gpx.mdx (Lithuanian) 2025-11-12 17:19:47 +01:00
vcoppe
6e845e765d New translations gpx.mdx (Korean) 2025-11-12 17:19:46 +01:00
vcoppe
ef18c1a673 New translations gpx.mdx (Italian) 2025-11-12 17:19:45 +01:00
vcoppe
a32460b8d0 New translations gpx.mdx (Hungarian) 2025-11-12 17:19:44 +01:00
vcoppe
aedfaa05ca New translations gpx.mdx (Hebrew) 2025-11-12 17:19:43 +01:00
vcoppe
78a30a7f51 New translations gpx.mdx (Finnish) 2025-11-12 17:19:42 +01:00
vcoppe
a59215e082 New translations gpx.mdx (Basque) 2025-11-12 17:19:41 +01:00
vcoppe
a41f084fad New translations gpx.mdx (Greek) 2025-11-12 17:19:40 +01:00
vcoppe
28edec8158 New translations gpx.mdx (German) 2025-11-12 17:19:39 +01:00
vcoppe
9870a631c4 New translations gpx.mdx (Danish) 2025-11-12 17:19:38 +01:00
vcoppe
5027f188c9 New translations gpx.mdx (Czech) 2025-11-12 17:19:37 +01:00
vcoppe
df157a5940 New translations gpx.mdx (Catalan) 2025-11-12 17:19:36 +01:00
vcoppe
1a50843ee0 New translations gpx.mdx (Belarusian) 2025-11-12 17:19:35 +01:00
vcoppe
fb1b365fcf New translations gpx.mdx (Spanish) 2025-11-12 17:19:34 +01:00
vcoppe
393568499d New translations gpx.mdx (French) 2025-11-12 17:19:33 +01:00
vcoppe
b24bf11192 New translations gpx.mdx (Romanian) 2025-11-12 17:19:32 +01:00
119 changed files with 1862 additions and 2781 deletions

View File

@@ -1,63 +1,63 @@
name: Deploy to GitHub Pages
on:
push:
branches: 'main'
push:
branches: 'main'
jobs:
build_site:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
build_site:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
cache-dependency-path: |
gpx/package-lock.json
website/package-lock.json
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: |
gpx/package-lock.json
website/package-lock.json
- name: Install dependencies for gpx
run: npm install --prefix gpx
- name: Install dependencies for gpx
run: npm install --prefix gpx
- name: Build gpx
run: npm run build --prefix gpx
- name: Build gpx
run: npm run build --prefix gpx
- name: Install dependencies for website
run: npm install --prefix website
- name: Install dependencies for website
run: npm install --prefix website
- name: Create env file
run: |
touch website/.env
echo PUBLIC_MAPBOX_TOKEN=${{ secrets.PUBLIC_MAPBOX_TOKEN }} >> website/.env
cat website/.env
- name: Create env file
run: |
touch website/.env
echo PUBLIC_MAPBOX_TOKEN=${{ secrets.PUBLIC_MAPBOX_TOKEN }} >> website/.env
cat website/.env
- name: Build website
env:
BASE_PATH: ''
run: |
npm run build --prefix website
- name: Build website
env:
BASE_PATH: ''
run: |
npm run build --prefix website
- name: Upload Artifacts
uses: actions/upload-pages-artifact@v3
with:
path: 'website/build/'
- name: Upload Artifacts
uses: actions/upload-pages-artifact@v4
with:
path: 'website/build/'
deploy:
needs: build_site
runs-on: ubuntu-latest
deploy:
needs: build_site
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4

View File

@@ -1,3 +1,6 @@
website/src/lib/components/ui
website/src/lib/docs/**/*.mdx
**/*.webmanifest
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
src/lib/components/ui
*.mdx

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2026 gpx.studio
Copyright (c) 2024 gpx.studio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -25,7 +25,7 @@
"scripts": {
"build": "tsc",
"postinstall": "npm run build",
"lint": "prettier --check . --config ../.prettierrc && eslint .",
"format": "prettier --write . --config ../.prettierrc"
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
}
}

View File

@@ -1,5 +1,4 @@
import { ramerDouglasPeucker } from './simplify';
import { GPXStatistics, GPXStatisticsGroup, TrackPointLocalStatistics } from './statistics';
import {
Coordinates,
GPXFileAttributes,
@@ -18,9 +17,6 @@ import {
import { immerable, isDraft, original, freeze } from 'immer';
function cloneJSON<T>(obj: T): T {
if (obj === undefined) {
return undefined;
}
if (obj === null || typeof obj !== 'object') {
return null;
}
@@ -37,6 +33,7 @@ export abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
abstract getNumberOfTrackPoints(): number;
abstract getStartTimestamp(): Date | undefined;
abstract getEndTimestamp(): Date | undefined;
abstract getStatistics(): GPXStatistics;
abstract getSegments(): TrackSegment[];
abstract getTrackPoints(): TrackPoint[];
@@ -76,6 +73,14 @@ abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement
return this.children[this.children.length - 1].getEndTimestamp();
}
getStatistics(): GPXStatistics {
let statistics = new GPXStatistics();
for (let child of this.children) {
statistics.mergeWith(child.getStatistics());
}
return statistics;
}
getSegments(): TrackSegment[] {
return this.children.flatMap((child) => child.getSegments());
}
@@ -140,9 +145,7 @@ export class GPXFile extends GPXTreeNode<Track> {
},
},
};
this.wpt = gpx.wpt
? gpx.wpt.map((waypoint, index) => new Waypoint(waypoint, index))
: [];
this.wpt = gpx.wpt ? gpx.wpt.map((waypoint) => new Waypoint(waypoint)) : [];
this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : [];
if (gpx.rte && gpx.rte.length > 0) {
this.trk = this.trk.concat(gpx.rte.map((route) => convertRouteToTrack(route)));
@@ -180,6 +183,9 @@ export class GPXFile extends GPXTreeNode<Track> {
segment._data['segmentIndex'] = segmentIndex;
});
});
this.wpt.forEach((waypoint, waypointIndex) => {
waypoint._data['index'] = waypointIndex;
});
}
get children(): Array<Track> {
@@ -200,16 +206,8 @@ export class GPXFile extends GPXTreeNode<Track> {
});
}
getStatistics(): GPXStatisticsGroup {
let statistics = new GPXStatisticsGroup();
this.forEachSegment((segment) => {
statistics.add(segment.getStatistics());
});
return statistics;
}
getStyle(defaultColor?: string): MergedLineStyles {
const style = this.trk
return this.trk
.map((track) => track.getStyle())
.reduce(
(acc, style) => {
@@ -219,6 +217,8 @@ export class GPXFile extends GPXTreeNode<Track> {
!acc.color.includes(style['gpx_style:color'])
) {
acc.color.push(style['gpx_style:color']);
} else if (defaultColor && !acc.color.includes(defaultColor)) {
acc.color.push(defaultColor);
}
if (
style &&
@@ -242,10 +242,6 @@ export class GPXFile extends GPXTreeNode<Track> {
width: [],
}
);
if (style.color.length === 0 && defaultColor) {
style.color.push(defaultColor);
}
return style;
}
clone(): GPXFile {
@@ -808,7 +804,7 @@ export class TrackSegment extends GPXTreeLeaf {
constructor(segment?: (TrackSegmentType & { _data?: any }) | TrackSegment) {
super();
if (segment) {
this.trkpt = segment.trkpt.map((point, index) => new TrackPoint(point, index));
this.trkpt = segment.trkpt.map((point) => new TrackPoint(point));
if (segment.hasOwnProperty('_data')) {
this._data = segment._data;
}
@@ -820,12 +816,15 @@ export class TrackSegment extends GPXTreeLeaf {
_computeStatistics(): GPXStatistics {
let statistics = new GPXStatistics();
statistics.global.length = this.trkpt.length;
statistics.local.points = this.trkpt.slice(0);
statistics.local.data = this.trkpt.map(() => new TrackPointLocalStatistics());
statistics.local.points = this.trkpt.map((point) => point);
statistics.local.elevation.smoothed = this._computeSmoothedElevation();
statistics.local.slope.at = this._computeSlope();
const points = this.trkpt;
for (let i = 0; i < points.length; i++) {
points[i]._data['index'] = i;
// distance
let dist = 0;
if (i > 0) {
@@ -834,18 +833,34 @@ export class TrackSegment extends GPXTreeLeaf {
statistics.global.distance.total += dist;
}
statistics.local.data[i].distance.total = statistics.global.distance.total;
statistics.local.distance.total.push(statistics.global.distance.total);
// elevation
if (i > 0) {
const ele =
statistics.local.elevation.smoothed[i] -
statistics.local.elevation.smoothed[i - 1];
if (ele > 0) {
statistics.global.elevation.gain += ele;
} else if (ele < 0) {
statistics.global.elevation.loss -= ele;
}
}
statistics.local.elevation.gain.push(statistics.global.elevation.gain);
statistics.local.elevation.loss.push(statistics.global.elevation.loss);
// time
if (points[i].time === undefined) {
statistics.local.data[i].time.total = 0;
statistics.local.time.total.push(0);
} else {
if (statistics.global.time.start === undefined) {
statistics.global.time.start = points[i].time;
}
statistics.global.time.end = points[i].time;
statistics.local.data[i].time.total =
(points[i].time.getTime() - statistics.global.time.start.getTime()) / 1000;
statistics.local.time.total.push(
(points[i].time.getTime() - statistics.global.time.start.getTime()) / 1000
);
}
// speed
@@ -860,8 +875,8 @@ export class TrackSegment extends GPXTreeLeaf {
}
}
statistics.local.data[i].distance.moving = statistics.global.distance.moving;
statistics.local.data[i].time.moving = statistics.global.time.moving;
statistics.local.distance.moving.push(statistics.global.distance.moving);
statistics.local.time.moving.push(statistics.global.time.moving);
// bounds
statistics.global.bounds.southWest.lat = Math.min(
@@ -945,7 +960,8 @@ export class TrackSegment extends GPXTreeLeaf {
}
}
this._elevationComputation(statistics);
[statistics.local.slope.segment, statistics.local.slope.length] =
this._computeSlopeSegments(statistics);
statistics.global.time.total =
statistics.global.time.start && statistics.global.time.end
@@ -961,115 +977,73 @@ export class TrackSegment extends GPXTreeLeaf {
? statistics.global.distance.moving / (statistics.global.time.moving / 3600)
: 0;
timeWindowSmoothing(
statistics.local.speed = distanceWindowSmoothingWithDistanceAccumulator(
points,
10000,
(start, end) =>
200,
(accumulated, start, end) =>
points[start].time && points[end].time
? (3600 *
(statistics.local.data[end].distance.total -
statistics.local.data[start].distance.total)) /
Math.max(
(points[end].time.getTime() - points[start].time.getTime()) / 1000,
1
)
: undefined,
(value, index) => {
statistics.local.data[index].speed = value;
}
? (3600 * accumulated) /
(points[end].time.getTime() - points[start].time.getTime())
: undefined
);
return statistics;
}
_elevationComputation(statistics: GPXStatistics) {
_computeSmoothedElevation(): number[] {
const points = this.trkpt;
let smoothed = distanceWindowSmoothing(
points,
100,
(index) => points[index].ele ?? 0,
(accumulated, start, end) => accumulated / (end - start + 1)
);
if (points.length > 0) {
smoothed[0] = points[0].ele ?? 0;
smoothed[points.length - 1] = points[points.length - 1].ele ?? 0;
}
return smoothed;
}
_computeSlope(): number[] {
const points = this.trkpt;
return distanceWindowSmoothingWithDistanceAccumulator(
points,
50,
(accumulated, start, end) =>
(100 * ((points[end].ele ?? 0) - (points[start].ele ?? 0))) /
(accumulated > 0 ? accumulated : 1)
);
}
_computeSlopeSegments(statistics: GPXStatistics): [number[], number[]] {
let simplified = ramerDouglasPeucker(
this.trkpt,
20,
getElevationDistanceFunction(statistics)
);
for (let i = 0; i < simplified.length - 1; i++) {
let start = simplified[i].point._data.index;
let end = simplified[i + 1].point._data.index;
let cumulEle = 0;
let currentStart = start;
let currentEnd = start;
let prevSmoothedEle = 0;
distanceWindowSmoothing(
start,
end + 1,
statistics,
0.1,
(s, e) => {
for (let i = currentStart; i < s; i++) {
cumulEle -= this.trkpt[i].ele ?? 0;
}
for (let i = currentEnd; i <= e; i++) {
cumulEle += this.trkpt[i].ele ?? 0;
}
currentStart = s;
currentEnd = e + 1;
return cumulEle / (e - s + 1);
},
(smoothedEle, j) => {
if (j === start) {
smoothedEle = this.trkpt[start].ele ?? 0;
prevSmoothedEle = smoothedEle;
} else if (j === end) {
smoothedEle = this.trkpt[end].ele ?? 0;
}
const ele = smoothedEle - prevSmoothedEle;
if (ele > 0) {
statistics.global.elevation.gain += ele;
} else if (ele < 0) {
statistics.global.elevation.loss -= ele;
}
prevSmoothedEle = smoothedEle;
if (j < end) {
statistics.local.data[j].elevation.gain = statistics.global.elevation.gain;
statistics.local.data[j].elevation.loss = statistics.global.elevation.loss;
}
}
);
}
if (statistics.global.length > 0) {
statistics.local.data[statistics.global.length - 1].elevation.gain =
statistics.global.elevation.gain;
statistics.local.data[statistics.global.length - 1].elevation.loss =
statistics.global.elevation.loss;
}
let slope = [];
let length = [];
for (let i = 0; i < simplified.length - 1; i++) {
let start = simplified[i].point._data.index;
let end = simplified[i + 1].point._data.index;
let dist =
statistics.local.data[end].distance.total -
statistics.local.data[start].distance.total;
statistics.local.distance.total[end] - statistics.local.distance.total[start];
let ele = (simplified[i + 1].point.ele ?? 0) - (simplified[i].point.ele ?? 0);
for (let j = start; j < end + (i + 1 === simplified.length - 1 ? 1 : 0); j++) {
statistics.local.data[j].slope.segment = (0.1 * ele) / dist;
statistics.local.data[j].slope.length = dist;
slope.push((0.1 * ele) / dist);
length.push(dist);
}
}
distanceWindowSmoothing(
0,
this.trkpt.length,
statistics,
0.05,
(start, end) => {
const ele = this.trkpt[end].ele - this.trkpt[start].ele || 0;
const dist =
statistics.local.data[end].distance.total -
statistics.local.data[start].distance.total;
return dist > 0 ? (0.1 * ele) / dist : 0;
},
(value, index) => {
statistics.local.data[index].slope.at = value;
}
);
return [slope, length];
}
getNumberOfTrackPoints(): number {
@@ -1316,8 +1290,8 @@ export class TrackSegment extends GPXTreeLeaf {
lastPoint: TrackPoint | undefined
) {
let og = getOriginal(this); // Read as much as possible from the original object because it is faster
let statistics = og._computeStatistics();
let trkpt = withArtificialTimestamps(og.trkpt, totalTime, lastPoint, startTime, statistics);
let slope = og._computeSlope();
let trkpt = withArtificialTimestamps(og.trkpt, totalTime, lastPoint, startTime, slope);
this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
}
@@ -1326,7 +1300,6 @@ export class TrackSegment extends GPXTreeLeaf {
}
}
const emptyExtensions: Record<string, string> = {};
export class TrackPoint {
[immerable] = true;
@@ -1337,7 +1310,7 @@ export class TrackPoint {
_data: { [key: string]: any } = {};
constructor(point: (TrackPointType & { _data?: any }) | TrackPoint, index?: number) {
constructor(point: (TrackPointType & { _data?: any }) | TrackPoint) {
this.attributes = point.attributes;
this.ele = point.ele;
this.time = point.time;
@@ -1345,9 +1318,6 @@ export class TrackPoint {
if (point.hasOwnProperty('_data')) {
this._data = point._data;
}
if (index !== undefined) {
this._data.index = index;
}
}
getCoordinates(): Coordinates {
@@ -1421,7 +1391,7 @@ export class TrackPoint {
this.extensions['gpxtpx:TrackPointExtension'] &&
this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']
? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']
: emptyExtensions;
: {};
}
toTrackPointType(exclude: string[] = []): TrackPointType {
@@ -1491,18 +1461,11 @@ export class TrackPoint {
clone(): TrackPoint {
return new TrackPoint({
attributes: {
lat: this.attributes.lat,
lon: this.attributes.lon,
},
attributes: cloneJSON(this.attributes),
ele: this.ele,
time: this.time ? new Date(this.time.getTime()) : undefined,
extensions: this.extensions ? cloneJSON(this.extensions) : undefined,
_data: {
index: this._data?.index,
anchor: this._data?.anchor,
zoom: this._data?.zoom,
},
extensions: cloneJSON(this.extensions),
_data: cloneJSON(this._data),
});
}
}
@@ -1521,28 +1484,19 @@ export class Waypoint {
type?: string;
_data: { [key: string]: any } = {};
constructor(waypoint: (WaypointType & { _data?: any }) | Waypoint, index?: number) {
constructor(waypoint: (WaypointType & { _data?: any }) | Waypoint) {
this.attributes = waypoint.attributes;
this.ele = waypoint.ele;
this.time = waypoint.time;
this.name = waypoint.name === '' ? undefined : waypoint.name;
this.cmt = waypoint.cmt === '' ? undefined : waypoint.cmt;
this.desc = waypoint.desc === '' ? undefined : waypoint.desc;
this.link =
!waypoint.link ||
!waypoint.link.attributes ||
!waypoint.link.attributes.href ||
waypoint.link.attributes.href === ''
? undefined
: waypoint.link;
this.sym = waypoint.sym === '' ? undefined : waypoint.sym;
this.type = waypoint.type === '' ? undefined : waypoint.type;
this.name = waypoint.name;
this.cmt = waypoint.cmt;
this.desc = waypoint.desc;
this.link = waypoint.link;
this.sym = waypoint.sym;
this.type = waypoint.type;
if (waypoint.hasOwnProperty('_data')) {
this._data = waypoint._data;
}
if (index !== undefined) {
this._data.index = index;
}
}
getCoordinates(): Coordinates {
@@ -1590,10 +1544,7 @@ export class Waypoint {
clone(): Waypoint {
return new Waypoint({
attributes: {
lat: this.attributes.lat,
lon: this.attributes.lon,
},
attributes: cloneJSON(this.attributes),
ele: this.ele,
time: this.time ? new Date(this.time.getTime()) : undefined,
name: this.name,
@@ -1642,6 +1593,310 @@ export class Waypoint {
}
}
export class GPXStatistics {
global: {
distance: {
moving: number;
total: number;
};
time: {
start: Date | undefined;
end: Date | undefined;
moving: number;
total: number;
};
speed: {
moving: number;
total: number;
};
elevation: {
gain: number;
loss: number;
};
bounds: {
southWest: Coordinates;
northEast: Coordinates;
};
atemp: {
avg: number;
count: number;
};
hr: {
avg: number;
count: number;
};
cad: {
avg: number;
count: number;
};
power: {
avg: number;
count: number;
};
extensions: Record<string, Record<string, number>>;
};
local: {
points: TrackPoint[];
distance: {
moving: number[];
total: number[];
};
time: {
moving: number[];
total: number[];
};
speed: number[];
elevation: {
smoothed: number[];
gain: number[];
loss: number[];
};
slope: {
at: number[];
segment: number[];
length: number[];
};
};
constructor() {
this.global = {
distance: {
moving: 0,
total: 0,
},
time: {
start: undefined,
end: undefined,
moving: 0,
total: 0,
},
speed: {
moving: 0,
total: 0,
},
elevation: {
gain: 0,
loss: 0,
},
bounds: {
southWest: {
lat: 90,
lon: 180,
},
northEast: {
lat: -90,
lon: -180,
},
},
atemp: {
avg: 0,
count: 0,
},
hr: {
avg: 0,
count: 0,
},
cad: {
avg: 0,
count: 0,
},
power: {
avg: 0,
count: 0,
},
extensions: {},
};
this.local = {
points: [],
distance: {
moving: [],
total: [],
},
time: {
moving: [],
total: [],
},
speed: [],
elevation: {
smoothed: [],
gain: [],
loss: [],
},
slope: {
at: [],
segment: [],
length: [],
},
};
}
mergeWith(other: GPXStatistics): void {
this.local.points = this.local.points.concat(other.local.points);
this.local.distance.total = this.local.distance.total.concat(
other.local.distance.total.map((distance) => distance + this.global.distance.total)
);
this.local.distance.moving = this.local.distance.moving.concat(
other.local.distance.moving.map((distance) => distance + this.global.distance.moving)
);
this.local.time.total = this.local.time.total.concat(
other.local.time.total.map((time) => time + this.global.time.total)
);
this.local.time.moving = this.local.time.moving.concat(
other.local.time.moving.map((time) => time + this.global.time.moving)
);
this.local.elevation.gain = this.local.elevation.gain.concat(
other.local.elevation.gain.map((gain) => gain + this.global.elevation.gain)
);
this.local.elevation.loss = this.local.elevation.loss.concat(
other.local.elevation.loss.map((loss) => loss + this.global.elevation.loss)
);
this.local.speed = this.local.speed.concat(other.local.speed);
this.local.elevation.smoothed = this.local.elevation.smoothed.concat(
other.local.elevation.smoothed
);
this.local.slope.at = this.local.slope.at.concat(other.local.slope.at);
this.local.slope.segment = this.local.slope.segment.concat(other.local.slope.segment);
this.local.slope.length = this.local.slope.length.concat(other.local.slope.length);
this.global.distance.total += other.global.distance.total;
this.global.distance.moving += other.global.distance.moving;
this.global.time.start =
this.global.time.start !== undefined && other.global.time.start !== undefined
? new Date(
Math.min(this.global.time.start.getTime(), other.global.time.start.getTime())
)
: (this.global.time.start ?? other.global.time.start);
this.global.time.end =
this.global.time.end !== undefined && other.global.time.end !== undefined
? new Date(
Math.max(this.global.time.end.getTime(), other.global.time.end.getTime())
)
: (this.global.time.end ?? other.global.time.end);
this.global.time.total += other.global.time.total;
this.global.time.moving += other.global.time.moving;
this.global.speed.moving =
this.global.time.moving > 0
? this.global.distance.moving / (this.global.time.moving / 3600)
: 0;
this.global.speed.total =
this.global.time.total > 0
? this.global.distance.total / (this.global.time.total / 3600)
: 0;
this.global.elevation.gain += other.global.elevation.gain;
this.global.elevation.loss += other.global.elevation.loss;
this.global.bounds.southWest.lat = Math.min(
this.global.bounds.southWest.lat,
other.global.bounds.southWest.lat
);
this.global.bounds.southWest.lon = Math.min(
this.global.bounds.southWest.lon,
other.global.bounds.southWest.lon
);
this.global.bounds.northEast.lat = Math.max(
this.global.bounds.northEast.lat,
other.global.bounds.northEast.lat
);
this.global.bounds.northEast.lon = Math.max(
this.global.bounds.northEast.lon,
other.global.bounds.northEast.lon
);
this.global.atemp.avg =
(this.global.atemp.count * this.global.atemp.avg +
other.global.atemp.count * other.global.atemp.avg) /
Math.max(1, this.global.atemp.count + other.global.atemp.count);
this.global.atemp.count += other.global.atemp.count;
this.global.hr.avg =
(this.global.hr.count * this.global.hr.avg +
other.global.hr.count * other.global.hr.avg) /
Math.max(1, this.global.hr.count + other.global.hr.count);
this.global.hr.count += other.global.hr.count;
this.global.cad.avg =
(this.global.cad.count * this.global.cad.avg +
other.global.cad.count * other.global.cad.avg) /
Math.max(1, this.global.cad.count + other.global.cad.count);
this.global.cad.count += other.global.cad.count;
this.global.power.avg =
(this.global.power.count * this.global.power.avg +
other.global.power.count * other.global.power.avg) /
Math.max(1, this.global.power.count + other.global.power.count);
this.global.power.count += other.global.power.count;
Object.keys(other.global.extensions).forEach((extension) => {
if (this.global.extensions[extension] === undefined) {
this.global.extensions[extension] = {};
}
Object.keys(other.global.extensions[extension]).forEach((value) => {
if (this.global.extensions[extension][value] === undefined) {
this.global.extensions[extension][value] = 0;
}
this.global.extensions[extension][value] +=
other.global.extensions[extension][value];
});
});
}
slice(start: number, end: number): GPXStatistics {
if (start < 0) {
start = 0;
} else if (start >= this.local.points.length) {
return new GPXStatistics();
}
if (end < start) {
return new GPXStatistics();
} else if (end >= this.local.points.length) {
end = this.local.points.length - 1;
}
let statistics = new GPXStatistics();
statistics.local.points = this.local.points.slice(start, end + 1);
statistics.global.distance.total =
this.local.distance.total[end] - this.local.distance.total[start];
statistics.global.distance.moving =
this.local.distance.moving[end] - this.local.distance.moving[start];
statistics.global.time.start = this.local.points[start].time;
statistics.global.time.end = this.local.points[end].time;
statistics.global.time.total = this.local.time.total[end] - this.local.time.total[start];
statistics.global.time.moving = this.local.time.moving[end] - this.local.time.moving[start];
statistics.global.speed.moving =
statistics.global.time.moving > 0
? statistics.global.distance.moving / (statistics.global.time.moving / 3600)
: 0;
statistics.global.speed.total =
statistics.global.time.total > 0
? statistics.global.distance.total / (statistics.global.time.total / 3600)
: 0;
statistics.global.elevation.gain =
this.local.elevation.gain[end] - this.local.elevation.gain[start];
statistics.global.elevation.loss =
this.local.elevation.loss[end] - this.local.elevation.loss[start];
statistics.global.bounds.southWest.lat = this.global.bounds.southWest.lat;
statistics.global.bounds.southWest.lon = this.global.bounds.southWest.lon;
statistics.global.bounds.northEast.lat = this.global.bounds.northEast.lat;
statistics.global.bounds.northEast.lon = this.global.bounds.northEast.lon;
statistics.global.atemp = this.global.atemp;
statistics.global.hr = this.global.hr;
statistics.global.cad = this.global.cad;
statistics.global.power = this.global.power;
return statistics;
}
}
const earthRadius = 6371008.8;
export function distance(
coord1: TrackPoint | Coordinates,
@@ -1656,15 +1911,11 @@ export function distance(
const rad = Math.PI / 180;
const lat1 = coord1.lat * rad;
const lat2 = coord2.lat * rad;
const dLat = lat2 - lat1;
const dLon = (coord2.lon - coord1.lon) * rad;
// Haversine formula - better numerical stability for small distances
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.asin(Math.sqrt(Math.min(a, 1)));
return earthRadius * c;
Math.sin(lat1) * Math.sin(lat2) +
Math.cos(lat1) * Math.cos(lat2) * Math.cos((coord2.lon - coord1.lon) * rad);
const maxMeters = earthRadius * Math.acos(Math.min(a, 1));
return maxMeters;
}
export function getElevationDistanceFunction(statistics: GPXStatistics) {
@@ -1675,9 +1926,9 @@ export function getElevationDistanceFunction(statistics: GPXStatistics) {
if (point1.ele === undefined || point2.ele === undefined || point3.ele === undefined) {
return 0;
}
let x1 = statistics.local.data[point1._data.index].distance.total * 1000;
let x2 = statistics.local.data[point2._data.index].distance.total * 1000;
let x3 = statistics.local.data[point3._data.index].distance.total * 1000;
let x1 = statistics.local.distance.total[point1._data.index] * 1000;
let x2 = statistics.local.distance.total[point2._data.index] * 1000;
let x3 = statistics.local.distance.total[point3._data.index] * 1000;
let y1 = point1.ele;
let y2 = point2.ele;
let y3 = point3.ele;
@@ -1691,61 +1942,57 @@ export function getElevationDistanceFunction(statistics: GPXStatistics) {
};
}
function windowSmoothing(
left: number,
right: number,
distance: (index1: number, index2: number) => number,
window: number,
compute: (start: number, end: number) => number,
callback: (value: number, index: number) => void
): void {
let start = left;
for (var i = left; i < right; i++) {
while (start + 1 < i && distance(start, i) > window) {
function distanceWindowSmoothing(
points: TrackPoint[],
distanceWindow: number,
accumulate: (index: number) => number,
compute: (accumulated: number, start: number, end: number) => number,
remove?: (index: number) => number
): number[] {
let result = [];
let start = 0,
end = 0,
accumulated = 0;
for (var i = 0; i < points.length; i++) {
while (
start + 1 < i &&
distance(points[start].getCoordinates(), points[i].getCoordinates()) > distanceWindow
) {
if (remove) {
accumulated -= remove(start);
} else {
accumulated -= accumulate(start);
}
start++;
}
let end = Math.min(i + 2, right);
while (end < right && distance(i, end) <= window) {
while (
end < points.length &&
distance(points[i].getCoordinates(), points[end].getCoordinates()) <= distanceWindow
) {
accumulated += accumulate(end);
end++;
}
callback(compute(start, end - 1), i);
result[i] = compute(accumulated, start, end - 1);
}
return result;
}
function distanceWindowSmoothing(
left: number,
right: number,
statistics: GPXStatistics,
window: number,
compute: (start: number, end: number) => number,
callback: (value: number, index: number) => void
): void {
windowSmoothing(
left,
right,
(index1, index2) =>
statistics.local.data[index2].distance.total -
statistics.local.data[index1].distance.total,
window,
compute,
callback
);
}
function timeWindowSmoothing(
function distanceWindowSmoothingWithDistanceAccumulator(
points: TrackPoint[],
window: number,
compute: (start: number, end: number) => number,
callback: (value: number, index: number) => void
): void {
windowSmoothing(
0,
points.length,
(index1, index2) =>
points[index2].time?.getTime() - points[index1].time?.getTime() || 2 * window,
window,
distanceWindow: number,
compute: (accumulated: number, start: number, end: number) => number
): number[] {
return distanceWindowSmoothing(
points,
distanceWindow,
(index) =>
index > 0
? distance(points[index - 1].getCoordinates(), points[index].getCoordinates())
: 0,
compute,
callback
(index) => distance(points[index].getCoordinates(), points[index + 1].getCoordinates())
);
}
@@ -1797,14 +2044,14 @@ function withArtificialTimestamps(
totalTime: number,
lastPoint: TrackPoint | undefined,
startTime: Date,
statistics: GPXStatistics
slope: number[]
): TrackPoint[] {
let weight = [];
let totalWeight = 0;
for (let i = 0; i < points.length - 1; i++) {
let dist = distance(points[i].getCoordinates(), points[i + 1].getCoordinates());
let w = dist * (0.5 + 1 / (1 + Math.exp(-0.2 * statistics.local.data[i].slope.at)));
let w = dist * (0.5 + 1 / (1 + Math.exp(-0.2 * slope[i])));
weight.push(w);
totalWeight += w;
}

View File

@@ -1,5 +1,4 @@
export * from './gpx';
export * from './statistics';
export { Coordinates, LineStyleExtension, WaypointType } from './types';
export { parseGPX, buildGPX } from './io';
export * from './simplify';

View File

@@ -3,6 +3,8 @@ import { Coordinates } from './types';
export type SimplifiedTrackPoint = { point: TrackPoint; distance?: number };
const earthRadius = 6371008.8;
export function ramerDouglasPeucker(
points: TrackPoint[],
epsilon: number = 50,
@@ -59,56 +61,76 @@ function ramerDouglasPeuckerRecursive(
}
export function crossarcDistance(
point1: TrackPoint | Coordinates,
point2: TrackPoint | Coordinates,
point1: TrackPoint,
point2: TrackPoint,
point3: TrackPoint | Coordinates
): number {
return crossarc(
point1 instanceof TrackPoint ? point1.getCoordinates() : point1,
point2 instanceof TrackPoint ? point2.getCoordinates() : point2,
point1.getCoordinates(),
point2.getCoordinates(),
point3 instanceof TrackPoint ? point3.getCoordinates() : point3
);
}
const metersPerLatitudeDegree = 111320;
function getMetersPerLongitudeDegree(latitude: number): number {
return Math.cos((latitude * Math.PI) / 180) * metersPerLatitudeDegree;
}
function crossarc(coord1: Coordinates, coord2: Coordinates, coord3: Coordinates): number {
// Calculates the perpendicular distance in meters
// between a line segment (defined by p1 and p2) and a third point, p3.
// Uses simple planar geometry (ignores earth curvature).
// Calculates the shortest distance in meters
// between an arc (defined by p1 and p2) and a third point, p3.
// Input lat1,lon1,lat2,lon2,lat3,lon3 in degrees.
// Convert to meters using approximate scaling
const metersPerLongitudeDegree = getMetersPerLongitudeDegree(coord1.lat);
const rad = Math.PI / 180;
const lat1 = coord1.lat * rad;
const lat2 = coord2.lat * rad;
const lat3 = coord3.lat * rad;
const x1 = coord1.lon * metersPerLongitudeDegree;
const y1 = coord1.lat * metersPerLatitudeDegree;
const x2 = coord2.lon * metersPerLongitudeDegree;
const y2 = coord2.lat * metersPerLatitudeDegree;
const x3 = coord3.lon * metersPerLongitudeDegree;
const y3 = coord3.lat * metersPerLatitudeDegree;
const lon1 = coord1.lon * rad;
const lon2 = coord2.lon * rad;
const lon3 = coord3.lon * rad;
const dx = x2 - x1;
const dy = y2 - y1;
const segmentLengthSquared = dx * dx + dy * dy;
// Prerequisites for the formulas
const bear12 = bearing(lat1, lon1, lat2, lon2);
const bear13 = bearing(lat1, lon1, lat3, lon3);
let dis13 = distance(lat1, lon1, lat3, lon3);
if (segmentLengthSquared === 0) {
// p1 and p2 are the same point
return Math.sqrt((x3 - x1) * (x3 - x1) + (y3 - y1) * (y3 - y1));
let diff = Math.abs(bear13 - bear12);
if (diff > Math.PI) {
diff = 2 * Math.PI - diff;
}
// Project p3 onto the line defined by p1-p2
const t = Math.max(0, Math.min(1, ((x3 - x1) * dx + (y3 - y1) * dy) / segmentLengthSquared));
// Is relative bearing obtuse?
if (diff > Math.PI / 2) {
return dis13;
}
// Find the closest point on the segment
const projX = x1 + t * dx;
const projY = y1 + t * dy;
// Find the cross-track distance.
let dxt = Math.asin(Math.sin(dis13 / earthRadius) * Math.sin(bear13 - bear12)) * earthRadius;
// Return distance from p3 to the projected point
return Math.sqrt((x3 - projX) * (x3 - projX) + (y3 - projY) * (y3 - projY));
// Is p4 beyond the arc?
let dis12 = distance(lat1, lon1, lat2, lon2);
let dis14 =
Math.acos(Math.cos(dis13 / earthRadius) / Math.cos(dxt / earthRadius)) * earthRadius;
if (dis14 > dis12) {
return distance(lat2, lon2, lat3, lon3);
} else {
return Math.abs(dxt);
}
}
function distance(latA: number, lonA: number, latB: number, lonB: number): number {
// Finds the distance between two lat / lon points.
return (
Math.acos(
Math.sin(latA) * Math.sin(latB) +
Math.cos(latA) * Math.cos(latB) * Math.cos(lonB - lonA)
) * earthRadius
);
}
function bearing(latA: number, lonA: number, latB: number, lonB: number): number {
// Finds the bearing from one lat / lon point to another.
return Math.atan2(
Math.sin(lonB - lonA) * Math.cos(latB),
Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(lonB - lonA)
);
}
export function projectedPoint(
@@ -124,39 +146,56 @@ export function projectedPoint(
}
function projected(coord1: Coordinates, coord2: Coordinates, coord3: Coordinates): Coordinates {
// Calculates the point on the line segment defined by p1 and p2
// Calculates the point on the line defined by p1 and p2
// that is closest to the third point, p3.
// Uses simple planar geometry (ignores earth curvature).
// Input lat1,lon1,lat2,lon2,lat3,lon3 in degrees.
// Convert to meters using approximate scaling
const metersPerLongitudeDegree = getMetersPerLongitudeDegree(coord1.lat);
const rad = Math.PI / 180;
const lat1 = coord1.lat * rad;
const lat2 = coord2.lat * rad;
const lat3 = coord3.lat * rad;
const x1 = coord1.lon * metersPerLongitudeDegree;
const y1 = coord1.lat * metersPerLatitudeDegree;
const x2 = coord2.lon * metersPerLongitudeDegree;
const y2 = coord2.lat * metersPerLatitudeDegree;
const x3 = coord3.lon * metersPerLongitudeDegree;
const y3 = coord3.lat * metersPerLatitudeDegree;
const lon1 = coord1.lon * rad;
const lon2 = coord2.lon * rad;
const lon3 = coord3.lon * rad;
const dx = x2 - x1;
const dy = y2 - y1;
const segmentLengthSquared = dx * dx + dy * dy;
// Prerequisites for the formulas
const bear12 = bearing(lat1, lon1, lat2, lon2);
const bear13 = bearing(lat1, lon1, lat3, lon3);
let dis13 = distance(lat1, lon1, lat3, lon3);
if (segmentLengthSquared === 0) {
// p1 and p2 are the same point
let diff = Math.abs(bear13 - bear12);
if (diff > Math.PI) {
diff = 2 * Math.PI - diff;
}
// Is relative bearing obtuse?
if (diff > Math.PI / 2) {
return coord1;
}
// Project p3 onto the line defined by p1-p2
const t = Math.max(0, Math.min(1, ((x3 - x1) * dx + (y3 - y1) * dy) / segmentLengthSquared));
// Find the cross-track distance.
let dxt = Math.asin(Math.sin(dis13 / earthRadius) * Math.sin(bear13 - bear12)) * earthRadius;
// Find the closest point on the segment
const projX = x1 + t * dx;
const projY = y1 + t * dy;
// Is p4 beyond the arc?
let dis12 = distance(lat1, lon1, lat2, lon2);
let dis14 =
Math.acos(Math.cos(dis13 / earthRadius) / Math.cos(dxt / earthRadius)) * earthRadius;
if (dis14 > dis12) {
return coord2;
} else {
// Determine the closest point (p4) on the great circle
const f = dis14 / earthRadius;
const lat4 = Math.asin(
Math.sin(lat1) * Math.cos(f) + Math.cos(lat1) * Math.sin(f) * Math.cos(bear12)
);
const lon4 =
lon1 +
Math.atan2(
Math.sin(bear12) * Math.sin(f) * Math.cos(lat1),
Math.cos(f) - Math.sin(lat1) * Math.sin(lat4)
);
// Convert back to degrees
return {
lat: projY / metersPerLatitudeDegree,
lon: projX / metersPerLongitudeDegree,
};
return { lat: lat4 / rad, lon: lon4 / rad };
}
}

View File

@@ -1,391 +0,0 @@
import { TrackPoint } from './gpx';
import { Coordinates } from './types';
export class GPXGlobalStatistics {
length: number;
distance: {
moving: number;
total: number;
};
time: {
start: Date | undefined;
end: Date | undefined;
moving: number;
total: number;
};
speed: {
moving: number;
total: number;
};
elevation: {
gain: number;
loss: number;
};
bounds: {
southWest: Coordinates;
northEast: Coordinates;
};
atemp: {
avg: number;
count: number;
};
hr: {
avg: number;
count: number;
};
cad: {
avg: number;
count: number;
};
power: {
avg: number;
count: number;
};
extensions: Record<string, Record<string, number>>;
constructor() {
this.length = 0;
this.distance = {
moving: 0,
total: 0,
};
this.time = {
start: undefined,
end: undefined,
moving: 0,
total: 0,
};
this.speed = {
moving: 0,
total: 0,
};
this.elevation = {
gain: 0,
loss: 0,
};
this.bounds = {
southWest: {
lat: 90,
lon: 180,
},
northEast: {
lat: -90,
lon: -180,
},
};
this.atemp = {
avg: 0,
count: 0,
};
this.hr = {
avg: 0,
count: 0,
};
this.cad = {
avg: 0,
count: 0,
};
this.power = {
avg: 0,
count: 0,
};
this.extensions = {};
}
mergeWith(other: GPXGlobalStatistics): void {
this.length += other.length;
this.distance.total += other.distance.total;
this.distance.moving += other.distance.moving;
this.time.start =
this.time.start !== undefined && other.time.start !== undefined
? new Date(Math.min(this.time.start.getTime(), other.time.start.getTime()))
: (this.time.start ?? other.time.start);
this.time.end =
this.time.end !== undefined && other.time.end !== undefined
? new Date(Math.max(this.time.end.getTime(), other.time.end.getTime()))
: (this.time.end ?? other.time.end);
this.time.total += other.time.total;
this.time.moving += other.time.moving;
this.speed.moving =
this.time.moving > 0 ? this.distance.moving / (this.time.moving / 3600) : 0;
this.speed.total = this.time.total > 0 ? this.distance.total / (this.time.total / 3600) : 0;
this.elevation.gain += other.elevation.gain;
this.elevation.loss += other.elevation.loss;
this.bounds.southWest.lat = Math.min(this.bounds.southWest.lat, other.bounds.southWest.lat);
this.bounds.southWest.lon = Math.min(this.bounds.southWest.lon, other.bounds.southWest.lon);
this.bounds.northEast.lat = Math.max(this.bounds.northEast.lat, other.bounds.northEast.lat);
this.bounds.northEast.lon = Math.max(this.bounds.northEast.lon, other.bounds.northEast.lon);
this.atemp.avg =
(this.atemp.count * this.atemp.avg + other.atemp.count * other.atemp.avg) /
Math.max(1, this.atemp.count + other.atemp.count);
this.atemp.count += other.atemp.count;
this.hr.avg =
(this.hr.count * this.hr.avg + other.hr.count * other.hr.avg) /
Math.max(1, this.hr.count + other.hr.count);
this.hr.count += other.hr.count;
this.cad.avg =
(this.cad.count * this.cad.avg + other.cad.count * other.cad.avg) /
Math.max(1, this.cad.count + other.cad.count);
this.cad.count += other.cad.count;
this.power.avg =
(this.power.count * this.power.avg + other.power.count * other.power.avg) /
Math.max(1, this.power.count + other.power.count);
this.power.count += other.power.count;
Object.keys(other.extensions).forEach((extension) => {
if (this.extensions[extension] === undefined) {
this.extensions[extension] = {};
}
Object.keys(other.extensions[extension]).forEach((value) => {
if (this.extensions[extension][value] === undefined) {
this.extensions[extension][value] = 0;
}
this.extensions[extension][value] += other.extensions[extension][value];
});
});
}
}
export class TrackPointLocalStatistics {
distance: {
moving: number;
total: number;
};
time: {
moving: number;
total: number;
};
speed: number;
elevation: {
gain: number;
loss: number;
};
slope: {
at: number;
segment: number;
length: number;
};
constructor() {
this.distance = {
moving: 0,
total: 0,
};
this.time = {
moving: 0,
total: 0,
};
this.speed = 0;
this.elevation = {
gain: 0,
loss: 0,
};
this.slope = {
at: 0,
segment: 0,
length: 0,
};
}
}
export class GPXLocalStatistics {
points: TrackPoint[];
data: TrackPointLocalStatistics[];
constructor() {
this.points = [];
this.data = [];
}
}
export type TrackPointWithLocalStatistics = {
trkpt: TrackPoint;
} & TrackPointLocalStatistics;
export class GPXStatistics {
global: GPXGlobalStatistics;
local: GPXLocalStatistics;
constructor() {
this.global = new GPXGlobalStatistics();
this.local = new GPXLocalStatistics();
}
sliced(start: number, end: number): GPXGlobalStatistics {
if (start < 0) {
start = 0;
} else if (start >= this.global.length) {
return new GPXGlobalStatistics();
}
if (end < start) {
return new GPXGlobalStatistics();
} else if (end >= this.global.length) {
end = this.global.length - 1;
}
if (start === 0 && end === this.global.length - 1) {
return this.global;
}
let statistics = new GPXGlobalStatistics();
statistics.length = end - start + 1;
statistics.distance.total =
this.local.data[end].distance.total - this.local.data[start].distance.total;
statistics.distance.moving =
this.local.data[end].distance.moving - this.local.data[start].distance.moving;
statistics.time.start = this.local.points[start].time;
statistics.time.end = this.local.points[end].time;
statistics.time.total = this.local.data[end].time.total - this.local.data[start].time.total;
statistics.time.moving =
this.local.data[end].time.moving - this.local.data[start].time.moving;
statistics.speed.moving =
statistics.time.moving > 0
? statistics.distance.moving / (statistics.time.moving / 3600)
: 0;
statistics.speed.total =
statistics.time.total > 0
? statistics.distance.total / (statistics.time.total / 3600)
: 0;
statistics.elevation.gain =
this.local.data[end].elevation.gain - this.local.data[start].elevation.gain;
statistics.elevation.loss =
this.local.data[end].elevation.loss - this.local.data[start].elevation.loss;
statistics.bounds.southWest.lat = this.global.bounds.southWest.lat;
statistics.bounds.southWest.lon = this.global.bounds.southWest.lon;
statistics.bounds.northEast.lat = this.global.bounds.northEast.lat;
statistics.bounds.northEast.lon = this.global.bounds.northEast.lon;
statistics.atemp = this.global.atemp;
statistics.hr = this.global.hr;
statistics.cad = this.global.cad;
statistics.power = this.global.power;
return statistics;
}
}
export class GPXStatisticsGroup {
private _statistics: GPXStatistics[];
private _cumulative: GPXGlobalStatistics[];
private _slice: [number, number] | null = null;
global: GPXGlobalStatistics;
constructor() {
this._statistics = [];
this._cumulative = [new GPXGlobalStatistics()];
this.global = new GPXGlobalStatistics();
}
add(statistics: GPXStatistics | GPXStatisticsGroup): void {
if (statistics instanceof GPXStatisticsGroup) {
statistics._statistics.forEach((stats) => this._add(stats));
} else {
this._add(statistics);
}
}
_add(statistics: GPXStatistics): void {
this._statistics.push(statistics);
const cumulative = new GPXGlobalStatistics();
cumulative.mergeWith(this._cumulative[this._cumulative.length - 1]);
cumulative.mergeWith(statistics.global);
this._cumulative.push(cumulative);
this.global.mergeWith(statistics.global);
}
sliced(start: number, end: number): GPXGlobalStatistics {
let sliced = new GPXGlobalStatistics();
for (let i = 0; i < this._statistics.length; i++) {
const statistics = this._statistics[i];
const cumulative = this._cumulative[i];
if (start < cumulative.length + statistics.global.length && end >= cumulative.length) {
const localStart = Math.max(0, start - cumulative.length);
const localEnd = Math.min(statistics.global.length - 1, end - cumulative.length);
sliced.mergeWith(statistics.sliced(localStart, localEnd));
}
}
return sliced;
}
getTrackPoint(index: number): TrackPointWithLocalStatistics | undefined {
if (this._slice !== null) {
index += this._slice[0];
}
for (let i = 0; i < this._statistics.length; i++) {
const statistics = this._statistics[i];
const cumulative = this._cumulative[i];
if (index < cumulative.length + statistics.global.length) {
return this._getTrackPoint(cumulative, statistics, index - cumulative.length);
}
}
return undefined;
}
_getTrackPoint(
cumulative: GPXGlobalStatistics,
statistics: GPXStatistics,
index: number
): TrackPointWithLocalStatistics {
const point = statistics.local.points[index];
return {
trkpt: point,
distance: {
moving: statistics.local.data[index].distance.moving + cumulative.distance.moving,
total: statistics.local.data[index].distance.total + cumulative.distance.total,
},
time: {
moving: statistics.local.data[index].time.moving + cumulative.time.moving,
total: statistics.local.data[index].time.total + cumulative.time.total,
},
speed: statistics.local.data[index].speed,
elevation: {
gain: statistics.local.data[index].elevation.gain + cumulative.elevation.gain,
loss: statistics.local.data[index].elevation.loss + cumulative.elevation.loss,
},
slope: {
at: statistics.local.data[index].slope.at,
segment: statistics.local.data[index].slope.segment,
length: statistics.local.data[index].slope.length,
},
};
}
forEachTrackPoint(
callback: (
point: TrackPoint,
distance: number,
speed: number,
slope: { at: number; segment: number; length: number },
index: number
) => void
): void {
for (let i = 0; i < this._statistics.length; i++) {
const statistics = this._statistics[i];
const cumulative = this._cumulative[i];
statistics.local.points.forEach((point, index) =>
callback(
point,
cumulative.distance.total + statistics.local.data[index].distance.total,
statistics.local.data[index].speed,
statistics.local.data[index].slope,
cumulative.length + index
)
);
}
}
}

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "gpx.studio",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@@ -1,17 +1,17 @@
{
"$schema": "https://shadcn-svelte.com/schema.json",
"style": "default",
"tailwind": {
"css": "src/app.css",
"baseColor": "slate"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true,
"registry": "https://shadcn-svelte.com/registry"
"$schema": "https://shadcn-svelte.com/schema.json",
"style": "default",
"tailwind": {
"css": "src/app.css",
"baseColor": "slate"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks",
"lib": "$lib"
},
"typescript": true,
"registry": "https://shadcn-svelte.com/registry"
}

View File

@@ -14,7 +14,7 @@
"@mapbox/sphericalmercator": "^2.0.1",
"@mapbox/tilebelt": "^2.0.2",
"@types/mapbox__sphericalmercator": "^1.2.3",
"chart.js": "^4.5.1",
"chart.js": "^4.4.9",
"chartjs-plugin-zoom": "^2.2.0",
"clsx": "^2.1.1",
"dexie": "^4.0.11",
@@ -22,7 +22,7 @@
"gpx": "file:../gpx",
"immer": "^10.1.1",
"jszip": "^3.10.1",
"mapbox-gl": "^3.17.0",
"mapbox-gl": "^3.12.0",
"mapillary-js": "^4.1.2",
"png.js": "^0.2.1",
"sanitize-html": "^2.17.0",
@@ -47,7 +47,7 @@
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.1",
"bits-ui": "^2.14.4",
"bits-ui": "^2.12.0",
"eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.1",
@@ -1701,10 +1701,9 @@
}
},
"node_modules/@mapbox/point-geometry": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz",
"integrity": "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ==",
"license": "ISC"
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
"integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="
},
"node_modules/@mapbox/polyline": {
"version": "1.2.1",
@@ -1739,26 +1738,11 @@
"integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="
},
"node_modules/@mapbox/vector-tile": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz",
"integrity": "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg==",
"license": "BSD-3-Clause",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz",
"integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==",
"dependencies": {
"@mapbox/point-geometry": "~1.1.0",
"@types/geojson": "^7946.0.16",
"pbf": "^4.0.1"
}
},
"node_modules/@mapbox/vector-tile/node_modules/pbf": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz",
"integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
"license": "BSD-3-Clause",
"dependencies": {
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
"@mapbox/point-geometry": "~0.1.0"
}
},
"node_modules/@mapbox/whoots-js": {
@@ -2660,8 +2644,7 @@
"node_modules/@types/mapbox__point-geometry": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz",
"integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA==",
"license": "MIT"
"integrity": "sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA=="
},
"node_modules/@types/mapbox__sphericalmercator": {
"version": "1.2.3",
@@ -2677,6 +2660,16 @@
"@types/geojson": "*"
}
},
"node_modules/@types/mapbox__vector-tile": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.4.tgz",
"integrity": "sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==",
"dependencies": {
"@types/geojson": "*",
"@types/mapbox__point-geometry": "*",
"@types/pbf": "*"
}
},
"node_modules/@types/mapbox-gl": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz",
@@ -3241,9 +3234,9 @@
]
},
"node_modules/bits-ui": {
"version": "2.14.4",
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.14.4.tgz",
"integrity": "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg==",
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.12.0.tgz",
"integrity": "sha512-8NF4ILNyAJlIxDXpl/akGXGBV5QmZAe+8gTfPttM5P6/+LrijumcSfFXY5cr4QkXwTmLA7H5stYpbgJf2XFJvg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3664,9 +3657,9 @@
}
},
"node_modules/chart.js": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
"integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
@@ -4954,10 +4947,9 @@
}
},
"node_modules/gl-matrix": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz",
"integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==",
"license": "MIT"
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
"integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
},
"node_modules/glob": {
"version": "11.0.2",
@@ -6069,55 +6061,44 @@
}
},
"node_modules/mapbox-gl": {
"version": "3.17.0",
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.17.0.tgz",
"integrity": "sha512-nCrDKRlr5di6xUksUDslNWwxroJ5yv1hT8pyVFtcpWJOOKsYQxF/wOFTMie8oxMnXeFkrz1Tl1TwA1XN1yX0KA==",
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.12.0.tgz",
"integrity": "sha512-DV6TRr+xoPrLSKuGiUcbyLVkoLdNaNNpn6O7+ZC27yQH7BOOIF7l6JKbTCMhfMJuZBVJfL8YRJjlMJ6MZCTggA==",
"license": "SEE LICENSE IN LICENSE.txt",
"workspaces": [
"src/style-spec",
"test/build/vite",
"test/build/webpack",
"test/build/typings"
],
"dependencies": {
"@mapbox/jsonlint-lines-primitives": "^2.0.2",
"@mapbox/mapbox-gl-supported": "^3.0.0",
"@mapbox/point-geometry": "^1.1.0",
"@mapbox/point-geometry": "^0.1.0",
"@mapbox/tiny-sdf": "^2.0.6",
"@mapbox/unitbezier": "^0.0.1",
"@mapbox/vector-tile": "^2.0.4",
"@mapbox/vector-tile": "^1.3.1",
"@mapbox/whoots-js": "^3.1.0",
"@types/geojson": "^7946.0.16",
"@types/geojson-vt": "^3.2.5",
"@types/mapbox__point-geometry": "^0.1.4",
"@types/mapbox__vector-tile": "^1.3.4",
"@types/pbf": "^3.0.5",
"@types/supercluster": "^7.1.3",
"cheap-ruler": "^4.0.0",
"csscolorparser": "~1.0.3",
"earcut": "^3.0.1",
"geojson-vt": "^4.0.2",
"gl-matrix": "^3.4.4",
"gl-matrix": "^3.4.3",
"grid-index": "^1.1.0",
"kdbush": "^4.0.2",
"martinez-polygon-clipping": "^0.7.4",
"murmurhash-js": "^1.0.0",
"pbf": "^4.0.1",
"pbf": "^3.2.1",
"potpack": "^2.0.0",
"quickselect": "^3.0.0",
"serialize-to-js": "^3.1.2",
"supercluster": "^8.0.1",
"tinyqueue": "^3.0.0"
}
},
"node_modules/mapbox-gl/node_modules/pbf": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz",
"integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
"license": "BSD-3-Clause",
"dependencies": {
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
"tinyqueue": "^3.0.0",
"vt-pbf": "^3.1.3"
}
},
"node_modules/mapillary-js": {
@@ -7635,6 +7616,14 @@
"node": ">=10"
}
},
"node_modules/serialize-to-js": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz",
"integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz",
@@ -9032,6 +9021,16 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
"node_modules/vt-pbf": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
"integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==",
"dependencies": {
"@mapbox/point-geometry": "0.1.0",
"@mapbox/vector-tile": "^1.3.1",
"pbf": "^3.2.1"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@@ -10,8 +10,8 @@
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore && eslint .",
"format": "prettier --write . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore"
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@lucide/svelte": "^0.544.0",
@@ -31,7 +31,7 @@
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.1",
"bits-ui": "^2.14.4",
"bits-ui": "^2.12.0",
"eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.1",
@@ -66,7 +66,7 @@
"@mapbox/sphericalmercator": "^2.0.1",
"@mapbox/tilebelt": "^2.0.2",
"@types/mapbox__sphericalmercator": "^1.2.3",
"chart.js": "^4.5.1",
"chart.js": "^4.4.9",
"chartjs-plugin-zoom": "^2.2.0",
"clsx": "^2.1.1",
"dexie": "^4.0.11",
@@ -74,7 +74,7 @@
"gpx": "file:../gpx",
"immer": "^10.1.1",
"jszip": "^3.10.1",
"mapbox-gl": "^3.17.0",
"mapbox-gl": "^3.12.0",
"mapillary-js": "^4.1.2",
"png.js": "^0.2.1",
"sanitize-html": "^2.17.0",

View File

@@ -1,126 +1,124 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--background: hsl(0 0% 100%) /* <- Wrap in HSL */;
--foreground: hsl(240 10% 3.9%);
--muted: hsl(240 4.8% 95.9%);
--muted-foreground: hsl(240 3.8% 46.1%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(240 10% 3.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(240 10% 3.9%);
--border: hsl(240 5.9% 90%);
--input: hsl(240 5.9% 90%);
--primary: hsl(240 5.9% 10%);
--primary-foreground: hsl(0 0% 98%);
--secondary: hsl(240 4.8% 95.9%);
--secondary-foreground: hsl(240 5.9% 10%);
--accent: hsl(240 4.8% 95.9%);
--accent-foreground: hsl(240 5.9% 10%);
--destructive: hsl(0 72.2% 50.6%);
--destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 10% 3.9%);
--sidebar: hsl(0 0% 98%);
--sidebar-foreground: hsl(240 5.3% 26.1%);
--sidebar-primary: hsl(240 5.9% 10%);
--sidebar-primary-foreground: hsl(0 0% 98%);
--sidebar-accent: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(220 13% 91%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
--background: hsl(0 0% 100%) /* <- Wrap in HSL */;
--foreground: hsl(240 10% 3.9%);
--muted: hsl(240 4.8% 95.9%);
--muted-foreground: hsl(240 3.8% 46.1%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(240 10% 3.9%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(240 10% 3.9%);
--border: hsl(240 5.9% 90%);
--input: hsl(240 5.9% 90%);
--primary: hsl(240 5.9% 10%);
--primary-foreground: hsl(0 0% 98%);
--secondary: hsl(240 4.8% 95.9%);
--secondary-foreground: hsl(240 5.9% 10%);
--accent: hsl(240 4.8% 95.9%);
--accent-foreground: hsl(240 5.9% 10%);
--destructive: hsl(0 72.2% 50.6%);
--destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 10% 3.9%);
--sidebar: hsl(0 0% 98%);
--sidebar-foreground: hsl(240 5.3% 26.1%);
--sidebar-primary: hsl(240 5.9% 10%);
--sidebar-primary-foreground: hsl(0 0% 98%);
--sidebar-accent: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(220 13% 91%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
--support: rgb(220 15 130);
--link: rgb(0 110 180);
--selection: hsl(240 4.8% 93%);
--radius: 0.5rem;
--support: rgb(220 15 130);
--link: rgb(0 110 180);
--radius: 0.5rem;
}
.dark {
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%);
--popover: hsl(240 10% 3.9%);
--popover-foreground: hsl(0 0% 98%);
--card: hsl(240 10% 3.9%);
--card-foreground: hsl(0 0% 98%);
--border: hsl(240 3.7% 15.9%);
--input: hsl(240 3.7% 15.9%);
--primary: hsl(0 0% 98%);
--primary-foreground: hsl(240 5.9% 10%);
--secondary: hsl(240 3.7% 15.9%);
--secondary-foreground: hsl(0 0% 98%);
--accent: hsl(240 3.7% 15.9%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 4.9% 83.9%);
--sidebar: hsl(240 5.9% 10%);
--sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
--background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%);
--popover: hsl(240 10% 3.9%);
--popover-foreground: hsl(0 0% 98%);
--card: hsl(240 10% 3.9%);
--card-foreground: hsl(0 0% 98%);
--border: hsl(240 3.7% 15.9%);
--input: hsl(240 3.7% 15.9%);
--primary: hsl(0 0% 98%);
--primary-foreground: hsl(240 5.9% 10%);
--secondary: hsl(240 3.7% 15.9%);
--secondary-foreground: hsl(0 0% 98%);
--accent: hsl(240 3.7% 15.9%);
--accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 4.9% 83.9%);
--sidebar: hsl(240 5.9% 10%);
--sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
--support: rgb(255 110 190);
--link: rgb(80 190 255);
--selection: hsl(240 3.7% 22%);
--support: rgb(255 110 190);
--link: rgb(80 190 255);
}
@theme inline {
/* Radius (for rounded-*) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* Radius (for rounded-*) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
/* Colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-ring: var(--ring);
--color-radius: var(--radius);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-support: var(--support);
--color-link: var(--link);
/* Colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-ring: var(--ring);
--color-radius: var(--radius);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-support: var(--support);
--color-link: var(--link);
--breakpoint-xs: 540px;
--breakpoint-xs: 540px;
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -24,14 +24,6 @@ export async function handle({ event, resolve }) {
let headTag = `<head>
<title>gpx.studio — ${title}</title>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "gpx.studio",
"url": "https://gpx.studio"
}
</script>
<meta name="description" content="${description}" />
<meta property="og:title" content="gpx.studio — ${title}" />
<meta property="og:description" content="${description}" />

View File

@@ -17,6 +17,7 @@
}
},
"sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite",
"glyphs": "https://api.maptiler.com/fonts/{fontstack}/{range}.pbf?key={key}",
"layers": [
{
"id": "background",

View File

@@ -22,7 +22,7 @@ import {
Binoculars,
Toilet,
} from 'lucide-static';
import { type RasterDEMSourceSpecification, type StyleSpecification } from 'mapbox-gl';
import { type StyleSpecification } from 'mapbox-gl';
import ignFrTopo from './custom/ign-fr-topo.json';
import ignFrPlan from './custom/ign-fr-plan.json';
import ignFrSatellite from './custom/ign-fr-satellite.json';
@@ -145,7 +145,7 @@ export const basemaps: { [key: string]: string | StyleSpecification } = {
swisstopoVector: 'https://vectortiles.geo.admin.ch/styles/ch.swisstopo.basemap.vt/style.json',
swisstopoSatellite:
'https://vectortiles.geo.admin.ch/styles/ch.swisstopo.imagerybasemap.vt/style.json',
linz: 'https://basemaps.linz.govt.nz/v1/styles/topographic-v2.json?api=d01fbtg0ar23gctac5m0jgyy2ds',
linz: 'https://basemaps.linz.govt.nz/v1/tiles/topographic/EPSG:3857/style/topographic.json?api=d01fbtg0ar23gctac5m0jgyy2ds',
linzTopo: {
version: 8,
sources: {
@@ -368,42 +368,6 @@ export const overlays: { [key: string]: string | StyleSpecification } = {
],
},
bikerouterGravel: bikerouterGravel as StyleSpecification,
openRailwayMap: {
version: 8,
sources: {
openRailwayMap: {
type: 'raster',
tiles: ['https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png'],
tileSize: 256,
maxzoom: 19,
attribution:
'Data <a href="https://www.openstreetmap.org/copyright">&copy; OpenStreetMap contributors</a>, Style: <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA 2.0</a> <a href="http://www.openrailwaymap.org/">OpenRailwayMap</a>',
},
},
layers: [
{
id: 'openRailwayMap',
type: 'raster',
source: 'openRailwayMap',
},
],
},
mapterhornHillshade: {
version: 8,
sources: {
mapterhornHillshade: {
type: 'raster-dem',
url: 'https://tiles.mapterhorn.com/tilejson.json',
},
},
layers: [
{
id: 'mapterhornHillshade',
type: 'hillshade',
source: 'mapterhornHillshade',
},
],
},
swisstopoSlope: {
version: 8,
sources: {
@@ -835,10 +799,8 @@ export const overlayTree: LayerTreeType = {
waymarkedTrailsHorseRiding: true,
waymarkedTrailsWinter: true,
},
bikerouterGravel: true,
cyclOSMlite: true,
mapterhornHillshade: true,
openRailwayMap: true,
bikerouterGravel: true,
},
countries: {
france: {
@@ -874,7 +836,6 @@ export const overpassTree: LayerTreeType = {
shower: true,
shelter: true,
barrier: true,
cemetery: true,
},
tourism: {
attraction: true,
@@ -921,10 +882,8 @@ export const defaultOverlays: LayerTreeType = {
waymarkedTrailsHorseRiding: false,
waymarkedTrailsWinter: false,
},
bikerouterGravel: false,
cyclOSMlite: false,
mapterhornHillshade: false,
openRailwayMap: false,
bikerouterGravel: false,
},
countries: {
france: {
@@ -960,7 +919,6 @@ export const defaultOverpassQueries: LayerTreeType = {
shower: false,
shelter: false,
barrier: false,
cemetery: false,
},
tourism: {
attraction: false,
@@ -1058,10 +1016,8 @@ export const defaultOverlayTree: LayerTreeType = {
waymarkedTrailsHorseRiding: false,
waymarkedTrailsWinter: false,
},
bikerouterGravel: false,
cyclOSMlite: false,
mapterhornHillshade: false,
openRailwayMap: false,
bikerouterGravel: false,
},
countries: {
france: {
@@ -1097,7 +1053,6 @@ export const defaultOverpassTree: LayerTreeType = {
shower: false,
shelter: false,
barrier: false,
cemetery: false,
},
tourism: {
attraction: false,
@@ -1144,7 +1099,9 @@ type OverpassQueryData = {
svg: string;
color: string;
};
tags: Record<string, string | string[]> | Record<string, string | string[]>[];
tags:
| Record<string, string | boolean | string[]>
| Record<string, string | boolean | string[]>[];
symbol?: string;
};
@@ -1225,20 +1182,6 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
},
symbol: 'Shelter',
},
cemetery: {
icon: {
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 17v-10a6 5 0 1 1 12 0v10"/><path d="M 4 21 a 1 1 0 0 0 1 1 h 14 a 1 1 0 0 0 1-1 v -1 a 2 2 0 0 0-2-2 H6 a 2 2 0 0 0-2 2 z"/></svg>',
color: '#000000',
},
tags: [
{
landuse: 'cemetery',
},
{
amenity: 'grave_yard',
},
],
},
'fuel-station': {
icon: {
svg: Fuel,
@@ -1275,25 +1218,7 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
color: '#000000',
},
tags: {
barrier: [
'bar',
'barrier_board',
'block',
'chain',
'cycle_barrier',
'gate',
'hampshire_gate',
'horse_stile',
'kissing_gate',
'lift_gate',
'motorcycle_barrier',
'sliding_beam',
'sliding_gate',
'stile',
'swing_gate',
'turnstile',
'wicket_gate',
],
barrier: true,
},
},
attraction: {
@@ -1453,18 +1378,3 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
symbol: 'Anchor',
},
};
export const terrainSources: { [key: string]: RasterDEMSourceSpecification } = {
'mapbox-dem': {
type: 'raster-dem',
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14,
},
mapterhorn: {
type: 'raster-dem',
url: 'https://tiles.mapterhorn.com/tilejson.json',
},
};
export const defaultTerrainSource = 'mapbox-dem';

View File

@@ -1,5 +1,6 @@
import {
Landmark,
Icon,
Shell,
Bike,
Building,
@@ -28,7 +29,6 @@ import {
TriangleAlert,
Anchor,
Toilet,
X,
type IconProps,
} from '@lucide/svelte';
import {
@@ -61,7 +61,6 @@ import {
TriangleAlert as TriangleAlertSvg,
Anchor as AnchorSvg,
Toilet as ToiletSvg,
X as XSvg,
} from 'lucide-static';
import type { Component } from 'svelte';
@@ -88,11 +87,7 @@ export const symbols: { [key: string]: Symbol } = {
icon: ShoppingBasket,
iconSvg: ShoppingBasketSvg,
},
crossing: {
value: 'Crossing',
icon: X,
iconSvg: XSvg,
},
crossing: { value: 'Crossing' },
department_store: {
value: 'Department Store',
icon: ShoppingBasket,

View File

@@ -18,7 +18,7 @@
href="https://github.com/gpxstudio/gpx.studio/blob/main/LICENSE"
target="_blank"
>
MIT © 2026 gpx.studio
MIT © 2025 gpx.studio
</Button>
<LanguageSelect class="w-40 mt-3" />
</div>
@@ -34,7 +34,6 @@
{i18n._('homepage.home')}
</Button>
<Button
data-sveltekit-reload
variant="link"
class="h-6 px-0 has-[>svg]:px-0 text-muted-foreground"
href={getURLForLanguage(i18n.lang, '/app')}
@@ -71,6 +70,15 @@
<Logo company="facebook" class="h-4 fill-muted-foreground" />
{i18n._('homepage.facebook')}
</Button>
<Button
variant="link"
class="h-6 px-0 has-[>svg]:px-0 text-muted-foreground"
href="https://x.com/gpxstudio"
target="_blank"
>
<Logo company="x" class="h-4 fill-muted-foreground" />
{i18n._('homepage.x')}
</Button>
<Button
variant="link"
class="h-6 px-0 has-[>svg]:px-0 text-muted-foreground"

View File

@@ -6,7 +6,7 @@
import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from '@lucide/svelte';
import { i18n } from '$lib/i18n.svelte';
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import type { GPXStatistics } from 'gpx';
import type { Readable } from 'svelte/store';
import { settings } from '$lib/logic/settings';
@@ -18,14 +18,14 @@
orientation,
panelSize,
}: {
gpxStatistics: Readable<GPXStatisticsGroup>;
slicedGPXStatistics: Readable<[GPXGlobalStatistics, number, number] | undefined>;
gpxStatistics: Readable<GPXStatistics>;
slicedGPXStatistics: Readable<[GPXStatistics, number, number] | undefined>;
orientation: 'horizontal' | 'vertical';
panelSize: number;
} = $props();
let statistics = $derived(
$slicedGPXStatistics !== undefined ? $slicedGPXStatistics[0] : $gpxStatistics.global
$slicedGPXStatistics !== undefined ? $slicedGPXStatistics[0] : $gpxStatistics
);
</script>
@@ -42,15 +42,15 @@
<Tooltip label={i18n._('quantities.distance')}>
<span class="flex flex-row items-center">
<Ruler size="16" class="mr-1" />
<WithUnits value={statistics.distance.total} type="distance" />
<WithUnits value={statistics.global.distance.total} type="distance" />
</span>
</Tooltip>
<Tooltip label={i18n._('quantities.elevation_gain_loss')}>
<span class="flex flex-row items-center">
<MoveUpRight size="16" class="mr-1" />
<WithUnits value={statistics.elevation.gain} type="elevation" />
<WithUnits value={statistics.global.elevation.gain} type="elevation" />
<MoveDownRight size="16" class="mx-1" />
<WithUnits value={statistics.elevation.loss} type="elevation" />
<WithUnits value={statistics.global.elevation.loss} type="elevation" />
</span>
</Tooltip>
{#if panelSize > 120 || orientation === 'horizontal'}
@@ -64,9 +64,13 @@
>
<span class="flex flex-row items-center">
<Zap size="16" class="mr-1" />
<WithUnits value={statistics.speed.moving} type="speed" showUnits={false} />
<WithUnits
value={statistics.global.speed.moving}
type="speed"
showUnits={false}
/>
<span class="mx-1">/</span>
<WithUnits value={statistics.speed.total} type="speed" />
<WithUnits value={statistics.global.speed.total} type="speed" />
</span>
</Tooltip>
{/if}
@@ -79,9 +83,9 @@
>
<span class="flex flex-row items-center">
<Timer size="16" class="mr-1" />
<WithUnits value={statistics.time.moving} type="time" />
<WithUnits value={statistics.global.time.moving} type="time" />
<span class="mx-1">/</span>
<WithUnits value={statistics.time.total} type="time" />
<WithUnits value={statistics.global.time.total} type="time" />
</span>
</Tooltip>
{/if}

View File

@@ -8,7 +8,7 @@
...others
}: {
iconOnly?: boolean;
company?: 'gpx.studio' | 'mapbox' | 'github' | 'crowdin' | 'facebook' | 'reddit';
company?: 'gpx.studio' | 'mapbox' | 'github' | 'crowdin' | 'facebook' | 'x' | 'reddit';
[key: string]: any;
} = $props();
</script>
@@ -55,6 +55,16 @@
d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z"
/></svg
>
{:else if company === 'x'}
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
class="fill-foreground {others.class ?? ''}"
><title>X</title><path
d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"
/></svg
>
{:else if company === 'reddit'}
<svg
role="img"

View File

@@ -538,7 +538,6 @@
let targetInput =
e &&
e.target &&
e.target instanceof HTMLElement &&
(e.target.tagName === 'INPUT' ||
e.target.tagName === 'TEXTAREA' ||
e.target.tagName === 'SELECT' ||
@@ -645,19 +644,6 @@
} else if (e.key === 'F5') {
$routing = !$routing;
e.preventDefault();
} else if (
e.key === 'ArrowRight' ||
e.key === 'ArrowDown' ||
e.key === 'ArrowLeft' ||
e.key === 'ArrowUp'
) {
if (!targetInput) {
selection.updateFromKey(
e.key === 'ArrowRight' || e.key === 'ArrowDown',
e.shiftKey
);
e.preventDefault();
}
}
}}
on:dragover={(e) => e.preventDefault()}

View File

@@ -23,7 +23,6 @@
{i18n._('homepage.home')}
</Button>
<Button
data-sveltekit-reload
variant="link"
class="text-base px-0 has-[>svg]:px-0"
href={getURLForLanguage(i18n.lang, '/app')}

View File

@@ -18,7 +18,7 @@
Construction,
} from '@lucide/svelte';
import type { Readable, Writable } from 'svelte/store';
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import type { GPXStatistics } from 'gpx';
import { settings } from '$lib/logic/settings';
import { i18n } from '$lib/i18n.svelte';
import { ElevationProfile } from '$lib/components/elevation-profile/elevation-profile';
@@ -32,8 +32,8 @@
elevationFill,
showControls = true,
}: {
gpxStatistics: Readable<GPXStatisticsGroup>;
slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>;
gpxStatistics: Readable<GPXStatistics>;
slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
additionalDatasets: Writable<string[]>;
elevationFill: Writable<'slope' | 'surface' | 'highway' | undefined>;
showControls?: boolean;

View File

@@ -14,16 +14,11 @@ import {
getTemperatureWithUnits,
getVelocityWithUnits,
} from '$lib/units';
import Chart, {
type ChartEvent,
type ChartOptions,
type ScriptableLineSegmentContext,
type TooltipItem,
} from 'chart.js/auto';
import Chart from 'chart.js/auto';
import mapboxgl from 'mapbox-gl';
import { get, type Readable, type Writable } from 'svelte/store';
import { map } from '$lib/components/map/map';
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import type { GPXStatistics } from 'gpx';
import { mode } from 'mode-watcher';
import { getHighwayColor, getSlopeColor, getSurfaceColor } from '$lib/assets/colors';
@@ -32,20 +27,6 @@ const { distanceUnits, velocityUnits, temperatureUnits } = settings;
Chart.defaults.font.family =
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font
interface ElevationProfilePoint {
x: number;
y: number;
time?: Date;
slope: {
at: number;
segment: number;
length: number;
};
extensions: Record<string, any>;
coordinates: [number, number];
index: number;
}
export class ElevationProfile {
private _chart: Chart | null = null;
private _canvas: HTMLCanvasElement;
@@ -54,14 +35,14 @@ export class ElevationProfile {
private _dragging = false;
private _panning = false;
private _gpxStatistics: Readable<GPXStatisticsGroup>;
private _slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>;
private _gpxStatistics: Readable<GPXStatistics>;
private _slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
private _additionalDatasets: Readable<string[]>;
private _elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>;
constructor(
gpxStatistics: Readable<GPXStatisticsGroup>,
slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>,
gpxStatistics: Readable<GPXStatistics>,
slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>,
additionalDatasets: Readable<string[]>,
elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>,
canvas: HTMLCanvasElement,
@@ -109,7 +90,7 @@ export class ElevationProfile {
}
initialize() {
let options: ChartOptions<'line'> = {
let options = {
animation: false,
parsing: false,
maintainAspectRatio: false,
@@ -117,8 +98,8 @@ export class ElevationProfile {
x: {
type: 'linear',
ticks: {
callback: function (value: number | string) {
return `${(value as number).toFixed(1).replace(/\.0+$/, '')} ${getDistanceUnits()}`;
callback: function (value: number) {
return `${value.toFixed(1).replace(/\.0+$/, '')} ${getDistanceUnits()}`;
},
align: 'inner',
maxRotation: 0,
@@ -127,8 +108,8 @@ export class ElevationProfile {
y: {
type: 'linear',
ticks: {
callback: function (value: number | string) {
return getElevationWithUnits(value as number, false);
callback: function (value: number) {
return getElevationWithUnits(value, false);
},
},
},
@@ -159,8 +140,8 @@ export class ElevationProfile {
title: () => {
return '';
},
label: (context: TooltipItem<'line'>) => {
let point = context.raw as ElevationProfilePoint;
label: (context: Chart.TooltipContext) => {
let point = context.raw;
if (context.datasetIndex === 0) {
const map_ = get(map);
if (map_ && this._marker) {
@@ -184,10 +165,10 @@ export class ElevationProfile {
return `${i18n._('quantities.power')}: ${getPowerWithUnits(point.y)}`;
}
},
afterBody: (contexts: TooltipItem<'line'>[]) => {
afterBody: (contexts: Chart.TooltipContext[]) => {
let context = contexts.filter((context) => context.datasetIndex === 0);
if (context.length === 0) return;
let point = context[0].raw as ElevationProfilePoint;
let point = context[0].raw;
let slope = {
at: point.slope.at.toFixed(1),
segment: point.slope.segment.toFixed(1),
@@ -246,7 +227,6 @@ export class ElevationProfile {
onPanStart: () => {
this._panning = true;
this._slicedGPXStatistics.set(undefined);
return true;
},
onPanComplete: () => {
this._panning = false;
@@ -258,13 +238,13 @@ export class ElevationProfile {
},
mode: 'x',
onZoomStart: ({ chart, event }: { chart: Chart; event: any }) => {
if (!this._chart) {
return false;
}
const maxZoom = this._chart.getInitialScaleBounds()?.x?.max ?? 0;
if (
event.deltaY < 0 &&
Math.abs(maxZoom / this._chart.getZoomLevel()) < 0.01
Math.abs(
this._chart.getInitialScaleBounds().x.max /
this._chart.options.plugins.zoom.limits.x.minRange -
this._chart.getZoomLevel()
) < 0.01
) {
// Disable wheel pan if zoomed in to the max, and zooming in
return false;
@@ -282,6 +262,7 @@ export class ElevationProfile {
},
},
},
stacked: false,
onResize: () => {
this.updateOverlay();
},
@@ -289,7 +270,7 @@ export class ElevationProfile {
let datasets: string[] = ['speed', 'hr', 'cad', 'atemp', 'power'];
datasets.forEach((id) => {
options.scales![`y${id}`] = {
options.scales[`y${id}`] = {
type: 'linear',
position: 'right',
grid: {
@@ -310,7 +291,7 @@ export class ElevationProfile {
{
id: 'toggleMarker',
events: ['mouseout'],
afterEvent: (chart: Chart, args: { event: ChartEvent }) => {
afterEvent: (chart: Chart, args: { event: Chart.ChartEvent }) => {
if (args.event.type === 'mouseout') {
const map_ = get(map);
if (map_ && this._marker) {
@@ -324,7 +305,7 @@ export class ElevationProfile {
let startIndex = 0;
let endIndex = 0;
const getIndex = (evt: PointerEvent) => {
const getIndex = (evt) => {
if (!this._chart) {
return undefined;
}
@@ -342,22 +323,22 @@ export class ElevationProfile {
if (evt.x - rect.left <= this._chart.chartArea.left) {
return 0;
} else if (evt.x - rect.left >= this._chart.chartArea.right) {
return this._chart.data.datasets[0].data.length - 1;
return get(this._gpxStatistics).local.points.length - 1;
} else {
return undefined;
}
}
const point = points.find((point) => (point.element as any).raw);
let point = points.find((point) => point.element.raw);
if (point) {
return (point.element as any).raw.index;
return point.element.raw.index;
} else {
return points[0].index;
}
};
let dragStarted = false;
const onMouseDown = (evt: PointerEvent) => {
const onMouseDown = (evt) => {
if (evt.shiftKey) {
// Panning interaction
return;
@@ -366,7 +347,7 @@ export class ElevationProfile {
this._canvas.style.cursor = 'col-resize';
startIndex = getIndex(evt);
};
const onMouseMove = (evt: PointerEvent) => {
const onMouseMove = (evt) => {
if (dragStarted) {
this._dragging = true;
endIndex = getIndex(evt);
@@ -375,7 +356,7 @@ export class ElevationProfile {
startIndex = endIndex;
} else if (startIndex !== endIndex) {
this._slicedGPXStatistics.set([
get(this._gpxStatistics).sliced(
get(this._gpxStatistics).slice(
Math.min(startIndex, endIndex),
Math.max(startIndex, endIndex)
),
@@ -386,7 +367,7 @@ export class ElevationProfile {
}
}
};
const onMouseUp = (evt: PointerEvent) => {
const onMouseUp = (evt) => {
dragStarted = false;
this._dragging = false;
this._canvas.style.cursor = '';
@@ -405,99 +386,85 @@ export class ElevationProfile {
return;
}
const data = get(this._gpxStatistics);
const units = {
distance: get(distanceUnits),
velocity: get(velocityUnits),
temperature: get(temperatureUnits),
};
const datasets: Array<Array<any>> = [[], [], [], [], [], []];
data.forEachTrackPoint((trkpt, distance, speed, slope, index) => {
datasets[0].push({
x: getConvertedDistance(distance, units.distance),
y: trkpt.ele ? getConvertedElevation(trkpt.ele, units.distance) : 0,
time: trkpt.time,
slope: slope,
extensions: trkpt.getExtensions(),
coordinates: trkpt.getCoordinates(),
index: index,
});
if (data.global.time.total > 0) {
datasets[1].push({
x: getConvertedDistance(distance, units.distance),
y: getConvertedVelocity(speed, units.velocity, units.distance),
index: index,
});
}
if (data.global.hr.count > 0) {
datasets[2].push({
x: getConvertedDistance(distance, units.distance),
y: trkpt.getHeartRate(),
index: index,
});
}
if (data.global.cad.count > 0) {
datasets[3].push({
x: getConvertedDistance(distance, units.distance),
y: trkpt.getCadence(),
index: index,
});
}
if (data.global.atemp.count > 0) {
datasets[4].push({
x: getConvertedDistance(distance, units.distance),
y: getConvertedTemperature(trkpt.getTemperature(), units.temperature),
index: index,
});
}
if (data.global.power.count > 0) {
datasets[5].push({
x: getConvertedDistance(distance, units.distance),
y: trkpt.getPower(),
index: index,
});
}
});
this._chart.data.datasets[0] = {
label: i18n._('quantities.elevation'),
data: datasets[0],
data: data.local.points.map((point, index) => {
return {
x: getConvertedDistance(data.local.distance.total[index]),
y: point.ele ? getConvertedElevation(point.ele) : 0,
time: point.time,
slope: {
at: data.local.slope.at[index],
segment: data.local.slope.segment[index],
length: data.local.slope.length[index],
},
extensions: point.getExtensions(),
coordinates: point.getCoordinates(),
index: index,
};
}),
normalized: true,
fill: 'start',
order: 1,
segment: {},
};
this._chart.data.datasets[1] = {
data: datasets[1],
data: data.local.points.map((point, index) => {
return {
x: getConvertedDistance(data.local.distance.total[index]),
y: getConvertedVelocity(data.local.speed[index]),
index: index,
};
}),
normalized: true,
yAxisID: 'yspeed',
};
this._chart.data.datasets[2] = {
data: datasets[2],
data: data.local.points.map((point, index) => {
return {
x: getConvertedDistance(data.local.distance.total[index]),
y: point.getHeartRate(),
index: index,
};
}),
normalized: true,
yAxisID: 'yhr',
};
this._chart.data.datasets[3] = {
data: datasets[3],
data: data.local.points.map((point, index) => {
return {
x: getConvertedDistance(data.local.distance.total[index]),
y: point.getCadence(),
index: index,
};
}),
normalized: true,
yAxisID: 'ycad',
};
this._chart.data.datasets[4] = {
data: datasets[4],
data: data.local.points.map((point, index) => {
return {
x: getConvertedDistance(data.local.distance.total[index]),
y: getConvertedTemperature(point.getTemperature()),
index: index,
};
}),
normalized: true,
yAxisID: 'yatemp',
};
this._chart.data.datasets[5] = {
data: datasets[5],
data: data.local.points.map((point, index) => {
return {
x: getConvertedDistance(data.local.distance.total[index]),
y: point.getPower(),
index: index,
};
}),
normalized: true,
yAxisID: 'ypower',
};
this._chart.options.scales!.x!['min'] = 0;
this._chart.options.scales!.x!['max'] = getConvertedDistance(
data.global.distance.total,
units.distance
);
this._chart.options.scales.x['min'] = 0;
this._chart.options.scales.x['max'] = getConvertedDistance(data.global.distance.total);
this.setVisibility();
this.setFill();
@@ -546,24 +513,21 @@ export class ElevationProfile {
return;
}
const elevationFill = get(this._elevationFill);
const dataset = this._chart.data.datasets[0];
let segment: any = {};
if (elevationFill === 'slope') {
segment = {
this._chart.data.datasets[0]['segment'] = {
backgroundColor: this.slopeFillCallback,
};
} else if (elevationFill === 'surface') {
segment = {
this._chart.data.datasets[0]['segment'] = {
backgroundColor: this.surfaceFillCallback,
};
} else if (elevationFill === 'highway') {
segment = {
this._chart.data.datasets[0]['segment'] = {
backgroundColor: this.highwayFillCallback,
};
} else {
segment = {};
this._chart.data.datasets[0]['segment'] = {};
}
Object.assign(dataset, { segment });
}
updateOverlay() {
@@ -590,12 +554,10 @@ export class ElevationProfile {
const gpxStatistics = get(this._gpxStatistics);
let startPixel = this._chart.scales.x.getPixelForValue(
getConvertedDistance(
gpxStatistics.getTrackPoint(startIndex)?.distance.total ?? 0
)
getConvertedDistance(gpxStatistics.local.distance.total[startIndex])
);
let endPixel = this._chart.scales.x.getPixelForValue(
getConvertedDistance(gpxStatistics.getTrackPoint(endIndex)?.distance.total ?? 0)
getConvertedDistance(gpxStatistics.local.distance.total[endIndex])
);
selectionContext.fillRect(
@@ -613,22 +575,19 @@ export class ElevationProfile {
}
}
slopeFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
const point = context.p0.raw as ElevationProfilePoint;
return getSlopeColor(point.slope.segment);
slopeFillCallback(context) {
return getSlopeColor(context.p0.raw.slope.segment);
}
surfaceFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
const point = context.p0.raw as ElevationProfilePoint;
return getSurfaceColor(point.extensions.surface);
surfaceFillCallback(context) {
return getSurfaceColor(context.p0.raw.extensions.surface);
}
highwayFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
const point = context.p0.raw as ElevationProfilePoint;
highwayFillCallback(context) {
return getHighwayColor(
point.extensions.highway,
point.extensions.sac_scale,
point.extensions.mtb_scale
context.p0.raw.extensions.highway,
context.p0.raw.extensions.sac_scale,
context.p0.raw.extensions.mtb_scale
);
}

View File

@@ -20,7 +20,6 @@
import { loadFile } from '$lib/logic/file-actions';
import { selection } from '$lib/logic/selection';
import { untrack } from 'svelte';
import { isSelected, toggle } from '$lib/components/map/layer-control/utils';
let {
useHash = true,
@@ -33,7 +32,6 @@
const {
currentBasemap,
selectedBasemapTree,
distanceUnits,
velocityUnits,
temperatureUnits,
@@ -68,9 +66,6 @@
if (allowedEmbeddingBasemaps.includes(options.basemap)) {
$currentBasemap = options.basemap;
}
if (!isSelected($selectedBasemapTree, options.basemap)) {
$selectedBasemapTree = toggle($selectedBasemapTree, options.basemap);
}
$distanceMarkers = options.distanceMarkers;
$directionMarkers = options.directionMarkers;
$distanceUnits = options.distanceUnits;

View File

@@ -21,7 +21,7 @@
SquareActivity,
} from '@lucide/svelte';
import { i18n } from '$lib/i18n.svelte';
import { GPXGlobalStatistics } from 'gpx';
import { GPXStatistics } from 'gpx';
import { ListRootItem } from '$lib/components/file-list/file-list';
import { fileStateCollection } from '$lib/logic/file-state';
import { selection } from '$lib/logic/selection';
@@ -48,24 +48,24 @@
extensions: false,
};
} else {
let statistics = $gpxStatistics.global;
let statistics = $gpxStatistics;
if (exportState.current === ExportState.ALL) {
statistics = Array.from(get(fileStateCollection).values())
.map((file) => file.statistics)
.reduce((acc, cur) => {
if (cur !== undefined) {
acc.mergeWith(cur.getStatisticsFor(new ListRootItem()).global);
acc.mergeWith(cur.getStatisticsFor(new ListRootItem()));
}
return acc;
}, new GPXGlobalStatistics());
}, new GPXStatistics());
}
return {
time: statistics.time.total === 0,
hr: statistics.hr.count === 0,
cad: statistics.cad.count === 0,
atemp: statistics.atemp.count === 0,
power: statistics.power.count === 0,
extensions: Object.keys(statistics.extensions).length === 0,
time: statistics.global.time.total === 0,
hr: statistics.global.hr.count === 0,
cad: statistics.global.cad.count === 0,
atemp: statistics.global.atemp.count === 0,
power: statistics.global.power.count === 0,
extensions: Object.keys(statistics.global.extensions).length === 0,
};
}
});

View File

@@ -121,16 +121,20 @@
}
.vertical :global(button) {
@apply hover:bg-[var(--selection)];
@apply hover:bg-muted;
}
.vertical :global(.sortable-selected button) {
@apply hover:bg-accent;
}
.vertical :global(.sortable-selected) {
@apply bg-[var(--selection)];
@apply bg-accent;
}
.horizontal :global(button) {
@apply bg-[var(--selection)];
@apply hover:bg-background;
@apply bg-accent;
@apply hover:bg-muted;
}
.horizontal :global(.sortable-selected button) {

View File

@@ -34,10 +34,11 @@
import { editStyle } from '$lib/components/file-list/style/utils.svelte';
import { getSymbolKey, symbols } from '$lib/assets/symbols';
import { selection, copied, cut } from '$lib/logic/selection';
import { map } from '$lib/components/map/map';
import { fileActions, pasteSelection } from '$lib/logic/file-actions';
import { allHidden } from '$lib/logic/hidden';
import { boundsManager } from '$lib/logic/bounds';
import { gpxColors, gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers';
import { gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers';
import { fileStateCollection } from '$lib/logic/file-state';
import { waypointPopup } from '$lib/components/map/gpx-layer/gpx-layer-popup';
import { allowedPastes } from './sortable-file-list';
@@ -57,31 +58,41 @@
let singleSelection = $derived($selection.size === 1);
let nodeColors: string[] = $derived.by(() => {
let nodeColors: string[] = $state([]);
$effect.pre(() => {
let colors: string[] = [];
if (node) {
if (node && $map) {
if (node instanceof GPXFile) {
let defaultColor = $gpxColors.get(item.getFileId());
let defaultColor = undefined;
let layer = gpxLayers.getLayer(item.getFileId());
if (layer) {
defaultColor = layer.layerColor;
}
let style = node.getStyle(defaultColor);
colors = style.color;
style.color.forEach((c) => {
if (!colors.includes(c)) {
colors.push(c);
}
});
} else if (node instanceof Track) {
let style = node.getStyle();
if (
style &&
style['gpx_style:color'] &&
!colors.includes(style['gpx_style:color'])
) {
colors.push(style['gpx_style:color']);
if (style) {
if (style['gpx_style:color'] && !colors.includes(style['gpx_style:color'])) {
colors.push(style['gpx_style:color']);
}
}
if (colors.length === 0) {
let defaultColor = $gpxColors.get(item.getFileId());
if (defaultColor) {
colors.push(defaultColor);
let layer = gpxLayers.getLayer(item.getFileId());
if (layer) {
colors.push(layer.layerColor);
}
}
}
}
return colors;
nodeColors = colors;
});
let symbolKey = $derived(node instanceof Waypoint ? getSymbolKey(node.sym) : undefined);
@@ -164,7 +175,7 @@
let file = fileStateCollection.getFile(item.getFileId());
if (layer && file) {
let waypoint = file.wpt[item.getWaypointIndex()];
if (waypoint && !waypoint._data.hidden) {
if (waypoint) {
waypointPopup?.setItem({
item: waypoint,
fileId: item.getFileId(),

View File

@@ -48,7 +48,7 @@
language = 'en';
}
map.init(language, hash, geocoder, geolocate);
map.init(PUBLIC_MAPBOX_TOKEN, language, hash, geocoder, geolocate);
});
onDestroy(() => {

View File

@@ -16,8 +16,7 @@
</script>
<Button
size="sm"
class="justify-start {className}"
class="p-1 has-[>svg]:px-2 h-8 justify-start {className}"
variant="outline"
onclick={() => {
navigator.clipboard.writeText(

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import { gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers';
import { DistanceMarkers } from '$lib/components/map/gpx-layer/distance-markers';
import { StartEndMarkers } from '$lib/components/map/gpx-layer/start-end-markers';
@@ -9,10 +9,13 @@
let distanceMarkers: DistanceMarkers;
let startEndMarkers: StartEndMarkers;
map.onLoad((map_) => {
onMount(() => {
gpxLayers.init();
startEndMarkers = new StartEndMarkers();
distanceMarkers = new DistanceMarkers();
});
map.onLoad((map_) => {
createPopups(map_);
});

View File

@@ -1,13 +1,11 @@
<script lang="ts">
import type { TrackPoint } from 'gpx';
import { Button } from '$lib/components/ui/button';
import CopyCoordinates from '$lib/components/map/gpx-layer/CopyCoordinates.svelte';
import * as Card from '$lib/components/ui/card';
import WithUnits from '$lib/components/WithUnits.svelte';
import { Compass, Earth, Mountain, Timer } from '@lucide/svelte';
import { Compass, Mountain, Timer } from '@lucide/svelte';
import { i18n } from '$lib/i18n.svelte';
import type { PopupItem } from '$lib/components/map/map-popup';
import { map } from '$lib/components/map/map';
let { trackpoint }: { trackpoint: PopupItem<TrackPoint> } = $props();
</script>
@@ -37,17 +35,5 @@
onCopy={() => trackpoint.hide?.()}
class="mt-0.5"
/>
{#if trackpoint.fileId === undefined}
<Button
size="sm"
variant="outline"
class="justify-start"
href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.getLatitude().toFixed(5)}/${trackpoint.item.getLongitude().toFixed(5)}`}
target="_blank"
>
<Earth size="14" />
{i18n._('menu.edit_osm')}
</Button>
{/if}
</Card.Content>
</Card.Root>

View File

@@ -13,8 +13,6 @@
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
import { fileActions } from '$lib/logic/file-actions';
import type { PopupItem } from '$lib/components/map/map-popup';
import { selection } from '$lib/logic/selection';
import { ListFileItem } from '$lib/components/file-list/file-list';
let {
waypoint,
@@ -22,9 +20,6 @@
waypoint: PopupItem<Waypoint>;
} = $props();
let selected = $derived(
waypoint.fileId ? $selection.hasAnyChildren(new ListFileItem(waypoint.fileId)) : false
);
let symbolKey = $derived(waypoint ? getSymbolKey(waypoint.item.sym) : undefined);
function sanitize(text: string | undefined): string {
@@ -86,7 +81,7 @@
</ScrollArea>
<div class="mt-2 flex flex-col gap-1">
<CopyCoordinates coordinates={waypoint.item.attributes} />
{#if $currentTool === Tool.WAYPOINT && selected}
{#if $currentTool === Tool.WAYPOINT}
<Button
class="p-1 has-[>svg]:px-2 h-8"
variant="outline"

View File

@@ -3,12 +3,19 @@ import { gpxStatistics } from '$lib/logic/statistics';
import { getConvertedDistanceToKilometers } from '$lib/units';
import type { GeoJSONSource } from 'mapbox-gl';
import { get } from 'svelte/store';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { map } from '$lib/components/map/map';
import { allHidden } from '$lib/logic/hidden';
const { distanceMarkers, distanceUnits } = settings;
const levels = [100, 50, 25, 10, 5, 1];
const stops = [
[100, 0],
[50, 7],
[25, 8, 10],
[10, 10],
[5, 11],
[1, 13],
];
export class DistanceMarkers {
updateBinded: () => void = this.update.bind(this);
@@ -43,33 +50,22 @@ export class DistanceMarkers {
data: this.getDistanceMarkersGeoJSON(),
});
}
if (!map_.getLayer('distance-markers')) {
map_.addLayer(
{
id: 'distance-markers',
stops.forEach(([d, minzoom, maxzoom]) => {
if (!map_.getLayer(`distance-markers-${d}`)) {
map_.addLayer({
id: `distance-markers-${d}`,
type: 'symbol',
source: 'distance-markers',
filter: [
'match',
['get', 'level'],
100,
['>=', ['zoom'], 0],
50,
['>=', ['zoom'], 7],
25,
[
'any',
['all', ['>=', ['zoom'], 8], ['<=', ['zoom'], 9]],
['>=', ['zoom'], 11],
],
10,
['>=', ['zoom'], 10],
5,
['>=', ['zoom'], 11],
1,
['>=', ['zoom'], 13],
false,
],
filter:
d === 5
? [
'any',
['==', ['get', 'level'], 5],
['==', ['get', 'level'], 25],
]
: ['==', ['get', 'level'], d],
minzoom: minzoom,
maxzoom: maxzoom ?? 24,
layout: {
'text-field': ['get', 'distance'],
'text-size': 14,
@@ -80,14 +76,17 @@ export class DistanceMarkers {
'text-halo-width': 2,
'text-halo-color': 'white',
},
},
ANCHOR_LAYER_KEY.distanceMarkers
);
}
});
} else {
map_.moveLayer(`distance-markers-${d}`);
}
});
} else {
if (map_.getLayer('distance-markers')) {
map_.removeLayer('distance-markers');
}
stops.forEach(([d]) => {
if (map_.getLayer(`distance-markers-${d}`)) {
map_.removeLayer(`distance-markers-${d}`);
}
});
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
@@ -102,26 +101,35 @@ export class DistanceMarkers {
getDistanceMarkersGeoJSON(): GeoJSON.FeatureCollection {
let statistics = get(gpxStatistics);
let features: GeoJSON.Feature[] = [];
let features = [];
let currentTargetDistance = 1;
statistics.forEachTrackPoint((trkpt, dist) => {
if (dist >= getConvertedDistanceToKilometers(currentTargetDistance)) {
for (let i = 0; i < statistics.local.distance.total.length; i++) {
if (
statistics.local.distance.total[i] >=
getConvertedDistanceToKilometers(currentTargetDistance)
) {
let distance = currentTargetDistance.toFixed(0);
let level = levels.find((level) => currentTargetDistance % level === 0) || 1;
let [level, minzoom] = stops.find(([d]) => currentTargetDistance % d === 0) ?? [
0, 0,
];
features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [trkpt.getLongitude(), trkpt.getLatitude()],
coordinates: [
statistics.local.points[i].getLongitude(),
statistics.local.points[i].getLatitude(),
],
},
properties: {
distance,
level,
minzoom,
},
} as GeoJSON.Feature);
currentTargetDistance += 1;
}
});
}
return {
type: 'FeatureCollection',

View File

@@ -1,6 +1,6 @@
import { get, type Readable } from 'svelte/store';
import mapboxgl, { type FilterSpecification } from 'mapbox-gl';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import mapboxgl from 'mapbox-gl';
import { map } from '$lib/components/map/map';
import { waypointPopup, trackpointPopup } from './gpx-layer-popup';
import {
ListTrackSegmentItem,
@@ -22,7 +22,6 @@ import { fileActionManager } from '$lib/logic/file-action-manager';
import { fileActions } from '$lib/logic/file-actions';
import { splitAs } from '$lib/components/toolbar/tools/scissors/scissors';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { gpxColors } from '$lib/components/map/gpx-layer/gpx-layers';
const colors = [
'#ff0000',
@@ -44,49 +43,26 @@ for (let color of colors) {
}
// Get the color with the least amount of uses
function getColor(fileId: string) {
function getColor() {
let color = colors.reduce((a, b) => (colorCount[a] <= colorCount[b] ? a : b));
colorCount[color]++;
gpxColors.update((colors) => {
colors.set(fileId, color);
return colors;
});
return color;
}
function replaceColor(fileId: string, oldColor: string, newColor: string) {
if (colorCount.hasOwnProperty(oldColor)) {
colorCount[oldColor]--;
}
colorCount[newColor]++;
gpxColors.update((colors) => {
colors.set(fileId, newColor);
return colors;
});
}
function removeColor(fileId: string, color: string) {
function decrementColor(color: string) {
if (colorCount.hasOwnProperty(color)) {
colorCount[color]--;
}
gpxColors.update((colors) => {
colors.delete(fileId);
return colors;
});
}
export function getSvgForSymbol(symbol?: string | undefined, layerColor?: string | undefined) {
function getMarkerForSymbol(symbol: string | undefined, layerColor: string) {
let symbolSvg = symbol ? symbols[symbol]?.iconSvg : undefined;
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
${
layerColor
? Square.replace('width="24"', 'width="12"')
.replace('height="24"', 'height="12"')
.replace('stroke="currentColor"', 'stroke="SteelBlue"')
.replace('stroke-width="2"', 'stroke-width="1.5" x="9.6" y="0.4"')
.replace('fill="none"', `fill="${layerColor}"`)
: ''
}
${Square.replace('width="24"', 'width="12"')
.replace('height="24"', 'height="12"')
.replace('stroke="currentColor"', 'stroke="SteelBlue"')
.replace('stroke-width="2"', 'stroke-width="1.5" x="9.6" y="0.4"')
.replace('fill="none"', `fill="${layerColor}"`)}
${MapPin.replace('width="24"', '')
.replace('height="24"', '')
.replace('stroke="currentColor"', '')
@@ -111,10 +87,9 @@ export class GPXLayer {
fileId: string;
file: Readable<GPXFileWithStatistics | undefined>;
layerColor: string;
markers: mapboxgl.Marker[] = [];
selected: boolean = false;
currentWaypointData: GeoJSON.FeatureCollection | null = null;
draggedWaypointIndex: number | null = null;
draggingStartingPosition: mapboxgl.Point = new mapboxgl.Point(0, 0);
draggable: boolean;
unsubscribe: Function[] = [];
updateBinded: () => void = this.update.bind(this);
@@ -123,25 +98,11 @@ export class GPXLayer {
layerOnMouseMoveBinded: (e: any) => void = this.layerOnMouseMove.bind(this);
layerOnClickBinded: (e: any) => void = this.layerOnClick.bind(this);
layerOnContextMenuBinded: (e: any) => void = this.layerOnContextMenu.bind(this);
waypointLayerOnMouseEnterBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnMouseEnter.bind(this);
waypointLayerOnMouseLeaveBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnMouseLeave.bind(this);
waypointLayerOnClickBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnClick.bind(this);
waypointLayerOnMouseDownBinded: (e: mapboxgl.MapMouseEvent) => void =
this.waypointLayerOnMouseDown.bind(this);
waypointLayerOnTouchStartBinded: (e: mapboxgl.MapTouchEvent) => void =
this.waypointLayerOnTouchStart.bind(this);
waypointLayerOnMouseMoveBinded: (e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) => void =
this.waypointLayerOnMouseMove.bind(this);
waypointLayerOnMouseUpBinded: (e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) => void =
this.waypointLayerOnMouseUp.bind(this);
constructor(fileId: string, file: Readable<GPXFileWithStatistics | undefined>) {
this.fileId = fileId;
this.file = file;
this.layerColor = getColor(fileId);
this.layerColor = getColor();
this.unsubscribe.push(
map.subscribe(($map) => {
if ($map) {
@@ -164,6 +125,18 @@ export class GPXLayer {
})
);
this.unsubscribe.push(directionMarkers.subscribe(this.updateBinded));
this.unsubscribe.push(
currentTool.subscribe((tool) => {
if (tool === Tool.WAYPOINT && !this.draggable) {
this.draggable = true;
this.markers.forEach((marker) => marker.setDraggable(true));
} else if (tool !== Tool.WAYPOINT && this.draggable) {
this.draggable = false;
this.markers.forEach((marker) => marker.setDraggable(false));
}
})
);
this.draggable = get(currentTool) === Tool.WAYPOINT;
}
update() {
@@ -178,12 +151,10 @@ export class GPXLayer {
file._data.style.color &&
this.layerColor !== `#${file._data.style.color}`
) {
replaceColor(this.fileId, this.layerColor, `#${file._data.style.color}`);
decrementColor(this.layerColor);
this.layerColor = `#${file._data.style.color}`;
}
this.loadIcons();
try {
let source = _map.getSource(this.fileId) as mapboxgl.GeoJSONSource | undefined;
if (source) {
@@ -196,23 +167,20 @@ export class GPXLayer {
}
if (!_map.getLayer(this.fileId)) {
_map.addLayer(
{
id: this.fileId,
type: 'line',
source: this.fileId,
layout: {
'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': ['get', 'color'],
'line-width': ['get', 'width'],
'line-opacity': ['get', 'opacity'],
},
_map.addLayer({
id: this.fileId,
type: 'line',
source: this.fileId,
layout: {
'line-join': 'round',
'line-cap': 'round',
},
ANCHOR_LAYER_KEY.tracks
);
paint: {
'line-color': ['get', 'color'],
'line-width': ['get', 'width'],
'line-opacity': ['get', 'opacity'],
},
});
_map.on('click', this.fileId, this.layerOnClickBinded);
_map.on('contextmenu', this.fileId, this.layerOnContextMenuBinded);
@@ -221,59 +189,6 @@ export class GPXLayer {
_map.on('mousemove', this.fileId, this.layerOnMouseMoveBinded);
}
let waypointSource = _map.getSource(this.fileId + '-waypoints') as
| mapboxgl.GeoJSONSource
| undefined;
this.currentWaypointData = this.getWaypointsGeoJSON();
if (waypointSource) {
waypointSource.setData(this.currentWaypointData);
} else {
_map.addSource(this.fileId + '-waypoints', {
type: 'geojson',
data: this.currentWaypointData,
});
}
if (!_map.getLayer(this.fileId + '-waypoints')) {
_map.addLayer(
{
id: this.fileId + '-waypoints',
type: 'symbol',
source: this.fileId + '-waypoints',
layout: {
'icon-image': ['get', 'icon'],
'icon-size': 0.3,
'icon-anchor': 'bottom',
'icon-padding': 0,
'icon-allow-overlap': true,
},
},
ANCHOR_LAYER_KEY.waypoints
);
_map.on(
'mouseenter',
this.fileId + '-waypoints',
this.waypointLayerOnMouseEnterBinded
);
_map.on(
'mouseleave',
this.fileId + '-waypoints',
this.waypointLayerOnMouseLeaveBinded
);
_map.on('click', this.fileId + '-waypoints', this.waypointLayerOnClickBinded);
_map.on(
'mousedown',
this.fileId + '-waypoints',
this.waypointLayerOnMouseDownBinded
);
_map.on(
'touchstart',
this.fileId + '-waypoints',
this.waypointLayerOnTouchStartBinded
);
}
if (get(directionMarkers)) {
if (!_map.getLayer(this.fileId + '-direction')) {
_map.addLayer(
@@ -298,7 +213,7 @@ export class GPXLayer {
'text-halo-color': 'white',
},
},
ANCHOR_LAYER_KEY.directionMarkers
_map.getLayer('distance-markers-100') ? 'distance-markers-100' : undefined
);
}
} else {
@@ -307,40 +222,151 @@ export class GPXLayer {
}
}
let visibleTrackSegmentIds: string[] = [];
let visibleItems: [number, number][] = [];
file.forEachSegment((segment, trackIndex, segmentIndex) => {
if (!segment._data.hidden) {
visibleTrackSegmentIds.push(`${trackIndex}-${segmentIndex}`);
}
});
const segmentFilter: FilterSpecification = [
'in',
['get', 'trackSegmentId'],
['literal', visibleTrackSegmentIds],
];
_map.setFilter(this.fileId, segmentFilter, { validate: false });
if (_map.getLayer(this.fileId + '-direction')) {
_map.setFilter(this.fileId + '-direction', segmentFilter, { validate: false });
}
let visibleWaypoints: number[] = [];
file.wpt.forEach((waypoint, waypointIndex) => {
if (!waypoint._data.hidden) {
visibleWaypoints.push(waypointIndex);
visibleItems.push([trackIndex, segmentIndex]);
}
});
_map.setFilter(
this.fileId + '-waypoints',
['in', ['get', 'waypointIndex'], ['literal', visibleWaypoints]],
this.fileId,
[
'any',
...visibleItems.map(([trackIndex, segmentIndex]) => [
'all',
['==', 'trackIndex', trackIndex],
['==', 'segmentIndex', segmentIndex],
]),
],
{ validate: false }
);
if (_map.getLayer(this.fileId + '-direction')) {
_map.setFilter(
this.fileId + '-direction',
[
'any',
...visibleItems.map(([trackIndex, segmentIndex]) => [
'all',
['==', 'trackIndex', trackIndex],
['==', 'segmentIndex', segmentIndex],
]),
],
{ validate: false }
);
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
return;
}
let markerIndex = 0;
if (get(selection).hasAnyChildren(new ListFileItem(this.fileId))) {
file.wpt.forEach((waypoint) => {
// Update markers
let symbolKey = getSymbolKey(waypoint.sym);
if (markerIndex < this.markers.length) {
this.markers[markerIndex].getElement().innerHTML = getMarkerForSymbol(
symbolKey,
this.layerColor
);
this.markers[markerIndex].setLngLat(waypoint.getCoordinates());
Object.defineProperty(this.markers[markerIndex], '_waypoint', {
value: waypoint,
writable: true,
});
} else {
let element = document.createElement('div');
element.classList.add('w-8', 'h-8', 'drop-shadow-xl');
element.innerHTML = getMarkerForSymbol(symbolKey, this.layerColor);
let marker = new mapboxgl.Marker({
draggable: this.draggable,
element,
anchor: 'bottom',
}).setLngLat(waypoint.getCoordinates());
Object.defineProperty(marker, '_waypoint', { value: waypoint, writable: true });
let dragEndTimestamp = 0;
marker.getElement().addEventListener('mousemove', (e) => {
if (marker._isDragging) {
return;
}
waypointPopup?.setItem({ item: marker._waypoint, fileId: this.fileId });
e.stopPropagation();
});
marker.getElement().addEventListener('click', (e) => {
if (dragEndTimestamp && Date.now() - dragEndTimestamp < 1000) {
return;
}
if (get(currentTool) === Tool.WAYPOINT && e.shiftKey) {
fileActions.deleteWaypoint(this.fileId, marker._waypoint._data.index);
e.stopPropagation();
return;
}
if (get(treeFileView)) {
if (
(e.ctrlKey || e.metaKey) &&
get(selection).hasAnyChildren(
new ListWaypointsItem(this.fileId),
false
)
) {
selection.addSelectItem(
new ListWaypointItem(this.fileId, marker._waypoint._data.index)
);
} else {
selection.selectItem(
new ListWaypointItem(this.fileId, marker._waypoint._data.index)
);
}
} else if (get(currentTool) === Tool.WAYPOINT) {
selectedWaypoint.set([marker._waypoint, this.fileId]);
} else {
waypointPopup?.setItem({ item: marker._waypoint, fileId: this.fileId });
}
e.stopPropagation();
});
marker.on('dragstart', () => {
mapCursor.notify(MapCursorState.WAYPOINT_DRAGGING, true);
marker.getElement().style.cursor = 'grabbing';
waypointPopup?.hide();
});
marker.on('dragend', (e) => {
mapCursor.notify(MapCursorState.WAYPOINT_DRAGGING, false);
marker.getElement().style.cursor = '';
getElevation([marker._waypoint]).then((ele) => {
fileActionManager.applyToFile(this.fileId, (file) => {
let latLng = marker.getLngLat();
let wpt = file.wpt[marker._waypoint._data.index];
wpt.setCoordinates({
lat: latLng.lat,
lon: latLng.lng,
});
wpt.ele = ele[0];
});
});
dragEndTimestamp = Date.now();
});
this.markers.push(marker);
}
markerIndex++;
});
}
while (markerIndex < this.markers.length) {
// Remove extra markers
this.markers.pop()?.remove();
}
this.markers.forEach((marker) => {
if (!marker._waypoint._data.hidden) {
marker.addTo(_map);
} else {
marker.remove();
}
});
}
remove() {
@@ -353,24 +379,6 @@ export class GPXLayer {
_map.off('mousemove', this.fileId, this.layerOnMouseMoveBinded);
_map.off('style.import.load', this.updateBinded);
_map.off(
'mouseenter',
this.fileId + '-waypoints',
this.waypointLayerOnMouseEnterBinded
);
_map.off(
'mouseleave',
this.fileId + '-waypoints',
this.waypointLayerOnMouseLeaveBinded
);
_map.off('click', this.fileId + '-waypoints', this.waypointLayerOnClickBinded);
_map.off('mousedown', this.fileId + '-waypoints', this.waypointLayerOnMouseDownBinded);
_map.off(
'touchstart',
this.fileId + '-waypoints',
this.waypointLayerOnTouchStartBinded
);
if (_map.getLayer(this.fileId + '-direction')) {
_map.removeLayer(this.fileId + '-direction');
}
@@ -380,17 +388,15 @@ export class GPXLayer {
if (_map.getSource(this.fileId)) {
_map.removeSource(this.fileId);
}
if (_map.getLayer(this.fileId + '-waypoints')) {
_map.removeLayer(this.fileId + '-waypoints');
}
if (_map.getSource(this.fileId + '-waypoints')) {
_map.removeSource(this.fileId + '-waypoints');
}
}
this.markers.forEach((marker) => {
marker.remove();
});
this.unsubscribe.forEach((unsubscribe) => unsubscribe());
removeColor(this.fileId, this.layerColor);
decrementColor(this.layerColor);
}
moveToFront() {
@@ -399,13 +405,10 @@ export class GPXLayer {
return;
}
if (_map.getLayer(this.fileId)) {
_map.moveLayer(this.fileId, ANCHOR_LAYER_KEY.tracks);
}
if (_map.getLayer(this.fileId + '-waypoints')) {
_map.moveLayer(this.fileId + '-waypoints', ANCHOR_LAYER_KEY.waypoints);
_map.moveLayer(this.fileId);
}
if (_map.getLayer(this.fileId + '-direction')) {
_map.moveLayer(this.fileId + '-direction', ANCHOR_LAYER_KEY.directionMarkers);
_map.moveLayer(this.fileId + '-direction');
}
}
@@ -446,7 +449,7 @@ export class GPXLayer {
}
}
layerOnClick(e: mapboxgl.MapMouseEvent) {
layerOnClick(e: any) {
if (
get(currentTool) === Tool.ROUTING &&
get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints'])
@@ -454,8 +457,8 @@ export class GPXLayer {
return;
}
let trackIndex = e.features![0].properties!.trackIndex;
let segmentIndex = e.features![0].properties!.segmentIndex;
let trackIndex = e.features[0].properties.trackIndex;
let segmentIndex = e.features[0].properties.segmentIndex;
if (
get(currentTool) === Tool.SCISSORS &&
@@ -463,11 +466,6 @@ export class GPXLayer {
new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex)
)
) {
if (get(map)?.queryRenderedFeatures(e.point, { layers: ['split-controls'] }).length) {
// Clicked on split control, ignoring
return;
}
fileActions.split(get(splitAs), this.fileId, trackIndex, segmentIndex, {
lat: e.lngLat.lat,
lon: e.lngLat.lng,
@@ -504,160 +502,6 @@ export class GPXLayer {
}
}
waypointLayerOnMouseEnter(e: mapboxgl.MapMouseEvent) {
if (this.draggedWaypointIndex !== null) {
return;
}
let file = get(this.file)?.file;
if (!file) {
return;
}
let waypointIndex = e.features![0].properties!.waypointIndex;
let waypoint = file.wpt[waypointIndex];
waypointPopup?.setItem({ item: waypoint, fileId: this.fileId });
mapCursor.notify(MapCursorState.WAYPOINT_HOVER, true);
}
waypointLayerOnMouseLeave() {
mapCursor.notify(MapCursorState.WAYPOINT_HOVER, false);
}
waypointLayerOnClick(e: mapboxgl.MapMouseEvent) {
e.preventDefault();
let waypointIndex = e.features![0].properties!.waypointIndex;
let file = get(this.file)?.file;
if (!file) {
return;
}
let waypoint = file.wpt[waypointIndex];
if (get(currentTool) === Tool.WAYPOINT) {
if (this.selected) {
if (e.originalEvent.shiftKey) {
fileActions.deleteWaypoint(this.fileId, waypointIndex);
} else {
selection.selectItem(new ListWaypointItem(this.fileId, waypointIndex));
selectedWaypoint.set([waypoint, this.fileId]);
}
} else {
if (get(treeFileView)) {
selection.selectItem(new ListWaypointItem(this.fileId, waypointIndex));
} else {
selection.selectItem(new ListFileItem(this.fileId));
}
selectedWaypoint.set([waypoint, this.fileId]);
}
} else {
if (get(treeFileView)) {
if ((e.originalEvent.ctrlKey || e.originalEvent.metaKey) && this.selected) {
selection.addSelectItem(new ListWaypointItem(this.fileId, waypointIndex));
} else {
selection.selectItem(new ListWaypointItem(this.fileId, waypointIndex));
}
} else {
if (!this.selected) {
selection.selectItem(new ListFileItem(this.fileId));
}
waypointPopup?.setItem({ item: waypoint, fileId: this.fileId });
}
}
}
waypointLayerOnMouseDown(e: mapboxgl.MapMouseEvent) {
if (get(currentTool) !== Tool.WAYPOINT || !this.selected) {
return;
}
const _map = get(map);
if (!_map) {
return;
}
e.preventDefault();
this.draggedWaypointIndex = e.features![0].properties!.waypointIndex;
this.draggingStartingPosition = e.point;
waypointPopup?.hide();
_map.on('mousemove', this.waypointLayerOnMouseMoveBinded);
_map.once('mouseup', this.waypointLayerOnMouseUpBinded);
}
waypointLayerOnTouchStart(e: mapboxgl.MapTouchEvent) {
if (e.points.length !== 1 || get(currentTool) !== Tool.WAYPOINT || !this.selected) {
return;
}
const _map = get(map);
if (!_map) {
return;
}
this.draggedWaypointIndex = e.features![0].properties!.waypointIndex;
this.draggingStartingPosition = e.point;
waypointPopup?.hide();
e.preventDefault();
_map.on('touchmove', this.waypointLayerOnMouseMoveBinded);
_map.once('touchend', this.waypointLayerOnMouseUpBinded);
}
waypointLayerOnMouseMove(e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) {
if (this.draggedWaypointIndex === null || e.point.equals(this.draggingStartingPosition)) {
return;
}
mapCursor.notify(MapCursorState.WAYPOINT_DRAGGING, true);
(
this.currentWaypointData!.features[this.draggedWaypointIndex].geometry as GeoJSON.Point
).coordinates = [e.lngLat.lng, e.lngLat.lat];
let waypointSource = get(map)?.getSource(this.fileId + '-waypoints') as
| mapboxgl.GeoJSONSource
| undefined;
if (waypointSource) {
waypointSource.setData(this.currentWaypointData!);
}
}
waypointLayerOnMouseUp(e: mapboxgl.MapMouseEvent | mapboxgl.MapTouchEvent) {
mapCursor.notify(MapCursorState.WAYPOINT_DRAGGING, false);
get(map)?.off('mousemove', this.waypointLayerOnMouseMoveBinded);
get(map)?.off('touchmove', this.waypointLayerOnMouseMoveBinded);
if (this.draggedWaypointIndex === null) {
return;
}
if (e.point.equals(this.draggingStartingPosition)) {
this.draggedWaypointIndex = null;
return;
}
getElevation([
{
lat: e.lngLat.lat,
lon: e.lngLat.lng,
},
]).then((ele) => {
if (this.draggedWaypointIndex === null) {
return;
}
fileActionManager.applyToFile(this.fileId, (file) => {
let wpt = file.wpt[this.draggedWaypointIndex!];
wpt.setCoordinates({
lat: e.lngLat.lat,
lon: e.lngLat.lng,
});
wpt.ele = ele[0];
});
this.draggedWaypointIndex = null;
});
}
getGeoJSON(): GeoJSON.FeatureCollection {
let file = get(this.file)?.file;
if (!file) {
@@ -695,7 +539,6 @@ export class GPXLayer {
}
feature.properties.trackIndex = trackIndex;
feature.properties.segmentIndex = segmentIndex;
feature.properties.trackSegmentId = `${trackIndex}-${segmentIndex}`;
segmentIndex++;
if (segmentIndex >= file.trk[trackIndex].trkseg.length) {
@@ -705,65 +548,4 @@ export class GPXLayer {
}
return data;
}
getWaypointsGeoJSON(): GeoJSON.FeatureCollection {
let file = get(this.file)?.file;
let data: GeoJSON.FeatureCollection = {
type: 'FeatureCollection',
features: [],
};
if (!file) {
return data;
}
file.wpt.forEach((waypoint, index) => {
data.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [waypoint.getLongitude(), waypoint.getLatitude()],
},
properties: {
fileId: this.fileId,
waypointIndex: index,
icon: `waypoint-${getSymbolKey(waypoint.sym) ?? 'default'}-${this.layerColor}`,
},
});
});
return data;
}
loadIcons() {
const _map = get(map);
let file = get(this.file)?.file;
if (!_map || !file) {
return;
}
let symbols = new Set<string | undefined>();
file.wpt.forEach((waypoint) => {
symbols.add(getSymbolKey(waypoint.sym));
});
symbols.forEach((symbol) => {
const iconId = `waypoint-${symbol ?? 'default'}-${this.layerColor}`;
if (!_map.hasImage(iconId)) {
let icon = new Image(100, 100);
icon.onload = () => {
if (!_map.hasImage(iconId)) {
_map.addImage(iconId, icon);
}
};
// Lucide icons are SVG files with a 24x24 viewBox
// Create a new SVG with a 32x32 viewBox and center the icon in a circle
icon.src =
'data:image/svg+xml,' +
encodeURIComponent(getSvgForSymbol(symbol, this.layerColor));
}
});
}
}

View File

@@ -1,5 +1,4 @@
import { GPXFileStateCollectionObserver } from '$lib/logic/file-state';
import { writable } from 'svelte/store';
import { GPXLayer } from './gpx-layer';
export class GPXLayerCollection {
@@ -43,4 +42,3 @@ export class GPXLayerCollection {
}
export const gpxLayers = new GPXLayerCollection();
export const gpxColors = writable(new Map<string, string>());

View File

@@ -34,20 +34,13 @@ export class StartEndMarkers {
if (!map_) return;
const tool = get(currentTool);
const statistics = get(gpxStatistics);
const slicedStatistics = get(slicedGPXStatistics);
const statistics = get(slicedGPXStatistics)?.[0] ?? get(gpxStatistics);
const hidden = get(allHidden);
if (statistics.global.length > 0 && tool !== Tool.ROUTING && !hidden) {
this.start
.setLngLat(
statistics.getTrackPoint(slicedStatistics?.[1] ?? 0)!.trkpt.getCoordinates()
)
.addTo(map_);
if (statistics.local.points.length > 0 && tool !== Tool.ROUTING && !hidden) {
this.start.setLngLat(statistics.local.points[0].getCoordinates()).addTo(map_);
this.end
.setLngLat(
statistics
.getTrackPoint(slicedStatistics?.[2] ?? statistics.global.length - 1)!
.trkpt.getCoordinates()
statistics.local.points[statistics.local.points.length - 1].getCoordinates()
)
.addTo(map_);
} else {

View File

@@ -101,7 +101,9 @@
acc: Record<string, ImportSpecification>,
imprt: ImportSpecification
) => {
if (!['basemap', 'overlays'].includes(imprt.id)) {
if (
!['basemap', 'overlays', 'glyphs-and-sprite'].includes(imprt.id)
) {
acc[imprt.id] = imprt;
}
return acc;

View File

@@ -13,7 +13,6 @@
overlays,
overlayTree,
overpassTree,
terrainSources,
} from '$lib/assets/layers';
import { getLayers, isSelected, toggle } from '$lib/components/map/layer-control/utils';
import { i18n } from '$lib/i18n.svelte';
@@ -32,7 +31,6 @@
currentOverpassQueries,
customLayers,
opacities,
terrainSource,
} = settings;
const { isLayerFromExtension, getLayerName } = extensionAPI;
@@ -56,7 +54,7 @@
}
$effect(() => {
if (open && $selectedBasemapTree && $currentBasemap) {
if ($selectedBasemapTree && $currentBasemap) {
if (!isSelected($selectedBasemapTree, $currentBasemap)) {
if (!isSelected($selectedBasemapTree, defaultBasemap)) {
$selectedBasemapTree = toggle($selectedBasemapTree, defaultBasemap);
@@ -67,7 +65,7 @@
});
$effect(() => {
if (open && $selectedOverlayTree) {
if ($selectedOverlayTree) {
untrack(() => {
if ($currentOverlays) {
let overlayLayers = getLayers($currentOverlays);
@@ -88,7 +86,7 @@
});
$effect(() => {
if (open && $selectedOverpassTree) {
if ($selectedOverpassTree) {
untrack(() => {
if ($currentOverpassQueries) {
let overlayLayers = getLayers($currentOverpassQueries);
@@ -162,7 +160,7 @@
type="single"
onValueChange={setOpacityFromSelection}
>
<Select.Trigger class="mr-1 w-full" size="sm">
<Select.Trigger class="h-8 mr-1 w-full">
{#if selectedOverlay}
{#if isSelected($selectedOverlayTree, selectedOverlay)}
{#if $isLayerFromExtension(selectedOverlay)}
@@ -235,23 +233,6 @@
</ScrollArea>
</Accordion.Content>
</Accordion.Item>
<Accordion.Item value="terrain-source">
<Accordion.Trigger>{i18n._('layers.terrain')}</Accordion.Trigger>
<Accordion.Content class="flex flex-col gap-3 overflow-visible">
<Select.Root bind:value={$terrainSource} type="single">
<Select.Trigger class="mr-1 w-full" size="sm">
{i18n._(`layers.label.${$terrainSource}`)}
</Select.Trigger>
<Select.Content class="h-fit max-h-[40dvh] overflow-y-auto">
{#each Object.keys(terrainSources) as id}
<Select.Item value={id}>
{i18n._(`layers.label.${id}`)}
</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
</ScrollArea>
</Sheet.Header>

View File

@@ -85,7 +85,7 @@
{:else if anySelectedLayer(node[id])}
<CollapsibleTreeNode {id}>
{#snippet trigger()}
<span>{i18n._(`layers.label.${id}`, id)}</span>
<span>{i18n._(`layers.label.${id}`)}</span>
{/snippet}
{#snippet content()}
<div class="ml-2">

View File

@@ -54,27 +54,28 @@
<Card.Root class="border-none shadow-md text-base p-2 max-w-[50dvw] gap-0">
<Card.Header class="p-0 gap-0">
<Card.Title class="text-md flex flex-row">
<div class="flex flex-col">
<p>{name}</p>
<div class="text-muted-foreground text-xs font-normal">
{poi.item.lat.toFixed(6)}&deg; {poi.item.lon.toFixed(6)}&deg;
<Card.Title class="text-md">
<div class="flex flex-row gap-3">
<div class="flex flex-col">
{name}
<div class="text-muted-foreground text-xs font-normal">
{poi.item.lat.toFixed(6)}&deg; {poi.item.lon.toFixed(6)}&deg;
</div>
</div>
<Button
class="ml-auto"
variant="outline"
size="icon"
href="https://www.openstreetmap.org/edit?editor=id&{poi.item.type ??
'node'}={poi.item.id}"
target="_blank"
>
<PencilLine size="16" />
</Button>
</div>
<Button
class="ml-auto"
variant="outline"
size="icon-sm"
href="https://www.openstreetmap.org/edit?editor=id&{poi.item.type ?? 'node'}={poi
.item.id}"
target="_blank"
>
<PencilLine size="16" />
</Button>
</Card.Title>
</Card.Header>
<Card.Content class="flex flex-col gap-1 p-0 text-sm whitespace-normal break-all">
<Card.Content class="flex flex-col p-0 text-sm mt-1 whitespace-normal break-all">
<ScrollArea class="flex flex-col max-h-[30dvh]">
{#if tags.image || tags['image:0']}
<div class="w-full rounded-md overflow-clip my-2 max-w-96 mx-auto">
@@ -99,14 +100,8 @@
{/each}
</div>
</ScrollArea>
<Button
size="sm"
class="mt-1 justify-start"
variant="outline"
disabled={$selection.size === 0}
onclick={addToFile}
>
<MapPin size="14" />
<Button class="mt-2" variant="outline" disabled={$selection.size === 0} onclick={addToFile}>
<MapPin size="16" />
{i18n._('toolbar.waypoint.add')}
</Button>
</Card.Content>

View File

@@ -8,7 +8,6 @@ import { map } from '$lib/components/map/map';
const { currentOverlays, previousOverlays, selectedOverlayTree } = settings;
export type CustomOverlay = {
extensionName: string;
id: string;
name: string;
tileUrls: string[];
@@ -47,16 +46,8 @@ export class ExtensionAPI {
}
addOrUpdateOverlay(overlay: CustomOverlay) {
if (
!overlay.extensionName ||
!overlay.id ||
!overlay.name ||
!overlay.tileUrls ||
overlay.tileUrls.length === 0
) {
throw new Error(
'Overlay must have an extensionName, id, name, and at least one tile URL.'
);
if (!overlay.id || !overlay.name || !overlay.tileUrls || overlay.tileUrls.length === 0) {
throw new Error('Overlay must have an id, name, and at least one tile URL.');
}
overlay.id = this.getOverlayId(overlay.id);
@@ -84,17 +75,10 @@ export class ExtensionAPI {
],
};
if (!overlayTree.overlays.hasOwnProperty(overlay.extensionName)) {
overlayTree.overlays[overlay.extensionName] = {};
}
overlayTree.overlays[overlay.extensionName][overlay.id] = true;
overlayTree.overlays.world[overlay.id] = true;
selectedOverlayTree.update((selected) => {
if (!selected.overlays.hasOwnProperty(overlay.extensionName)) {
selected.overlays[overlay.extensionName] = {};
}
selected.overlays[overlay.extensionName][overlay.id] = true;
selected.overlays.world[overlay.id] = true;
return selected;
});
@@ -110,10 +94,7 @@ export class ExtensionAPI {
}
currentOverlays.update((current) => {
if (!current.overlays.hasOwnProperty(overlay.extensionName)) {
current.overlays[overlay.extensionName] = {};
}
current.overlays[overlay.extensionName][overlay.id] = show;
current.overlays.world[overlay.id] = show;
return current;
});
}
@@ -152,29 +133,6 @@ export class ExtensionAPI {
});
}
updateOverlaysOrder(ids: string[]) {
ids = ids.map((id) => this.getOverlayId(id));
selectedOverlayTree.update((selected) => {
let isSelected: Record<string, boolean> = {};
ids.forEach((id) => {
const overlay = get(this._overlays).get(id);
if (
overlay &&
selected.overlays.hasOwnProperty(overlay.extensionName) &&
selected.overlays[overlay.extensionName].hasOwnProperty(id)
) {
isSelected[id] = selected.overlays[overlay.extensionName][id];
delete selected.overlays[overlay.extensionName][id];
}
});
Object.entries(isSelected).forEach(([id, value]) => {
const overlay = get(this._overlays).get(id)!;
selected.overlays[overlay.extensionName][id] = value;
});
return selected;
});
}
isLayerFromExtension = derived(this._overlays, ($overlays) => {
return (id: string) => $overlays.has(id);
});

View File

@@ -6,7 +6,6 @@ import { overpassQueryData } from '$lib/assets/layers';
import { MapPopup } from '$lib/components/map/map-popup';
import { settings } from '$lib/logic/settings';
import { db } from '$lib/db';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const { currentOverpassQueries } = settings;
@@ -86,28 +85,23 @@ export class OverpassLayer {
}
if (!this.map.getLayer('overpass')) {
this.map.addLayer(
{
id: 'overpass',
type: 'symbol',
source: 'overpass',
layout: {
'icon-image': ['get', 'icon'],
'icon-size': 0.25,
'icon-padding': 0,
'icon-allow-overlap': ['step', ['zoom'], false, 14, true],
},
this.map.addLayer({
id: 'overpass',
type: 'symbol',
source: 'overpass',
layout: {
'icon-image': ['get', 'icon'],
'icon-size': 0.25,
'icon-padding': 0,
'icon-allow-overlap': ['step', ['zoom'], false, 14, true],
},
ANCHOR_LAYER_KEY.overpass
);
});
this.map.on('mouseenter', 'overpass', this.onHoverBinded);
this.map.on('click', 'overpass', this.onHoverBinded);
}
this.map.setFilter('overpass', ['in', 'query', ...getCurrentQueries()], {
validate: false,
});
this.map.setFilter('overpass', ['in', 'query', ...getCurrentQueries()]);
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
}
@@ -289,12 +283,10 @@ function getQuery(query: string) {
}
}
function getQueryItem(tags: Record<string, string | string[]>) {
let arrayEntry = Object.entries(tags).find((entry): entry is [string, string[]] =>
Array.isArray(entry[1])
);
function getQueryItem(tags: Record<string, string | boolean | string[]>) {
let arrayEntry = Object.values(tags).find((value) => Array.isArray(value));
if (arrayEntry !== undefined) {
return arrayEntry[1]
return arrayEntry
.map(
(val) =>
`nwr${Object.entries(tags)
@@ -317,7 +309,7 @@ function belongsToQuery(element: any, query: string) {
}
}
function belongsToQueryItem(element: any, tags: Record<string, string | string[]>) {
function belongsToQueryItem(element: any, tags: Record<string, string | boolean | string[]>) {
return Object.entries(tags).every(([tag, value]) =>
Array.isArray(value) ? value.includes(element.tags[tag]) : element.tags[tag] === value
);

View File

@@ -3,16 +3,8 @@ import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
import { get, writable, type Writable } from 'svelte/store';
import { settings } from '$lib/logic/settings';
import { tick } from 'svelte';
import { terrainSources } from '$lib/assets/layers';
const {
treeFileView,
elevationProfile,
bottomPanelSize,
rightPanelSize,
distanceUnits,
terrainSource,
} = settings;
const { treeFileView, elevationProfile, bottomPanelSize, rightPanelSize, distanceUnits } = settings;
let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = {
maxZoom: 15,
@@ -20,28 +12,6 @@ let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = {
easing: () => 1,
};
const emptySource: mapboxgl.GeoJSONSourceSpecification = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
};
export const ANCHOR_LAYER_KEY = {
mapillary: 'mapillary-end',
tracks: 'tracks-end',
directionMarkers: 'direction-markers-end',
distanceMarkers: 'distance-markers-end',
interactions: 'interactions-end',
overpass: 'overpass-end',
waypoints: 'waypoints-end',
};
const anchorLayers: mapboxgl.LayerSpecification[] = Object.values(ANCHOR_LAYER_KEY).map((id) => ({
id: id,
type: 'symbol',
source: 'empty-source',
}));
export class MapboxGLMap {
private _map: Writable<mapboxgl.Map | null> = writable(null);
private _onLoadCallbacks: ((map: mapboxgl.Map) => void)[] = [];
@@ -51,16 +21,31 @@ export class MapboxGLMap {
return this._map.subscribe(run, invalidate);
}
init(language: string, hash: boolean, geocoder: boolean, geolocate: boolean) {
init(
accessToken: string,
language: string,
hash: boolean,
geocoder: boolean,
geolocate: boolean
) {
const map = new mapboxgl.Map({
container: 'map',
style: {
version: 8,
sources: {
'empty-source': emptySource,
},
layers: anchorLayers,
sources: {},
layers: [],
imports: [
{
id: 'glyphs-and-sprite', // make Mapbox glyphs and sprite available to other styles
url: '',
data: {
version: 8,
sources: {},
layers: [],
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
sprite: `https://api.mapbox.com/styles/v1/mapbox/outdoors-v12/sprite?access_token=${accessToken}`,
},
},
{
id: 'basemap',
url: '',
@@ -68,6 +53,11 @@ export class MapboxGLMap {
{
id: 'overlays',
url: '',
data: {
version: 8,
sources: {},
layers: [],
},
},
],
},
@@ -144,26 +134,39 @@ export class MapboxGLMap {
});
map.addControl(scaleControl);
map.on('style.load', () => {
map.addSource('mapbox-dem', {
type: 'raster-dem',
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14,
});
if (map.getPitch() > 0) {
map.setTerrain({
source: 'mapbox-dem',
exaggeration: 1,
});
}
map.setFog({
color: 'rgb(186, 210, 235)',
'high-color': 'rgb(36, 92, 223)',
'horizon-blend': 0.1,
'space-color': 'rgb(156, 240, 255)',
});
map.on('pitch', this.setTerrain.bind(this));
this.setTerrain();
});
map.on('style.import.load', () => {
const basemap = map.getStyle().imports?.find((imprt) => imprt.id === 'basemap');
if (basemap && basemap.data && basemap.data.glyphs) {
map.setGlyphsUrl(basemap.data.glyphs);
}
map.on('pitch', () => {
if (map.getPitch() > 0) {
map.setTerrain({
source: 'mapbox-dem',
exaggeration: 1,
});
} else {
map.setTerrain(null);
}
});
});
map.on('load', () => {
this._map.set(map); // only set the store after the map has loaded
window._map = map; // entry point for extensions
this.resize();
this.setTerrain();
scaleControl.setUnit(get(distanceUnits));
this._onLoadCallbacks.forEach((callback) => callback(map));
@@ -179,7 +182,6 @@ export class MapboxGLMap {
scaleControl.setUnit(units);
})
);
this._unsubscribes.push(terrainSource.subscribe(() => this.setTerrain()));
}
onLoad(callback: (map: mapboxgl.Map) => void) {
@@ -220,29 +222,6 @@ export class MapboxGLMap {
}
}
}
setTerrain() {
const map = get(this._map);
if (map) {
const source = get(terrainSource);
try {
if (!map.getSource(source)) {
map.addSource(source, terrainSources[source]);
}
if (map.getPitch() > 0) {
map.setTerrain({
source: source,
exaggeration: 1,
});
} else {
map.setTerrain(null);
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
return;
}
}
}
}
export const map = new MapboxGLMap();

View File

@@ -2,7 +2,6 @@ import mapboxgl, { type LayerSpecification, type VectorSourceSpecification } fro
import { Viewer, type ViewerBearingEvent } from 'mapillary-js/dist/mapillary.module';
import 'mapillary-js/dist/mapillary.css';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const mapillarySource: VectorSourceSpecification = {
type: 'vector',
@@ -100,10 +99,10 @@ export class MapillaryLayer {
this.map.addSource('mapillary', mapillarySource);
}
if (!this.map.getLayer('mapillary-sequence')) {
this.map.addLayer(mapillarySequenceLayer, ANCHOR_LAYER_KEY.mapillary);
this.map.addLayer(mapillarySequenceLayer);
}
if (!this.map.getLayer('mapillary-image')) {
this.map.addLayer(mapillaryImageLayer, ANCHOR_LAYER_KEY.mapillary);
this.map.addLayer(mapillaryImageLayer);
}
this.map.on('style.load', this.addBinded);
this.map.on('mouseenter', 'mapillary-image', this.onMouseEnterBinded);

View File

@@ -15,7 +15,7 @@
import { onDestroy, onMount } from 'svelte';
import { getURLForLanguage } from '$lib/utils';
import { Trash2 } from '@lucide/svelte';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { map } from '$lib/components/map/map';
import type { GeoJSONSource } from 'mapbox-gl';
import { selection } from '$lib/logic/selection';
import { fileActions } from '$lib/logic/file-actions';
@@ -63,18 +63,15 @@
});
}
if (!$map.getLayer('rectangle')) {
$map.addLayer(
{
id: 'rectangle',
type: 'fill',
source: 'rectangle',
paint: {
'fill-color': 'SteelBlue',
'fill-opacity': 0.5,
},
$map.addLayer({
id: 'rectangle',
type: 'fill',
source: 'rectangle',
paint: {
'fill-color': 'SteelBlue',
'fill-opacity': 0.5,
},
ANCHOR_LAYER_KEY.interactions
);
});
}
}
}

View File

@@ -2,6 +2,7 @@
import { Button } from '$lib/components/ui/button';
import Help from '$lib/components/Help.svelte';
import { MountainSnow } from '@lucide/svelte';
import { map } from '$lib/components/map/map';
import { i18n } from '$lib/i18n.svelte';
import { getURLForLanguage } from '$lib/utils';
import { selection } from '$lib/logic/selection';
@@ -19,7 +20,11 @@
variant="outline"
class="whitespace-normal h-fit"
disabled={!validSelection}
onclick={() => fileActions.addElevationToSelection()}
onclick={() => {
if ($map) {
fileActions.addElevationToSelection($map);
}
}}
>
<MountainSnow size="16" class="shrink-0" />
{i18n._('toolbar.elevation.button')}

View File

@@ -38,7 +38,7 @@
let endTime: string | undefined = $state(undefined);
let movingTime: number | undefined = $state(undefined);
let speed: number | undefined = $state(undefined);
let artificial = $state(true);
let artificial = $state(false);
function toCalendarDate(date: Date): CalendarDate {
return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
@@ -346,7 +346,7 @@
let fileId = item.getFileId();
fileActionManager.applyToFile(fileId, (file) => {
if (item instanceof ListFileItem) {
if (artificial && !$gpxStatistics.global.time.moving) {
if (artificial || !$gpxStatistics.global.time.moving) {
file.createArtificialTimestamps(
getDate(startDate!, startTime!),
movingTime!
@@ -359,7 +359,7 @@
);
}
} else if (item instanceof ListTrackItem) {
if (artificial && !$gpxStatistics.global.time.moving) {
if (artificial || !$gpxStatistics.global.time.moving) {
file.createArtificialTimestamps(
getDate(startDate!, startTime!),
movingTime!,
@@ -374,7 +374,7 @@
);
}
} else if (item instanceof ListTrackSegmentItem) {
if (artificial && !$gpxStatistics.global.time.moving) {
if (artificial || !$gpxStatistics.global.time.moving) {
file.createArtificialTimestamps(
getDate(startDate!, startTime!),
movingTime!,

View File

@@ -10,7 +10,7 @@
import { onDestroy } from 'svelte';
import { getURLForLanguage } from '$lib/utils';
import { selection } from '$lib/logic/selection';
import { minTolerance, ReducedGPXLayerCollection, tolerance } from './utils.svelte';
import { minTolerance, ReducedGPXLayerCollection, tolerance } from './reduce.svelte';
let props: { class?: string } = $props();

View File

@@ -1,11 +1,11 @@
import { ListItem, ListTrackSegmentItem } from '$lib/components/file-list/file-list';
import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { map } from '$lib/components/map/map';
import { fileActions } from '$lib/logic/file-actions';
import { GPXFileStateCollectionObserver, type GPXFileState } from '$lib/logic/file-state';
import { selection } from '$lib/logic/selection';
import { ramerDouglasPeucker, TrackPoint, type SimplifiedTrackPoint } from 'gpx';
import type { GeoJSONSource } from 'mapbox-gl';
import { get, writable } from 'svelte/store';
import { get, writable, type Writable } from 'svelte/store';
export const minTolerance = 0.1;
@@ -28,15 +28,17 @@ export class ReducedGPXLayer {
update() {
const file = this._fileState.file;
if (!file) {
const stats = this._fileState.statistics;
if (!file || !stats) {
return;
}
file.forEachSegment((segment, trackIndex, segmentIndex) => {
let segmentItem = new ListTrackSegmentItem(file._data.id, trackIndex, segmentIndex);
let statistics = stats.getStatisticsFor(segmentItem);
this._updateSimplified(segmentItem.getFullId(), [
segmentItem,
segment.trkpt.length,
ramerDouglasPeucker(segment.trkpt, minTolerance),
statistics.local.points.length,
ramerDouglasPeucker(statistics.local.points, minTolerance),
]);
});
}
@@ -144,18 +146,17 @@ export class ReducedGPXLayerCollection {
});
}
if (!map_.getLayer('simplified')) {
map_.addLayer(
{
id: 'simplified',
type: 'line',
source: 'simplified',
paint: {
'line-color': 'white',
'line-width': 3,
},
map_.addLayer({
id: 'simplified',
type: 'line',
source: 'simplified',
paint: {
'line-color': 'white',
'line-width': 3,
},
ANCHOR_LAYER_KEY.interactions
);
});
} else {
map_.moveLayer('simplified');
}
}

View File

@@ -163,7 +163,7 @@
{i18n._('toolbar.routing.activity')}
</span>
<Select.Root type="single" bind:value={$routingProfile}>
<Select.Trigger class="grow" size="sm">
<Select.Trigger class="h-8 grow">
{i18n._(`toolbar.routing.activities.${$routingProfile}`)}
</Select.Trigger>
<Select.Content>
@@ -195,7 +195,7 @@
disabled={!validSelection}
onclick={fileActions.reverseSelection}
>
<ArrowRightLeft class="size-3" />{i18n._('toolbar.routing.reverse.button')}
<ArrowRightLeft size="12" />{i18n._('toolbar.routing.reverse.button')}
</ButtonWithTooltip>
<ButtonWithTooltip
label={i18n._('toolbar.routing.route_back_to_start.tooltip')}
@@ -231,7 +231,7 @@
}
}}
>
<House class="size-3" />{i18n._('toolbar.routing.route_back_to_start.button')}
<House size="12" />{i18n._('toolbar.routing.route_back_to_start.button')}
</ButtonWithTooltip>
<ButtonWithTooltip
label={i18n._('toolbar.routing.round_trip.tooltip')}
@@ -240,7 +240,7 @@
disabled={!validSelection}
onclick={fileActions.createRoundTripForSelection}
>
<Repeat class="size-3" />{i18n._('toolbar.routing.round_trip.button')}
<Repeat size="12" />{i18n._('toolbar.routing.round_trip.button')}
</ButtonWithTooltip>
</div>
<div class="w-full flex flex-row gap-2 items-end justify-between">

View File

@@ -793,25 +793,24 @@ export class RoutingControls {
replacingDistance +=
distance(response[i - 1].getCoordinates(), response[i].getCoordinates()) / 1000;
}
let startAnchorStats = stats.getTrackPoint(anchors[0].point._data.index)!;
let endAnchorStats = stats.getTrackPoint(
anchors[anchors.length - 1].point._data.index
)!;
let replacedDistance =
endAnchorStats.distance.moving - startAnchorStats.distance.moving;
stats.local.distance.moving[anchors[anchors.length - 1].point._data.index] -
stats.local.distance.moving[anchors[0].point._data.index];
let newDistance = stats.global.distance.moving + replacingDistance - replacedDistance;
let newTime = (newDistance / stats.global.speed.moving) * 3600;
let remainingTime =
stats.global.time.moving -
(endAnchorStats.time.moving - startAnchorStats.time.moving);
(stats.local.time.moving[anchors[anchors.length - 1].point._data.index] -
stats.local.time.moving[anchors[0].point._data.index]);
let replacingTime = newTime - remainingTime;
if (replacingTime <= 0) {
// Fallback to simple time difference
replacingTime = endAnchorStats.time.total - startAnchorStats.time.total;
replacingTime =
stats.local.time.total[anchors[anchors.length - 1].point._data.index] -
stats.local.time.total[anchors[0].point._data.index];
}
speed = (replacingDistance / replacingTime) * 3600;
@@ -821,7 +820,9 @@ export class RoutingControls {
let endIndex = anchors[anchors.length - 1].point._data.index;
startTime = new Date(
(segment.trkpt[endIndex].time?.getTime() ?? 0) -
(replacingTime + endAnchorStats.time.total - endAnchorStats.time.moving) *
(replacingTime +
stats.local.time.total[endIndex] -
stats.local.time.moving[endIndex]) *
1000
);
}

View File

@@ -26,10 +26,12 @@
let validSelection = $derived(
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
$gpxStatistics.global.length > 0
$gpxStatistics.local.points.length > 0
);
let maxSliderValue = $derived(
validSelection && $gpxStatistics.global.length > 0 ? $gpxStatistics.global.length - 1 : 1
validSelection && $gpxStatistics.local.points.length > 0
? $gpxStatistics.local.points.length - 1
: 1
);
let sliderValues = $derived([0, maxSliderValue]);
let canCrop = $derived(sliderValues[0] != 0 || sliderValues[1] != maxSliderValue);
@@ -43,7 +45,7 @@
function updateSlicedGPXStatistics() {
if (validSelection && canCrop) {
$slicedGPXStatistics = [
get(gpxStatistics).sliced(sliderValues[0], sliderValues[1]),
get(gpxStatistics).slice(sliderValues[0], sliderValues[1]),
sliderValues[0],
sliderValues[1],
];
@@ -105,7 +107,7 @@
{i18n._('toolbar.scissors.split_as')}
</span>
<Select.Root bind:value={$splitAs} type="single">
<Select.Trigger class="w-fit grow" size="sm">
<Select.Trigger class="h-8 w-fit grow">
{i18n._('gpx.' + $splitAs)}
</Select.Trigger>
<Select.Content>

View File

@@ -1,3 +1,5 @@
import { TrackPoint, TrackSegment } from 'gpx';
import mapboxgl from 'mapbox-gl';
import { ListTrackSegmentItem } from '$lib/components/file-list/file-list';
import { currentTool, Tool } from '$lib/components/toolbar/tools';
import { splitAs } from '$lib/components/toolbar/tools/scissors/scissors';
@@ -7,42 +9,20 @@ import { gpxStatistics } from '$lib/logic/statistics';
import { get } from 'svelte/store';
import { fileStateCollection } from '$lib/logic/file-state';
import { fileActions } from '$lib/logic/file-actions';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
export class SplitControls {
active: boolean = false;
map: mapboxgl.Map;
controls: ControlWithMarker[] = [];
shownControls: ControlWithMarker[] = [];
unsubscribes: Function[] = [];
layerOnMouseEnterBinded: (e: any) => void = this.layerOnMouseEnter.bind(this);
layerOnMouseLeaveBinded: () => void = this.layerOnMouseLeave.bind(this);
layerOnClickBinded: (e: any) => void = this.layerOnClick.bind(this);
toggleControlsForZoomLevelAndBoundsBinded: () => void =
this.toggleControlsForZoomLevelAndBounds.bind(this);
constructor(map: mapboxgl.Map) {
this.map = map;
if (!this.map.hasImage('split-control')) {
let icon = new Image(100, 100);
icon.onload = () => {
if (!this.map.hasImage('split-control')) {
this.map.addImage('split-control', icon);
}
};
// Lucide icons are SVG files with a 24x24 viewBox
// Create a new SVG with a 32x32 viewBox and center the icon in a circle
icon.src =
'data:image/svg+xml,' +
encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
<circle cx="20" cy="20" r="20" fill="white" />
<g transform="translate(8 8)">
${Scissors.replace('stroke="currentColor"', 'stroke="black"')}
</g>
</svg>
`);
}
this.unsubscribes.push(gpxStatistics.subscribe(this.addIfNeeded.bind(this)));
this.unsubscribes.push(currentTool.subscribe(this.addIfNeeded.bind(this)));
this.unsubscribes.push(selection.subscribe(this.addIfNeeded.bind(this)));
@@ -51,18 +31,29 @@ export class SplitControls {
addIfNeeded() {
let scissors = get(currentTool) === Tool.SCISSORS;
if (!scissors) {
this.remove();
if (this.active) {
this.remove();
}
return;
}
this.updateControls();
if (this.active) {
this.updateControls();
} else {
this.add();
}
}
add() {
this.active = true;
this.map.on('zoom', this.toggleControlsForZoomLevelAndBoundsBinded);
this.map.on('move', this.toggleControlsForZoomLevelAndBoundsBinded);
}
updateControls() {
let data: GeoJSON.FeatureCollection = {
type: 'FeatureCollection',
features: [],
};
// Update the markers when the files change
let controlIndex = 0;
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = fileStateCollection.getFile(fileId);
@@ -73,23 +64,30 @@ export class SplitControls {
new ListTrackSegmentItem(fileId, trackIndex, segmentIndex)
)
) {
for (let i = 1; i < segment.trkpt.length - 1; i++) {
let point = segment.trkpt[i];
for (let point of segment.trkpt.slice(1, -1)) {
// Update the existing controls (could be improved by matching the existing controls with the new ones?)
if (point._data.anchor) {
data.features.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [point.getLongitude(), point.getLatitude()],
},
properties: {
fileId: fileId,
trackIndex: trackIndex,
segmentIndex: segmentIndex,
pointIndex: i,
minZoom: point._data.zoom,
},
});
if (controlIndex < this.controls.length) {
this.controls[controlIndex].fileId = fileId;
this.controls[controlIndex].point = point;
this.controls[controlIndex].segment = segment;
this.controls[controlIndex].trackIndex = trackIndex;
this.controls[controlIndex].segmentIndex = segmentIndex;
this.controls[controlIndex].marker.setLngLat(
point.getCoordinates()
);
} else {
this.controls.push(
this.createControl(
point,
segment,
fileId,
trackIndex,
segmentIndex
)
);
}
controlIndex++;
}
}
}
@@ -97,78 +95,86 @@ export class SplitControls {
}
}, false);
try {
let source = this.map.getSource('split-controls') as mapboxgl.GeoJSONSource | undefined;
if (source) {
source.setData(data);
} else {
this.map.addSource('split-controls', {
type: 'geojson',
data: data,
});
}
if (!this.map.getLayer('split-controls')) {
this.map.addLayer(
{
id: 'split-controls',
type: 'symbol',
source: 'split-controls',
layout: {
'icon-image': 'split-control',
'icon-size': 0.25,
'icon-padding': 0,
},
filter: ['<=', ['get', 'minZoom'], ['zoom']],
},
ANCHOR_LAYER_KEY.interactions
);
this.map.on('mouseenter', 'split-controls', this.layerOnMouseEnterBinded);
this.map.on('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded);
this.map.on('click', 'split-controls', this.layerOnClickBinded);
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
while (controlIndex < this.controls.length) {
// Remove the extra controls
this.controls.pop()?.marker.remove();
}
this.toggleControlsForZoomLevelAndBounds();
}
remove() {
this.map.off('mouseenter', 'split-controls', this.layerOnMouseEnterBinded);
this.map.off('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded);
this.map.off('click', 'split-controls', this.layerOnClickBinded);
this.active = false;
try {
if (this.map.getLayer('split-controls')) {
this.map.removeLayer('split-controls');
}
if (this.map.getSource('split-controls')) {
this.map.removeSource('split-controls');
}
} catch (e) {
// No reliable way to check if the map is ready to remove sources and layers
for (let control of this.controls) {
control.marker.remove();
}
this.map.off('zoom', this.toggleControlsForZoomLevelAndBoundsBinded);
this.map.off('move', this.toggleControlsForZoomLevelAndBoundsBinded);
}
layerOnMouseEnter(e: any) {
mapCursor.notify(MapCursorState.SPLIT_CONTROL, true);
toggleControlsForZoomLevelAndBounds() {
// Show markers only if they are in the current zoom level and bounds
this.shownControls.splice(0, this.shownControls.length);
let southWest = this.map.unproject([0, this.map.getCanvas().height]);
let northEast = this.map.unproject([this.map.getCanvas().width, 0]);
let bounds = new mapboxgl.LngLatBounds(southWest, northEast);
let zoom = this.map.getZoom();
this.controls.forEach((control) => {
control.inZoom = control.point._data.zoom <= zoom;
if (control.inZoom && bounds.contains(control.marker.getLngLat())) {
control.marker.addTo(this.map);
this.shownControls.push(control);
} else {
control.marker.remove();
}
});
}
layerOnMouseLeave() {
mapCursor.notify(MapCursorState.SPLIT_CONTROL, false);
}
createControl(
point: TrackPoint,
segment: TrackSegment,
fileId: string,
trackIndex: number,
segmentIndex: number
): ControlWithMarker {
let element = document.createElement('div');
element.className = `h-6 w-6 p-0.5 rounded-full bg-white border-2 border-black cursor-pointer`;
element.innerHTML = Scissors.replace('width="24"', '')
.replace('height="24"', '')
.replace('stroke="currentColor"', 'stroke="black"');
layerOnClick(e: mapboxgl.MapMouseEvent) {
let coordinates = (e.features![0].geometry as GeoJSON.Point).coordinates;
fileActions.split(
get(splitAs),
e.features![0].properties!.fileId,
e.features![0].properties!.trackIndex,
e.features![0].properties!.segmentIndex,
{ lon: coordinates[0], lat: coordinates[1] },
e.features![0].properties!.pointIndex
);
let marker = new mapboxgl.Marker({
draggable: true,
className: 'z-10',
element,
}).setLngLat(point.getCoordinates());
let control = {
point,
segment,
fileId,
trackIndex,
segmentIndex,
marker,
inZoom: false,
};
marker.getElement().addEventListener('click', (e) => {
e.stopPropagation();
fileActions.split(
get(splitAs),
control.fileId,
control.trackIndex,
control.segmentIndex,
control.point.getCoordinates(),
control.point._data.index
);
});
return control;
}
destroy() {
@@ -176,3 +182,16 @@ export class SplitControls {
this.unsubscribes.forEach((unsubscribe) => unsubscribe());
}
}
type Control = {
segment: TrackSegment;
fileId: string;
trackIndex: number;
segmentIndex: number;
point: TrackPoint;
};
type ControlWithMarker = Control & {
marker: mapboxgl.Marker;
inZoom: boolean;
};

View File

@@ -16,8 +16,6 @@
import { fileActions } from '$lib/logic/file-actions';
import { map } from '$lib/components/map/map';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import mapboxgl from 'mapbox-gl';
import { getSvgForSymbol } from '$lib/components/map/gpx-layer/gpx-layer';
let props: {
class?: string;
@@ -41,21 +39,6 @@
})
);
let marker: mapboxgl.Marker | null = null;
function reset() {
if ($selectedWaypoint) {
selectedWaypoint.reset();
} else {
name = '';
description = '';
link = '';
sym = '';
longitude = 0;
latitude = 0;
}
}
$effect(() => {
if ($selectedWaypoint) {
const wpt = $selectedWaypoint[0];
@@ -71,7 +54,14 @@
latitude = parseFloat(wpt.getLatitude().toFixed(6));
});
} else {
untrack(reset);
untrack(() => {
name = '';
description = '';
link = '';
sym = '';
longitude = 0;
latitude = 0;
});
}
});
@@ -95,14 +85,14 @@
desc: description.length > 0 ? description : undefined,
cmt: description.length > 0 ? description : undefined,
link: link.length > 0 ? { attributes: { href: link } } : undefined,
sym: sym.length > 0 ? sym : undefined,
sym: sym,
},
selectedWaypoint.wpt && selectedWaypoint.fileId
? new ListWaypointItem(selectedWaypoint.fileId, selectedWaypoint.wpt._data.index)
: undefined
);
reset();
selectedWaypoint.reset();
}
function setCoordinates(e: any) {
@@ -110,37 +100,6 @@
longitude = e.lngLat.lng.toFixed(6);
}
$effect(() => {
if ($selectedWaypoint) {
if (marker) {
marker.remove();
marker = null;
}
} else if (latitude != 0 || longitude != 0) {
if ($map) {
if (marker) {
marker.setLngLat([longitude, latitude]).getElement().innerHTML =
getSvgForSymbol(symbolKey);
} else {
let element = document.createElement('div');
element.classList.add('w-8', 'h-8');
element.innerHTML = getSvgForSymbol(symbolKey);
marker = new mapboxgl.Marker({
element,
anchor: 'bottom',
})
.setLngLat([longitude, latitude])
.addTo($map);
}
}
} else {
if (marker) {
marker.remove();
marker = null;
}
}
});
onMount(() => {
if ($map) {
$map.on('click', setCoordinates);
@@ -153,10 +112,6 @@
$map.off('click', setCoordinates);
mapCursor.notify(MapCursorState.TOOL_WITH_CROSSHAIR, false);
}
if (marker) {
marker.remove();
marker = null;
}
});
</script>
@@ -174,27 +129,19 @@
bind:value={description}
id="description"
disabled={!canCreate && !$selectedWaypoint}
class="min-h-8 h-8 py-1 px-3 text-sm"
/>
<Label for="symbol">{i18n._('toolbar.waypoint.icon')}</Label>
<Select.Root bind:value={sym} type="single">
<Select.Trigger
id="symbol"
size="sm"
class="w-full"
class="w-full h-8"
disabled={!canCreate && !$selectedWaypoint}
>
<span class="flex flex-row gap-1.5 items-center">
{#if symbolKey}
{#if symbols[symbolKey].icon}
{@const Component = symbols[symbolKey].icon}
<Component size="14" />
{/if}
{i18n._(`gpx.symbol.${symbolKey}`)}
{:else}
{sym}
{/if}
</span>
{#if symbolKey}
{i18n._(`gpx.symbol.${symbolKey}`)}
{:else}
{sym}
{/if}
</Select.Trigger>
<Select.Content class="max-h-60 overflow-y-scroll">
{#each sortedSymbols as [key, symbol]}
@@ -202,7 +149,7 @@
<span>
{#if symbol.icon}
{@const Component = symbol.icon}
<Component size="14" class="inline-block align-sub" />
<Component size="14" class="inline-block align-sub mr-0.5" />
{:else}
<span class="w-4 inline-block"></span>
{/if}
@@ -263,7 +210,7 @@
{i18n._('toolbar.waypoint.create')}
{/if}
</Button>
<Button variant="outline" size="icon" onclick={reset}>
<Button variant="outline" size="icon" onclick={() => selectedWaypoint.reset()}>
<CircleX size="16" />
</Button>
</div>

View File

@@ -29,11 +29,11 @@ Soubory můžete také přetáhnout přímo ze souborového systému do okna.
Vytvořit kopii aktuálně vybraných souborů.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Smazat
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
Smazat aktuálně vybrané soubory.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Smazat vše
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
Smazat všechny soubory.

View File

@@ -1,5 +1,5 @@
Mapbox ist das Unternehmen, das einige der schönen Karten auf dieser Website zur Verfügung stellt.
Sie entwickeln auch die <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">Karten-Engine</a> welche **gpx.studio** unterstützt.
Wir sind äußerst glücklich und dankbar, Teil ihres <a href="https://mapbox.com/community" target="_blank">Community</a> Programms zu sein, das gemeinnützige Organisationen, Bildungseinrichtungen und Organisationen mit positivem Einfluss unterstützt.
Wir sind äusserst glücklich und dankbar, Teil ihres <a href="https://mapbox.com/community" target="_blank">Community</a> Programms zu sein, das gemeinnützige Organisationen, Bildungseinrichtungen und Organisationen mit positivem Einfluss unterstützt.
Diese Partnerschaft ermöglicht es **gpx.studio**, von den Mapbox-Tools zu ermäßigten Preisen zu profitieren, was erheblich zur finanziellen Tragfähigkeit des Projekts beiträgt und es uns ermöglicht, die bestmögliche Benutzererfahrung zu bieten.

View File

@@ -1,5 +1,5 @@
---
title: Opciones de vista
title: View options
---
<script lang="ts">

View File

@@ -29,13 +29,13 @@ Beste era batez, fitxategiak zuzenean arrastatu eta jaregin ditzakezu zure fitxa
Sortu hautatutako fitxategien kopia bat.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Ezabatu
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
Ezabatu hautatutako fitxategiak.
Delete the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Ezabatu guztiak
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
Ezabatu fitxategi guztiak.
Delete all files.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Esportatu...

View File

@@ -50,7 +50,7 @@ Facendo clic destro su una scheda file, è possibile accedere alle stesse azioni
Come accennato nella [sezione opzioni di visualizzazione](./menu/view), è possibile passare a un layout ad albero per l'elenco dei file.
Questo layout è ideale per gestire un gran numero di file aperti, organizzandoli in una lista verticale sul lato destro della mappa.
Inoltre, la vista ad albero dei file consente d'ispezionare [tracce, segmenti e punti di interesse](./gpx) all'interno dei file attraverso sezioni espandibili.
Inoltre, la vista ad albero dei file consente di ispezionare [tracce, segmenti e punti di interesse](./gpx) all'interno dei file attraverso sezioni espandibili.
Puoi anche applicare [modifiche](./menu/edit) e [strumenti](./toolbar) agli elementi interni del file.
Inoltre, è possibile trascinare e rilasciare gli elementi per riordinarli, o spostarli nella gerarchia o anche in un altro file.
@@ -78,7 +78,7 @@ Quando si passa sopra il profilo di elevazione, un suggerimento mostrerà le sta
Per ottenere le statistiche per una sezione specifica del profilo di elevazione, è possibile trascinare un rettangolo di selezione sul profilo.
Fare clic sul profilo per resettare la selezione.
È inoltre possibile utilizzare la rotellina del mouse per ingrandire e rimpicciolire sul profilo di elevazione, e spostarsi a sinistra e a destra trascinando il profilo tenendo premuto il tasto <kbd>Maiuscolo</kbd>.
È inoltre possibile utilizzare la rotellina del mouse per ingrandire e rimpicciolire sul profilo di elevazione, e spostarsi a sinistra e a destra trascinando il profilo tenendo premuto il tasto <kbd>Maiusc</kbd>.
<div class="h-48 w-full">
<ElevationProfile

View File

@@ -21,7 +21,7 @@ Queste sono organizzate in una struttura gerarchica, con le tracce stesse al liv
- Una **traccia** è composta da una sequenza di segmenti scollegati.
Inoltre, può contenere metadati come un **nome**, una **descrizione**, e **proprietà di visualizzazione**.
- Un **segmento** è una sequenza di punti GPS che formano un percorso continuo.
- Un **punto GPS** è una posizione con una latitudine, una longitudine, ed eventualmente una marcatura temporale e un'altitudine.
- Un **punto GPS** è una posizione con una latitudine, una longitudine, ed eventualmente un timestamp e un'altitudine.
Alcuni dispositivi memorizzano anche informazioni aggiuntive come frequenza cardiaca, cadenza, temperatura e potenza.
Nella maggior parte dei casi, i file GPX contengono una singola traccia con un singolo segmento.

View File

@@ -5,9 +5,9 @@
## <HeartHandshake size="18" class="inline-block align-baseline" /> Aiuta a mantenere il sito gratuito (e senza pubblicità)
Ogni volta che aggiungi o sposti i punti GPS, i nostri server calcolano il percorso migliore sulla rete stradale.
Utilizziamo anche le API di <a href="https://mapbox.com" target="_blank">Mapbox</a> per visualizzare mappe stupende, recuperare i dati altimetrici e consentire la ricerca di luoghi.
Utilizziamo anche le API di <a href="https://mapbox.com" target="_blank">Mapbox</a> per visualizzare mappe gradevoli, recuperare i dati altimetrici e consentire la ricerca di luoghi.
Sfortunatamente, fare tutto ciò è costoso.
Sfortunatamente, questo è costoso.
Se ti piace utilizzare questo strumento e lo trovi utile, per favore considera di fare una piccola donazione per aiutare a mantenere il sito web gratuito e senza pubblicità.
Grazie mille per il vostro supporto! ❤️

View File

@@ -29,13 +29,13 @@ cÈ inoltre possibile trascinare i file direttamente dal file system del tuo Pc
Crea una copia dei file attualmente selezionati.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" />Elimina
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
Elimina i file attualmente selezionati.
Delete the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" />Cancella tutto
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
Elimina tutti i file.
Delete all files.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Esporta...

View File

@@ -14,7 +14,7 @@ Deze handleiding zal je door alle componenten en gereedschappen van de interface
<DocsImage src="getting-started/interface" alt="De gpx.studio interface." />
Zoals weergegeven in bovenstaande scherm, is de interface verdeeld in vier hoofddelen rond de kaart.
Voordat we in de details van elke sectie duiken, eerst een snel overzicht van de interface.
Voordat we in de details van elke sectie duiken, hebben we een snel overzicht van de interface.
## Menu

View File

@@ -83,7 +83,7 @@ Deze actie is alleen beschikbaar wanneer de verticale indeling van de bestandsli
### <ClipboardPaste size="16" class="inline-block" style="margin-bottom: 2px" /> Plakken
Plak de bestandsitems van het klembord naar het huidige hiërarchieniveau indien compatibel.
Plak de bestandsitems van het klembord naar het huidige hiërarchie niveau indien compatibel.
<DocsNote>

View File

@@ -2,7 +2,7 @@
import { HeartHandshake } from '@lucide/svelte';
</script>
## <HeartHandshake size="18" class="inline-block align-baseline" /> Hãy giúp duy trì trang web miễn phí (và không có quảng cáo)
## <HeartHandshake size="18" class="inline-block align-baseline" /> Help keep the website free (and ad-free)
Khi bạn thêm hoặc di chuyển các điểm định vị, máy chủ của chúng tôi sẽ tính toán đoạn đường tốt nhất trên mạng lưới giao thông.
Chúng tôi cũng sử dụng các API từ <a href="https://mapbox.com" target="_blank">Mapbox</a> để hiển thị đa dạng các bản đồ, lưu trữ các dữ liệu độ cao cũng như giúp bạn có thể tìm kiếm các địa điểm khác nhau.

View File

@@ -1,5 +1,5 @@
Mapbox là công ty cung cấp một số bản đồ đẹp trên trang web này.
Họ cũng phát triển <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">công cụ bản đồ</a> cung cấp sức mạnh cho **gpx.studio**.
Mapbox is the company that provides some of the beautiful maps on this website.
They also develop the <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">map engine</a> which powers **gpx.studio**.
Chúng tôi vô cùng may mắn và biết ơn khi được tham gia chương trình <a href="https://mapbox.com/community" target="_blank">Cộng đồng</a> của họ, chương trình hỗ trợ các tổ chức phi lợi nhuận, các tổ chức giáo dục và các tổ chức tạo ra tác động tích cực.
Sự hợp tác này cho phép **gpx.studio** được hưởng lợi từ các công cụ của Mapbox với giá ưu đãi, góp phần đáng kể vào tính khả thi về tài chính của dự án và giúp chúng tôi mang đến trải nghiệm người dùng tốt nhất có thể.
We are incredibly fortunate and grateful to be part of their <a href="https://mapbox.com/community" target="_blank">Community</a> program, which supports nonprofits, educational institutions, and positive impact organizations.
This partnership allows **gpx.studio** to benefit from Mapbox tools at discounted prices, greatly contributing to the financial viability of the project and enabling us to offer the best possible user experience.

View File

@@ -9,8 +9,8 @@ title: Edit actions
# { title }
Không giống như các thao tác trên tệp, các thao tác chỉnh sửa có thể thay đổi nội dung của các tệp hiện đang được chọn.
Hơn nữa, khi bố cục dạng cây của danh sách tệp được bật (xem [Tệp và thống kê](../files-and-stats)), chúng cũng có thể được áp dụng cho [đường đi, đoạn đường và điểm quan tâm](../gpx).
Unlike the file actions, the edit actions can potentially modify the content of the currently selected files.
Moreover, when the tree layout of the files list is enabled (see [Files and statistics](../files-and-stats)), they can also be applied to [tracks, segments, and points of interest](../gpx).
Therefore, we will refer to the elements that can be modified by these actions as _file items_.
Note that except for the undo and redo actions, the edit actions are also accessible through the context menu (right-click) of the file items.

View File

@@ -31,7 +31,7 @@ Create a copy of the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
.
Delete the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all

View File

@@ -29,13 +29,13 @@ title: 文件
创建当前选中文件的副本。
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> 删除
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
删除当前选中的文件。
Delete the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> 删除全部
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
删除全部文件。
Delete all files.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> 导出...

View File

@@ -14,7 +14,7 @@ class Locale {
private _isLoadingInitial = $state(true);
private _isLoading = $state(true);
private dictionary: Dictionary = $state({});
private _t = $derived((key: string, fallback?: string) => {
private _t = $derived((key: string) => {
const keys = key.split('.');
let value: string | Dictionary = this.dictionary;
@@ -22,7 +22,7 @@ class Locale {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
return fallback || key;
return key;
}
}

View File

@@ -66,8 +66,10 @@ export class BoundsManager {
finalizeFitBounds() {
if (
this._bounds.getSouth() >= this._bounds.getNorth() &&
this._bounds.getWest() >= this._bounds.getEast()
this._bounds.getSouth() === 90 &&
this._bounds.getWest() === 180 &&
this._bounds.getNorth() === -90 &&
this._bounds.getEast() === -180
) {
return;
}

View File

@@ -17,6 +17,7 @@ import {
import { i18n } from '$lib/i18n.svelte';
import { freeze, type WritableDraft } from 'immer';
import {
distance,
GPXFile,
parseGPX,
Track,
@@ -29,7 +30,7 @@ import {
} from 'gpx';
import { get } from 'svelte/store';
import { settings } from '$lib/logic/settings';
import { getClosestLinePoint, getClosestTrackSegments, getElevation } from '$lib/utils';
import { getClosestLinePoint, getElevation } from '$lib/utils';
import { gpxStatistics } from '$lib/logic/statistics';
import { boundsManager } from './bounds';
@@ -215,7 +216,7 @@ export const fileActions = {
reverseSelection: () => {
if (
!get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints']) ||
get(gpxStatistics).global.length <= 1
get(gpxStatistics).local.points?.length <= 1
) {
return;
}
@@ -345,20 +346,19 @@ export const fileActions = {
let startTime: Date | undefined = undefined;
if (speed !== undefined) {
if (
statistics.global.length > 0 &&
statistics.getTrackPoint(0)!.trkpt.time !== undefined
statistics.local.points.length > 0 &&
statistics.local.points[0].time !== undefined
) {
startTime = statistics.getTrackPoint(0)!.trkpt.time;
startTime = statistics.local.points[0].time;
} else {
for (let i = 0; i < statistics.global.length; i++) {
const point = statistics.getTrackPoint(i)!;
if (point.trkpt.time !== undefined) {
startTime = new Date(
point.trkpt.time.getTime() -
(1000 * 3600 * point.distance.total) / speed
);
break;
}
let index = statistics.local.points.findIndex(
(point) => point.time !== undefined
);
if (index !== -1 && statistics.local.points[index].time) {
startTime = new Date(
statistics.local.points[index].time.getTime() -
(1000 * 3600 * statistics.local.distance.total[index]) / speed
);
}
}
}
@@ -453,13 +453,34 @@ export const fileActions = {
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
if (level === ListLevel.FILE) {
let file = fileStateCollection.getFile(fileId);
let statistics = fileStateCollection.getStatistics(fileId);
if (file && statistics) {
if (file) {
if (file.trk.length > 1) {
let fileIds = getFileIds(file.trk.length);
let closest = file.wpt.map((wpt) =>
getClosestTrackSegments(file, statistics, wpt.getCoordinates())
);
let closest = file.wpt.map((wpt, wptIndex) => {
return {
wptIndex: wptIndex,
index: [0],
distance: Number.MAX_VALUE,
};
});
file.trk.forEach((track, index) => {
track.getSegments().forEach((segment) => {
segment.trkpt.forEach((point) => {
file.wpt.forEach((wpt, wptIndex) => {
let dist = distance(
point.getCoordinates(),
wpt.getCoordinates()
);
if (dist < closest[wptIndex].distance) {
closest[wptIndex].distance = dist;
closest[wptIndex].index = [index];
} else if (dist === closest[wptIndex].distance) {
closest[wptIndex].index.push(index);
}
});
});
});
});
file.trk.forEach((track, index) => {
let newFile = file.clone();
let tracks = track.trkseg.map((segment, segmentIndex) => {
@@ -474,11 +495,9 @@ export const fileActions = {
newFile.replaceWaypoints(
0,
file.wpt.length - 1,
file.wpt.filter((wpt, wptIndex) =>
closest[wptIndex].some(
([trackIndex, segmentIndex]) => trackIndex === index
)
)
closest
.filter((c) => c.index.includes(index))
.map((c) => file.wpt[c.wptIndex])
);
newFile._data.id = fileIds[index];
newFile.metadata.name =
@@ -487,9 +506,29 @@ export const fileActions = {
});
} else if (file.trk.length === 1) {
let fileIds = getFileIds(file.trk[0].trkseg.length);
let closest = file.wpt.map((wpt) =>
getClosestTrackSegments(file, statistics, wpt.getCoordinates())
);
let closest = file.wpt.map((wpt, wptIndex) => {
return {
wptIndex: wptIndex,
index: [0],
distance: Number.MAX_VALUE,
};
});
file.trk[0].trkseg.forEach((segment, index) => {
segment.trkpt.forEach((point) => {
file.wpt.forEach((wpt, wptIndex) => {
let dist = distance(
point.getCoordinates(),
wpt.getCoordinates()
);
if (dist < closest[wptIndex].distance) {
closest[wptIndex].distance = dist;
closest[wptIndex].index = [index];
} else if (dist === closest[wptIndex].distance) {
closest[wptIndex].index.push(index);
}
});
});
});
file.trk[0].trkseg.forEach((segment, index) => {
let newFile = file.clone();
newFile.replaceTrackSegments(0, 0, file.trk[0].trkseg.length - 1, [
@@ -498,11 +537,9 @@ export const fileActions = {
newFile.replaceWaypoints(
0,
file.wpt.length - 1,
file.wpt.filter((wpt, wptIndex) =>
closest[wptIndex].some(
([trackIndex, segmentIndex]) => segmentIndex === index
)
)
closest
.filter((c) => c.index.includes(index))
.map((c) => file.wpt[c.wptIndex])
);
newFile._data.id = fileIds[index];
newFile.metadata.name = `${file.trk[0].name ?? file.metadata.name} (${index + 1})`;
@@ -807,7 +844,7 @@ export const fileActions = {
});
});
},
addElevationToSelection: async () => {
addElevationToSelection: async (map: mapboxgl.Map) => {
if (get(selection).size === 0) {
return;
}

View File

@@ -4,12 +4,10 @@ import { get, writable, type Writable } from 'svelte/store';
export enum MapCursorState {
DEFAULT,
LAYER_HOVER,
TOOL_WITH_CROSSHAIR,
WAYPOINT_HOVER,
WAYPOINT_DRAGGING,
TRACKPOINT_DRAGGING,
TOOL_WITH_CROSSHAIR,
SCISSORS,
SPLIT_CONTROL,
MAPILLARY_HOVER,
STREET_VIEW_CROSSHAIR,
}
@@ -18,12 +16,10 @@ const scissorsCursor = `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/20
const cursorStyles = {
[MapCursorState.DEFAULT]: 'default',
[MapCursorState.LAYER_HOVER]: 'pointer',
[MapCursorState.WAYPOINT_HOVER]: 'pointer',
[MapCursorState.WAYPOINT_DRAGGING]: 'grabbing',
[MapCursorState.TRACKPOINT_DRAGGING]: 'grabbing',
[MapCursorState.TOOL_WITH_CROSSHAIR]: 'crosshair',
[MapCursorState.SCISSORS]: scissorsCursor,
[MapCursorState.SPLIT_CONTROL]: 'pointer',
[MapCursorState.MAPILLARY_HOVER]: 'pointer',
[MapCursorState.STREET_VIEW_CROSSHAIR]: 'crosshair',
};
@@ -34,8 +30,8 @@ export class MapCursor {
constructor() {
this._states = writable(new Set());
this._states.subscribe((states) => {
let state = Array.from(states.values()).reduce((max, value) => {
return value > max ? value : max;
let state = states.entries().reduce((max, entry) => {
return entry[0] > max ? entry[0] : max;
}, MapCursorState.DEFAULT);
let canvas = get(map)?.getCanvas();
if (canvas) {

View File

@@ -179,112 +179,6 @@ export class Selection {
}
}
updateFromKey(down: boolean, shift: boolean) {
let selected = get(this._selection).getSelected();
if (selected.length === 0) {
return;
}
let next: ListItem | undefined = undefined;
if (selected[0] instanceof ListFileItem) {
let order = get(settings.fileOrder);
let limitIndex: number | undefined = undefined;
selected.forEach((item) => {
let index = order.indexOf(item.getFileId());
if (
limitIndex === undefined ||
(down && index > limitIndex) ||
(!down && index < limitIndex)
) {
limitIndex = index;
}
});
if (limitIndex !== undefined) {
let nextIndex = down ? limitIndex + 1 : limitIndex - 1;
while (true) {
if (nextIndex < 0) {
nextIndex = order.length - 1;
} else if (nextIndex >= order.length) {
nextIndex = 0;
}
if (nextIndex === limitIndex) {
break;
}
next = new ListFileItem(order[nextIndex]);
if (!get(selection).has(next)) {
break;
}
nextIndex += down ? 1 : -1;
}
}
} else if (
selected[0] instanceof ListTrackItem &&
selected[selected.length - 1] instanceof ListTrackItem
) {
let fileId = selected[0].getFileId();
let file = fileStateCollection.getFile(fileId);
if (file) {
let numberOfTracks = file.trk.length;
let trackIndex = down
? selected[selected.length - 1].getTrackIndex()
: selected[0].getTrackIndex();
if (down && trackIndex < numberOfTracks - 1) {
next = new ListTrackItem(fileId, trackIndex + 1);
} else if (!down && trackIndex > 0) {
next = new ListTrackItem(fileId, trackIndex - 1);
}
}
} else if (
selected[0] instanceof ListTrackSegmentItem &&
selected[selected.length - 1] instanceof ListTrackSegmentItem
) {
let fileId = selected[0].getFileId();
let file = fileStateCollection.getFile(fileId);
if (file) {
let trackIndex = selected[0].getTrackIndex();
let numberOfSegments = file.trk[trackIndex].trkseg.length;
let segmentIndex = down
? selected[selected.length - 1].getSegmentIndex()
: selected[0].getSegmentIndex();
if (down && segmentIndex < numberOfSegments - 1) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1);
} else if (!down && segmentIndex > 0) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1);
}
}
} else if (
selected[0] instanceof ListWaypointItem &&
selected[selected.length - 1] instanceof ListWaypointItem
) {
let fileId = selected[0].getFileId();
let file = fileStateCollection.getFile(fileId);
if (file) {
let numberOfWaypoints = file.wpt.length;
let waypointIndex = down
? selected[selected.length - 1].getWaypointIndex()
: selected[0].getWaypointIndex();
if (down && waypointIndex < numberOfWaypoints - 1) {
next = new ListWaypointItem(fileId, waypointIndex + 1);
} else if (!down && waypointIndex > 0) {
next = new ListWaypointItem(fileId, waypointIndex - 1);
}
}
}
if (next && (!get(this._selection).has(next) || !shift)) {
if (shift) {
this.addSelectItem(next);
} else {
this.selectItem(next);
}
}
}
getOrderedSelection(reverse: boolean = false): ListItem[] {
let selected: ListItem[] = [];
this.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {

View File

@@ -8,7 +8,6 @@ import {
defaultOverlayTree,
defaultOverpassQueries,
defaultOverpassTree,
defaultTerrainSource,
type CustomLayer,
} from '$lib/assets/layers';
import { browser } from '$app/environment';
@@ -155,7 +154,6 @@ export const settings = {
customLayers: new Setting<Record<string, CustomLayer>>('customLayers', {}),
customBasemapOrder: new Setting<string[]>('customBasemapOrder', []),
customOverlayOrder: new Setting<string[]>('customOverlayOrder', []),
terrainSource: new Setting('terrainSource', defaultTerrainSource),
directionMarkers: new Setting('directionMarkers', false),
distanceMarkers: new Setting('distanceMarkers', false),
streetViewSource: new Setting('streetViewSource', 'mapillary'),

View File

@@ -1,5 +1,5 @@
import { ListItem, ListLevel } from '$lib/components/file-list/file-list';
import { GPXFile, GPXStatistics, GPXStatisticsGroup, type Track } from 'gpx';
import { GPXFile, GPXStatistics, type Track } from 'gpx';
export class GPXStatisticsTree {
level: ListLevel;
@@ -21,23 +21,23 @@ export class GPXStatisticsTree {
}
}
getStatisticsFor(item: ListItem): GPXStatisticsGroup {
let statistics = new GPXStatisticsGroup();
getStatisticsFor(item: ListItem): GPXStatistics {
let statistics = new GPXStatistics();
let id = item.getIdAtLevel(this.level);
if (id === undefined || id === 'waypoints') {
Object.keys(this.statistics).forEach((key) => {
if (this.statistics[key] instanceof GPXStatistics) {
statistics.add(this.statistics[key]);
statistics.mergeWith(this.statistics[key]);
} else {
statistics.add(this.statistics[key].getStatisticsFor(item));
statistics.mergeWith(this.statistics[key].getStatisticsFor(item));
}
});
} else {
let child = this.statistics[id];
if (child instanceof GPXStatistics) {
statistics.add(child);
statistics.mergeWith(child);
} else if (child !== undefined) {
statistics.add(child.getStatisticsFor(item));
statistics.mergeWith(child.getStatisticsFor(item));
}
}
return statistics;

View File

@@ -1,5 +1,5 @@
import { selection } from '$lib/logic/selection';
import { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
import { GPXStatistics } from 'gpx';
import { fileStateCollection, GPXFileState } from '$lib/logic/file-state';
import {
ListFileItem,
@@ -12,7 +12,7 @@ import { settings } from '$lib/logic/settings';
const { fileOrder } = settings;
export class SelectedGPXStatistics {
private _statistics: Writable<GPXStatisticsGroup>;
private _statistics: Writable<GPXStatistics>;
private _files: Map<
string,
{
@@ -22,21 +22,18 @@ export class SelectedGPXStatistics {
>;
constructor() {
this._statistics = writable(new GPXStatisticsGroup());
this._statistics = writable(new GPXStatistics());
this._files = new Map();
selection.subscribe(() => this.update());
fileOrder.subscribe(() => this.update());
}
subscribe(
run: (value: GPXStatisticsGroup) => void,
invalidate?: (value?: GPXStatisticsGroup) => void
) {
subscribe(run: (value: GPXStatistics) => void, invalidate?: (value?: GPXStatistics) => void) {
return this._statistics.subscribe(run, invalidate);
}
update() {
let statistics = new GPXStatisticsGroup();
let statistics = new GPXStatistics();
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let stats = fileStateCollection.getStatistics(fileId);
if (stats) {
@@ -46,7 +43,7 @@ export class SelectedGPXStatistics {
!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) ||
first
) {
statistics.add(stats.getStatisticsFor(item));
statistics.mergeWith(stats.getStatisticsFor(item));
first = false;
}
});
@@ -79,7 +76,7 @@ export class SelectedGPXStatistics {
export const gpxStatistics = new SelectedGPXStatistics();
export const slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined> =
export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> =
writable(undefined);
gpxStatistics.subscribe(() => {

View File

@@ -1,5 +1,5 @@
import fs from 'fs';
import { languages } from '../languages';
import { languages } from '$lib/languages';
function localizeManifest(manifestTemplateData: any, language: string) {
const localizedManifestFile = `static/${language}.manifest.webmanifest`;

View File

@@ -229,9 +229,6 @@ export function getConvertedVelocity(
}
}
export function getConvertedTemperature(
value: number,
targetTemperatureUnits = get(temperatureUnits)
) {
return targetTemperatureUnits === 'celsius' ? value : celsiusToFahrenheit(value);
export function getConvertedTemperature(value: number) {
return get(temperatureUnits) === 'celsius' ? value : celsiusToFahrenheit(value);
}

View File

@@ -2,13 +2,11 @@ import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { base } from '$app/paths';
import { languages } from '$lib/languages';
import { TrackPoint, Waypoint, type Coordinates, crossarcDistance, distance, GPXFile } from 'gpx';
import { TrackPoint, Waypoint, type Coordinates, crossarcDistance, distance } from 'gpx';
import mapboxgl from 'mapbox-gl';
import { pointToTile, pointToTileFraction } from '@mapbox/tilebelt';
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import PNGReader from 'png.js';
import type { GPXStatisticsTree } from '$lib/logic/statistics-tree';
import { ListTrackSegmentItem } from '$lib/components/file-list/file-list';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -49,59 +47,6 @@ export function getClosestLinePoint(
return closest;
}
export function getClosestTrackSegments(
file: GPXFile,
statistics: GPXStatisticsTree,
point: Coordinates
): [number, number][] {
let segmentBoundsDistances: [number, number, number][] = [];
file.forEachSegment((segment, trackIndex, segmentIndex) => {
let segmentStatistics = statistics.getStatisticsFor(
new ListTrackSegmentItem(file._data.id, trackIndex, segmentIndex)
);
let segmentBounds = segmentStatistics.global.bounds;
let northEast = segmentBounds.northEast;
let southWest = segmentBounds.southWest;
let bounds = new mapboxgl.LngLatBounds(southWest, northEast);
if (bounds.contains(point)) {
segmentBoundsDistances.push([0, trackIndex, segmentIndex]);
} else {
let northWest: Coordinates = { lat: northEast.lat, lon: southWest.lon };
let southEast: Coordinates = { lat: southWest.lat, lon: northEast.lon };
let distanceToBounds = Math.min(
crossarcDistance(northWest, northEast, point),
crossarcDistance(northEast, southEast, point),
crossarcDistance(southEast, southWest, point),
crossarcDistance(southWest, northWest, point)
);
segmentBoundsDistances.push([distanceToBounds, trackIndex, segmentIndex]);
}
});
segmentBoundsDistances.sort((a, b) => a[0] - b[0]);
let closest: { distance: number; indices: [number, number][] } = {
distance: Number.MAX_VALUE,
indices: [],
};
for (let s = 0; s < segmentBoundsDistances.length; s++) {
if (segmentBoundsDistances[s][0] > closest.distance) {
break;
}
const segment = file.getSegment(segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]);
segment.trkpt.forEach((pt) => {
let dist = distance(pt.getCoordinates(), point);
if (dist < closest.distance) {
closest.distance = dist;
closest.indices = [[segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]]];
} else if (dist === closest.distance) {
closest.indices.push([segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]]);
}
});
}
return closest.indices;
}
export function getElevation(
points: (TrackPoint | Waypoint | Coordinates)[],
ELEVATION_ZOOM: number = 13,

View File

@@ -79,8 +79,7 @@
"unhide": "Паказаць",
"center": "Center",
"open_in": "Адчыніць у",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Update layer"
},
"opacity": "Overlay opacity",
"terrain": "Terrain source",
"label": {
"basemaps": "Basemaps",
"overlays": "Overlays",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Shower",
"shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Railway Station",
"tram-stop": "Tram Stop",
"bus-stop": "Bus Stop",
"ferry": "Ferry",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferry"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Veure",
"center": "Centrar",
"open_in": "Obrir amb",
"copy_coordinates": "Copiar coordenades",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copiar coordenades"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Actualitza la capa"
},
"opacity": "Opacitat de la superposició",
"terrain": "Terrain source",
"label": {
"basemaps": "Mapes base",
"overlays": "Capes",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Aigua",
"shower": "Dutxa",
"shelter": "Refugi",
"cemetery": "Cemetery",
"motorized": "Cotxes i motos",
"fuel-station": "Gasolinera",
"parking": "Aparcament",
@@ -380,9 +375,7 @@
"railway-station": "Estació de tren",
"tram-stop": "Parada de tramvia",
"bus-stop": "Parada d'autobús",
"ferry": "Ferri",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferri"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contacte",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Zobrazit skryté",
"center": "Vycentrovat",
"open_in": "Otevřít v",
"copy_coordinates": "Zkopírovat souřadnice",
"edit_osm": "Upravit v OpenStreetMap"
"copy_coordinates": "Zkopírovat souřadnice"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Aktualizovat vrstvu"
},
"opacity": "Průhlednost překryvu",
"terrain": "Zdroj terénu",
"label": {
"basemaps": "Základní mapy",
"overlays": "Překrytí",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Vrstevnice",
"swisstopoHiking": "swisstopo Turistická",
"swisstopoHikingClosures": "swisstopo Turistické uzávěry",
@@ -356,7 +352,6 @@
"water": "Voda",
"shower": "Sprcha",
"shelter": "Přístřeší",
"cemetery": "Hřbitov",
"motorized": "Automobily a motocykly",
"fuel-station": "Čerpací stanice",
"parking": "Parkoviště",
@@ -380,9 +375,7 @@
"railway-station": "Železniční stanice",
"tram-stop": "Zastávka tramvaje",
"bus-stop": "Autobusová zastávka",
"ferry": "Trajekt",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Trajekt"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "Aplikace",
"contact": "Kontakt",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Unhide",
"center": "Center",
"open_in": "Open in",
"copy_coordinates": "Kopier koordinater",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Kopier koordinater"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Update layer"
},
"opacity": "Overlay opacity",
"terrain": "Terrain source",
"label": {
"basemaps": "Basemaps",
"overlays": "Overlays",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Shower",
"shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Railway Station",
"tram-stop": "Tram Stop",
"bus-stop": "Bus Stop",
"ferry": "Ferry",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferry"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Einblenden",
"center": "Zentrieren",
"open_in": "Öffnen in",
"copy_coordinates": "Koordinaten kopieren",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Koordinaten kopieren"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Layer aktualisieren"
},
"opacity": "Deckkraft der Überlagerung",
"terrain": "Terrain source",
"label": {
"basemaps": "Basiskarte",
"overlays": "Ebenen",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Neigung",
"swisstopoHiking": "swisstopo Wandern",
"swisstopoHikingClosures": "swisstopo Wanderungen Schließungen",
@@ -356,7 +352,6 @@
"water": "Trinkwasser",
"shower": "Dusche",
"shelter": "Unterstand",
"cemetery": "Cemetery",
"motorized": "Autos und Motorräder",
"fuel-station": "Tankstelle",
"parking": "Parken",
@@ -380,9 +375,7 @@
"railway-station": "Bahnhof",
"tram-stop": "Straßenbahnhaltestelle",
"bus-stop": "Bushaltestelle",
"ferry": "Fähre",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Fähre"
}
},
"chart": {
@@ -478,10 +471,11 @@
},
"homepage": {
"website": "Webseite",
"home": "Startseite",
"home": "Zuhause",
"app": "App",
"contact": "Kontakt",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Unhide",
"center": "Center",
"open_in": "Open in",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Update layer"
},
"opacity": "Overlay opacity",
"terrain": "Terrain source",
"label": {
"basemaps": "Basemaps",
"overlays": "Overlays",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Shower",
"shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Railway Station",
"tram-stop": "Tram Stop",
"bus-stop": "Bus Stop",
"ferry": "Ferry",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferry"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Unhide",
"center": "Center",
"open_in": "Open in",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Update layer"
},
"opacity": "Overlay opacity",
"terrain": "Terrain source",
"label": {
"basemaps": "Basemaps",
"overlays": "Overlays",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Shower",
"shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Railway Station",
"tram-stop": "Tram Stop",
"bus-stop": "Bus Stop",
"ferry": "Ferry",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferry"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -2,7 +2,7 @@
"metadata": {
"home_title": "el editor online de archivos GPX",
"app_title": "app",
"embed_title": " editor online de archivos GPX",
"embed_title": "El editor online de archivos GPX",
"help_title": "ayuda",
"404_title": "página no encontrada",
"description": "Mira, edita y crea archivos GPX online con planificación avanzada de rutas y herramientas de procesamiento de archivos, bonitos mapas y visualizaciones detalladas de datos."
@@ -36,7 +36,7 @@
"switch_basemap": "Cambiar al mapa base anterior",
"toggle_overlays": "Alternar capas",
"toggle_3d": "Alternar 3D",
"settings": "Configuración",
"settings": "Configuraciones",
"distance_units": "Unidades de distancia",
"metric": "Métrico",
"imperial": "Imperial",
@@ -79,8 +79,7 @@
"unhide": "Mostrar",
"center": "Centrar",
"open_in": "Abrir en",
"copy_coordinates": "Copiar coordenadas",
"edit_osm": "Editar en OpenStreetMap"
"copy_coordinates": "Copiar coordenadas"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Actualizar capa"
},
"opacity": "Opacidad de la capa superpuesta",
"terrain": "Origen del terreno",
"label": {
"basemaps": "Mapas base",
"overlays": "Capas",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "Gravel bikerouter.de",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Senderismo",
"swisstopoHikingClosures": "swisstopo Rutas Senderismo",
@@ -356,7 +352,6 @@
"water": "Agua",
"shower": "Ducha",
"shelter": "Refugio",
"cemetery": "Cementerio",
"motorized": "Coches y motos",
"fuel-station": "Gasolinera",
"parking": "Aparcamiento",
@@ -380,9 +375,7 @@
"railway-station": "Estación de tren",
"tram-stop": "Parada de tranvía",
"bus-stop": "Parada de autobús",
"ferry": "Ferri",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferri"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contacto",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -28,7 +28,7 @@
"undo": "Desegin",
"redo": "Berregin",
"delete": "Ezabatu",
"delete_all": "Ezabatu guztiak",
"delete_all": "Delete all",
"select_all": "Hautatu dena",
"view": "Ikusi",
"elevation_profile": "Altuera profila",
@@ -79,8 +79,7 @@
"unhide": "Erakutsi",
"center": "Erdiratu",
"open_in": "Ireki hemen",
"copy_coordinates": "Kopiatu koordenatuak",
"edit_osm": "Editatu OpenStreeMapen"
"copy_coordinates": "Kopiatu koordenatuak"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Eguneratu geruza"
},
"opacity": "Geruzaren opakutasuna",
"terrain": "Terrain source",
"label": {
"basemaps": "Oinarrizko mapak",
"overlays": "Geruzak",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Malda",
"swisstopoHiking": "swisstopo Mendi ibilaldiak",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Ura",
"shower": "Dutxa",
"shelter": "Babeslekua",
"cemetery": "Hilerria",
"motorized": "Kotxeak eta motorrak",
"fuel-station": "Gasolindegia",
"parking": "Aparkalekua",
@@ -380,9 +375,7 @@
"railway-station": "Tren geltokia",
"tram-stop": "Tranbia geltokia",
"bus-stop": "Autobus geltokia",
"ferry": "Ferria",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferria"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Kontaktua",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Näytä",
"center": "Keskitä",
"open_in": "Avaa",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Päivitä karttataso"
},
"opacity": "Peitetason läpinäkyvyys",
"terrain": "Terrain source",
"label": {
"basemaps": "Taustakartat",
"overlays": "Peitetasot",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Rinnekaltevuus",
"swisstopoHiking": "swisstopo Retkeilyreitit",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Shower",
"shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Rautatieasemat",
"tram-stop": "Raitiovaunupysäkit",
"bus-stop": "Linja-autopysäkit",
"ferry": "Lautat",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Lautat"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Yhteystiedot",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Afficher",
"center": "Centrer",
"open_in": "Ouvrir avec",
"copy_coordinates": "Copier les coordonnées",
"edit_osm": "Éditer dans OpenStreetMap"
"copy_coordinates": "Copier les coordonnées"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Mettre à jour la couche"
},
"opacity": "Opacité de la surcouche",
"terrain": "Source du relief",
"label": {
"basemaps": "Fonds de carte",
"overlays": "Surcouches",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Relief",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Pente",
"swisstopoHiking": "swisstopo Randonnée",
"swisstopoHikingClosures": "swisstopo Fermetures de randonnée",
@@ -353,10 +349,9 @@
"eat-and-drink": "Nourriture et boissons",
"amenities": "Commodités",
"toilets": "Toilettes",
"water": "Eau potable",
"water": "Cours d'eau",
"shower": "Douche",
"shelter": "Abri",
"cemetery": "Cimetière",
"motorized": "Voitures et motos",
"fuel-station": "Station-service",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Gare",
"tram-stop": "Arrêt de tram",
"bus-stop": "Arrêt de bus",
"ferry": "Ferry",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferry"
}
},
"chart": {
@@ -448,7 +441,7 @@
"convenience_store": "Épicerie",
"crossing": "Croisement",
"department_store": "Grand magasin",
"drinking_water": "Eau potable",
"drinking_water": "Cours d'eau",
"exit": "Sortie",
"lodge": "Refuge",
"lodging": "Hébergement",
@@ -473,7 +466,7 @@
"summit": "Sommet",
"telephone": "Téléphone",
"tunnel": "Tunnel",
"water_source": "Point d'eau"
"water_source": "Source d'eau"
}
},
"homepage": {
@@ -482,6 +475,7 @@
"app": "Application",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Unhide",
"center": "Center",
"open_in": "Open in",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Update layer"
},
"opacity": "Overlay opacity",
"terrain": "Terrain source",
"label": {
"basemaps": "Basemaps",
"overlays": "Overlays",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "גשם",
"shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Railway Station",
"tram-stop": "Tram Stop",
"bus-stop": "Bus Stop",
"ferry": "Ferry",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferry"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Felfedés ",
"center": "Középre ",
"open_in": "Megnyitás itt ",
"copy_coordinates": "Koordináták másolása",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Koordináták másolása"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Réteg feltöltése"
},
"opacity": "Átfedés átlátszósága",
"terrain": "Terrain source",
"label": {
"basemaps": "Alaptérkép",
"overlays": "Térkép rétegek",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "kerékpár és terepkerékpár út",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Lejtő",
"swisstopoHiking": "swisstopo Túra",
"swisstopoHikingClosures": "swisstopo túralezárások",
@@ -356,7 +352,6 @@
"water": "Víz",
"shower": "Zuhanyozó",
"shelter": "Menedék",
"cemetery": "Cemetery",
"motorized": "Autók és Motorok",
"fuel-station": "Benzinkút",
"parking": "Parkoló",
@@ -380,9 +375,7 @@
"railway-station": "Vasútállomás",
"tram-stop": "Villamos megálló",
"bus-stop": "Buszmegálló",
"ferry": "Komp",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Komp"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Kapcsolat",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Tampilkan",
"center": "Tengah",
"open_in": "Buka di",
"copy_coordinates": "Salin koordinat",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Salin koordinat"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Perbarui lapisan"
},
"opacity": "Opasitas Overlay",
"terrain": "Terrain source",
"label": {
"basemaps": "Peta dasar",
"overlays": "Overlay",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Kemiringan",
"swisstopoHiking": "swisstopo Pendakian",
"swisstopoHikingClosures": "Penutupan Jalur Pendakian swisstopo",
@@ -356,7 +352,6 @@
"water": "Air",
"shower": "Mandi",
"shelter": "Penampungan",
"cemetery": "Cemetery",
"motorized": "Mobil dan Motor",
"fuel-station": "Stasiun bahan bakar",
"parking": "Parkir",
@@ -380,9 +375,7 @@
"railway-station": "Stasiun kereta api",
"tram-stop": "Halt trem",
"bus-stop": "Pemberhentian Bus",
"ferry": "Feri",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Feri"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -28,14 +28,14 @@
"undo": "Annulla",
"redo": "Ripeti",
"delete": "Elimina",
"delete_all": "Cancella tutto",
"delete_all": "Delete all",
"select_all": "Seleziona tutto",
"view": "Visualizza",
"elevation_profile": "Profilo altimetrico",
"tree_file_view": "Vista ad albero",
"switch_basemap": "Passa alla mappa di base precedente",
"toggle_overlays": "Attiva / disattiva le sovrapposizioni",
"toggle_3d": "Attiva / disattiva 3D",
"toggle_overlays": "Attiva/disattiva le sovrapposizioni",
"toggle_3d": "Attiva/disattiva 3D",
"settings": "Impostazioni",
"distance_units": "Unità distanza",
"metric": "Metrico",
@@ -53,7 +53,7 @@
"street_view_source": "Sorgente della vista stradale",
"mapillary": "Mapillary",
"google": "Google",
"toggle_street_view": "Vista stradale",
"toggle_street_view": "Street View",
"layers": "Livelli della mappa...",
"distance_markers": "Indicatori di distanza",
"direction_markers": "Frecce direzionali",
@@ -79,15 +79,14 @@
"unhide": "Mostra",
"center": "Centra",
"open_in": "Apri con",
"copy_coordinates": "Copia le coordinate",
"edit_osm": "Modifica in OpenStreetMap"
"copy_coordinates": "Copia le coordinate"
},
"toolbar": {
"routing": {
"tooltip": "Pianifica o modifica un percorsoo",
"activity": "Attività",
"use_routing": "Instradamento",
"use_routing_tooltip": "Collega i punti di ancoraggio tramite la rete stradale (o in linea retta se disabilitato)",
"use_routing_tooltip": "Collega i punti di ancoraggio tramite la rete stradale o in linea retta se disabilitato",
"allow_private": "Consenti strade private",
"reverse": {
"button": "Inverti la traccia",
@@ -235,18 +234,18 @@
"help_no_selection": "Seleziona un file per richiedere i dati di altitudine."
},
"waypoint": {
"tooltip": "Creare e modificare punti d'interesse",
"tooltip": "Creare e modificare punti di interesse",
"icon": "Icona",
"link": "Collegamento",
"longitude": "Longitudine",
"latitude": "Latitudine",
"create": "Creare un punto d'interesse",
"add": "Aggiungi punto d'interesse al file",
"help": "Compila il modulo per creare un nuovo punto d'interesse, oppure fai clic su uno esistente per modificarlo. Fare clic sulla mappa per inserire le coordinate o trascinare i punti d'interesse per spostarli.",
"help_no_selection": "Selezionare un file per creare o modificare punti d'interesse."
"create": "Creare un punto di interesse",
"add": "Aggiungi punto di interesse al file",
"help": "Compila il modulo per creare un nuovo punto di interesse, oppure fai clic su uno esistente per modificarlo. Fare clic sulla mappa per inserire le coordinate o trascinare i punti di interesse per spostarli.",
"help_no_selection": "Selezionare un file per creare o modificare punti di interesse."
},
"reduce": {
"tooltip": "Riduci il numero di punti GPS",
"tooltip": "Riduci il numero di punti della traccia",
"tolerance": "Tolleranza",
"number_of_points": "Numero di punti GPS",
"button": "Minimizza",
@@ -254,14 +253,14 @@
"help_no_selection": "Selezionare una traccia per ridurre il numero dei suoi punti GPS."
},
"clean": {
"tooltip": "Pulire i punti GPS e i punti d'interesse con una selezione rettangolare",
"tooltip": "Pulire i punti GPS e i punti di interesse con una selezione rettangolare",
"delete_trackpoints": "Eliminare punti GPS",
"delete_waypoints": "Cancella punti d'interesse",
"delete_inside": "Elimina all'interno della selezione",
"delete_outside": "Elimina fuori dalla selezione",
"button": "Elimina",
"help": "Selezionare un'area rettangolare sulla mappa per rimuovere i punti GPS e i punti d'interesse.",
"help_no_selection": "Seleziona una traccia per pulire i punti GPS e i punti d'interesse."
"help": "Selezionare un'area rettangolare sulla mappa per rimuovere i punti GPS e i punti di interesse.",
"help_no_selection": "Seleziona una traccia per pulire i punti GPS e i punti di interesse."
}
},
"layers": {
@@ -273,7 +272,7 @@
"new": "Nuovo livello personalizzato",
"edit": "Modifica livello personalizzato",
"urls": "URL(s)",
"url_placeholder": "WMTS, WMS o JSON in stile Mapbox",
"url_placeholder": "WMTS, WMS o Mapbox stile JSON",
"max_zoom": "Zoom massimo",
"layer_type": "Tipo del layer",
"basemap": "Mappa Base",
@@ -282,7 +281,6 @@
"update": "Aggiorna livello"
},
"opacity": "Opacità di sovrapposizione",
"terrain": "Terrain source",
"label": {
"basemaps": "Mappe di base",
"overlays": "Sovrapposizioni",
@@ -310,7 +308,7 @@
"linz": "LINZ Topo",
"linzTopo": "LINZ Topo50",
"swisstopoRaster": "swisstopo Raster",
"swisstopoVector": "swisstopo Vector",
"swisstopoVector": "Swisstopo Vector",
"swisstopoSatellite": "swisstopo Satellite",
"ignBe": "IGN Topo",
"ignFrPlan": "IGN Plan",
@@ -319,27 +317,25 @@
"ignFrSatellite": "Satellitare IGN",
"ignEs": "IGN Topo",
"ignEsSatellite": "Satellitare IGN",
"ordnanceSurvey": "Ordnance Survey",
"ordnanceSurvey": "Sondaggio Ordnance",
"norwayTopo": "Topografisk Norgeskart 4",
"finlandTopo": "Lantmäteriverket Terrängkarta",
"finlandTopo": "Carta topografica del vecchio Catasto svedese",
"bgMountains": "BGMountains",
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Pendenza",
"swisstopoHiking": "swisstopo Escursione",
"swisstopoHikingClosures": "swisstopo Fine escursione",
"swisstopoCycling": "swisstopo Ciclabile",
"swisstopoCyclingClosures": "swisstopo Fine ciclabile",
"swisstopoMountainBike": "swisstopo MTB",
"swisstopoMountainBikeClosures": "swisstopo Fine MTB",
"swisstopoSkiTouring": "swisstopo Sci Alpinismo",
"swisstopoSlope": "Carta topografica Svizzera Pendenza",
"swisstopoHiking": "Carta topografica Svedese Escursione",
"swisstopoHikingClosures": "Carta topografica Svizzera Fine escursione",
"swisstopoCycling": "Carta topografica Svizzera Ciclabile",
"swisstopoCyclingClosures": "Carta topografica Svizzera fine ciclabile",
"swisstopoMountainBike": "Carta topografica Svizzera MTB",
"swisstopoMountainBikeClosures": "Carta topografica Svizzera fine MTB",
"swisstopoSkiTouring": "Carta topografica Svizzera pista sci",
"ignFrCadastre": "IGN Catasto",
"ignSlope": "IGN Pendenza",
"ignSkiTouring": "IGN Sci Alpinismo",
"waymarked_trails": "Sentieri Segnalati",
"ignSlope": "Pendenza IGN",
"ignSkiTouring": "IGN Sciescursionismo",
"waymarked_trails": "Waymarked Trails",
"waymarkedTrailsHiking": "Escursionismo",
"waymarkedTrailsCycling": "Ciclismo",
"waymarkedTrailsMTB": "MTB",
@@ -356,7 +352,6 @@
"water": "Acqua",
"shower": "Doccia",
"shelter": "Riparo",
"cemetery": "Cimitero",
"motorized": "Auto e Motocicli",
"fuel-station": "Stazione di Rifornimento",
"parking": "Parcheggio",
@@ -380,9 +375,7 @@
"railway-station": "Stazione ferroviaria",
"tram-stop": "Fermata del tram",
"bus-stop": "Fermata dell'autobus",
"ferry": "Traghetto",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Traghetto"
}
},
"chart": {
@@ -411,11 +404,11 @@
"feet": "ft",
"kilometers": "km",
"miles": "mi",
"nautical_miles": "NM",
"nautical_miles": "nm",
"celsius": "°C",
"fahrenheit": "°F",
"kilometers_per_hour": "km/h",
"miles_per_hour": "mi/h",
"miles_per_hour": "mph",
"minutes_per_kilometer": "min/km",
"minutes_per_mile": "min/mi",
"minutes_per_nautical_mile": "min/nm",
@@ -431,8 +424,8 @@
"tracks": "Tracce",
"segment": "Segmento",
"segments": "Segmenti",
"waypoint": "Punto d'interesse",
"waypoints": "Punti d'interesse",
"waypoint": "Punto di interesse",
"waypoints": "Punti di interesse",
"symbol": {
"alert": "Avviso",
"anchor": "Ancora",
@@ -482,19 +475,20 @@
"app": "App",
"contact": "Contatto",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",
"email": "Email",
"contribute": "Contribuire",
"supported_by": "supportato da",
"support_button": "Supporta gpx.studio su Ko-fi",
"support_button": "Supporto di gpx.studio su Ko-fi",
"route_planning": "Pianificazione del percorso",
"route_planning_description": "Un'interfaccia intuitiva per creare itinerari su misura per ogni sport, basata sui dati OpenStreetMap.",
"route_planning_description": "Un'interfaccia intuitiva per creare itinerari su misura per ogni sport, basati sui dati OpenStreetMap.",
"file_processing": "Elaborazione avanzata dei file",
"file_processing_description": "Una serie di strumenti per eseguire tutte le attività comuni di elaborazione dei file e che possono essere applicati a più file contemporaneamente.",
"maps": "Mappe globali e locali",
"maps_description": "Una vasta collezione di mappe di base, sovrapposizioni e punti d'interesse per aiutarti a creare la tua prossima avventura all'aperto o visualizzare la tua ultima impresa.",
"maps_description": "Una vasta collezione di mappe di base, sovrapposizioni e punti d'interesse per aiutarti a creare la tua prossima avventura all'aperto o visualizzare il tuo ultimo risultato.",
"data_visualization": "Visualizzazione dei dati",
"data_visualization_description": "Un profilo di elevazione interattivo con statistiche dettagliate per analizzare attività registrate e obiettivi futuri.",
"identity": "Gratuito, senza pubblicità e open source",

View File

@@ -79,8 +79,7 @@
"unhide": "표시",
"center": "중앙",
"open_in": "Open in",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "레이어 갱신"
},
"opacity": "오버레이 투명도",
"terrain": "Terrain source",
"label": {
"basemaps": "배경 지도",
"overlays": "오버레이",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Shower",
"shelter": "대피소",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "주유소",
"parking": "주차장",
@@ -380,9 +375,7 @@
"railway-station": "철도역",
"tram-stop": "트램 정류장",
"bus-stop": "버스 정류장",
"ferry": "페리",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "페리"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "앱",
"contact": "문의",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Rodyti",
"center": "Center",
"open_in": "Atverti naudojant",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Naujinti sluoksnį"
},
"opacity": "Sluoksnio skaidrumas",
"terrain": "Terrain source",
"label": {
"basemaps": "Pagrindo žemėlapiai",
"overlays": "Sluoksniai",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Vanduo",
"shower": "Dušas",
"shelter": "Prieglauda",
"cemetery": "Cemetery",
"motorized": "Automobiliai ir motociklai",
"fuel-station": "Degalinė",
"parking": "Automobilių stovėjimo aikštelė",
@@ -380,9 +375,7 @@
"railway-station": "Geležinkelio stotis",
"tram-stop": "Tramvajaus stotelė",
"bus-stop": "Autobusų stotelė",
"ferry": "Keltas",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Keltas"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "Programa",
"contact": "Kontaktai",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Unhide",
"center": "Center",
"open_in": "Open in",
"copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
"copy_coordinates": "Copy coordinates"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Update layer"
},
"opacity": "Overlay opacity",
"terrain": "Terrain source",
"label": {
"basemaps": "Basemaps",
"overlays": "Overlays",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Slope",
"swisstopoHiking": "swisstopo Hiking",
"swisstopoHikingClosures": "swisstopo Hiking Closures",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Shower",
"shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station",
"parking": "Parking",
@@ -380,9 +375,7 @@
"railway-station": "Railway Station",
"tram-stop": "Tram Stop",
"bus-stop": "Bus Stop",
"ferry": "Ferry",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Ferry"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

View File

@@ -79,8 +79,7 @@
"unhide": "Maak zichtbaar",
"center": "Midden",
"open_in": "Openen in",
"copy_coordinates": "Coördinaten kopiëren",
"edit_osm": "Bewerken in OpenStreetMap"
"copy_coordinates": "Coördinaten kopiëren"
},
"toolbar": {
"routing": {
@@ -282,7 +281,6 @@
"update": "Update laag"
},
"opacity": "Laag Transparantie",
"terrain": "Terrein bron",
"label": {
"basemaps": "Basis kaarten",
"overlays": "Lagen",
@@ -326,8 +324,6 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Grind",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Helling",
"swisstopoHiking": "swisstopo Wandelen",
"swisstopoHikingClosures": "swisstopo Hiking Sluiting",
@@ -356,7 +352,6 @@
"water": "Water",
"shower": "Douche",
"shelter": "Schuilplaats",
"cemetery": "Begraafplaats",
"motorized": "Auto's en Motorfietsen",
"fuel-station": "Tankstation",
"parking": "Parkeren",
@@ -380,9 +375,7 @@
"railway-station": "Treinstation",
"tram-stop": "Tramhalte",
"bus-stop": "Bushalte",
"ferry": "Veerboot",
"mapbox-dem": "Mapbox DEM",
"mapterhorn": "Mapterhorn"
"ferry": "Veerboot"
}
},
"chart": {
@@ -482,6 +475,7 @@
"app": "App",
"contact": "Contact",
"reddit": "Reddit",
"x": "X",
"facebook": "Facebook",
"github": "GitHub",
"crowdin": "Crowdin",

Some files were not shown because too many files have changed in this diff Show More