rework page metadata and links

This commit is contained in:
vcoppe
2024-07-08 18:54:16 +02:00
parent 83cd3fd987
commit 65b297e133
17 changed files with 145 additions and 149 deletions

View File

@@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

View File

@@ -5,15 +5,6 @@
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta property="og:image" content="https://gpx.studio/og_logo.png" />
<meta property="og:url" content="https://gpx.studio/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="gpx.studio" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://gpx.studio/og_logo.png" />
<meta name="twitter:url" content="https://gpx.studio/" />
<meta name="twitter:site" content="@gpxstudio" />
<meta name="twitter:creator" content="@gpxstudio" />
%sveltekit.head%
</head>

View File

@@ -3,7 +3,8 @@
import LanguageSelect from '$lib/components/LanguageSelect.svelte';
import Logo from '$lib/components/Logo.svelte';
import { AtSign, BookOpenText, Heart, Home, Map } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import { _, locale } from 'svelte-i18n';
import { getURLForLanguage } from '$lib/languages';
</script>
<footer class="w-full">
@@ -24,15 +25,27 @@
<div class="grow max-w-2xl flex flex-row flex-wrap justify-between gap-x-10 gap-y-6">
<div class="flex flex-col items-start gap-1">
<span class="font-semibold">{$_('homepage.website')}</span>
<Button variant="link" class="h-6 px-0 text-muted-foreground" href="./about">
<Button
variant="link"
class="h-6 px-0 text-muted-foreground"
href={getURLForLanguage('/[...language]', $locale)}
>
<Home size="16" class="mr-1" />
{$_('homepage.home')}
</Button>
<Button variant="link" class="h-6 px-0 text-muted-foreground" href="./">
<Button
variant="link"
class="h-6 px-0 text-muted-foreground"
href={getURLForLanguage('/[...language]/app', $locale)}
>
<Map size="16" class="mr-1" />
{$_('homepage.app')}
</Button>
<Button variant="link" class="h-6 px-0 text-muted-foreground" href="./documentation">
<Button
variant="link"
class="h-6 px-0 text-muted-foreground"
href={getURLForLanguage('/[...language]/documentation', $locale)}
>
<BookOpenText size="16" class="mr-1" />
{$_('homepage.documentation')}
</Button>

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { languages } from '$lib/languages';
import { _, isLoading } from 'svelte-i18n';
$: location = $page.route.id?.split('/').slice(1).at(1) ?? 'home';
</script>
<svelte:head>
{#if $isLoading}
<title>gpx.studio — the online GPX file editor</title>
<meta
name="description"
content="View, edit and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
/>
<meta property="og:title" content="gpx.studio — the online GPX file editor" />
<meta
property="og:description"
content="View, edit and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
/>
<meta name="twitter:title" content="gpx.studio — the online GPX file editor" />
<meta
name="twitter:description"
content="View, edit and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
/>
{:else}
<title>gpx.studio — {$_(`metadata.${location}_title`)}</title>
<meta name="description" content={$_('metadata.description')} />
<meta property="og:title" content="gpx.studio — {$_(`metadata.${location}_title`)}" />
<meta property="og:description" content={$_('metadata.description')} />
<meta name="twitter:title" content="gpx.studio — {$_(`metadata.${location}_title`)}" />
<meta name="twitter:description" content={$_('metadata.description')} />
{/if}
<meta property="og:image" content="https://gpx.studio/og_logo.png" />
<meta property="og:url" content="https://gpx.studio/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="gpx.studio" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://gpx.studio/og_logo.png" />
<meta name="twitter:url" content="https://gpx.studio/" />
<meta name="twitter:site" content="@gpxstudio" />
<meta name="twitter:creator" content="@gpxstudio" />
<link
rel="alternate"
hreflang="x-default"
href="https://gpx.studio{base}/{location === 'home' ? '' : location}"
/>
{#each Object.keys(languages) as lang}
<link
rel="alternate"
hreflang={lang}
href="https://gpx.studio{base}/{lang === 'en' ? '' : lang + '/'}{location === 'home'
? ''
: location}"
/>
{/each}
</svelte:head>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import * as Select from '$lib/components/ui/select';
import { getURLForLanguage, languages } from '$lib/languages';
import { Languages } from 'lucide-svelte';
@@ -18,14 +18,25 @@
}
</script>
<Select.Root bind:selected onSelectedChange={(s) => goto(getURLForLanguage(s?.value))}>
<Select.Root bind:selected>
<Select.Trigger class="w-[180px] {$$props.class ?? ''}">
<Languages size="16" />
<Select.Value class="ml-2 mr-auto" />
</Select.Trigger>
<Select.Content>
{#each Object.entries(languages) as [key, value]}
<Select.Item value={key}>{value}</Select.Item>
{#each Object.entries(languages) as [lang, label]}
<a href={getURLForLanguage($page.route.id, lang)}>
<Select.Item value={lang}>{label}</Select.Item>
</a>
{/each}
</Select.Content>
</Select.Root>
<!-- hidden links for svelte crawling -->
<div class="hidden">
{#each Object.entries(languages) as [lang, label]}
<a href={getURLForLanguage($page.route.id, lang)}>
{label}
</a>
{/each}
</div>

View File

@@ -73,6 +73,7 @@
import { mode, setMode, systemPrefersMode } from 'mode-watcher';
import { _, locale } from 'svelte-i18n';
import { getURLForLanguage, languages } from '$lib/languages';
import { page } from '$app/stores';
const {
distanceUnits,
@@ -123,7 +124,7 @@
<div
class="w-fit flex flex-row items-center justify-center p-1 bg-background rounded-b-md md:rounded-md pointer-events-auto shadow-md"
>
<a href="./" target="_blank">
<a href={getURLForLanguage('/[...language]', $locale)} target="_blank">
<Logo class="h-5 mt-0.5 mx-2 md:hidden" iconOnly={true} />
<Logo class="h-5 mt-0.5 mx-2 hidden md:block" />
</a>
@@ -363,9 +364,9 @@
</Menubar.SubTrigger>
<Menubar.SubContent>
<Menubar.RadioGroup bind:value={$locale}>
{#each Object.entries(languages) as [code, name]}
<a href={getURLForLanguage(code)}>
<Menubar.RadioItem value={code}>{name}</Menubar.RadioItem>
{#each Object.entries(languages) as [lang, label]}
<a href={getURLForLanguage($page.route.id, lang)}>
<Menubar.RadioItem value={lang}>{label}</Menubar.RadioItem>
</a>
{/each}
</Menubar.RadioGroup>
@@ -415,7 +416,7 @@
<div class="h-fit flex flex-row items-center ml-1 gap-1">
<Button
variant="ghost"
href="./documentation"
href={getURLForLanguage('/[...language]/documentation', $locale)}
target="_blank"
class="cursor-default h-fit rounded-sm px-3 py-0.5"
>

View File

@@ -3,24 +3,37 @@
import { Button } from '$lib/components/ui/button';
import ModeSwitch from '$lib/components/ModeSwitch.svelte';
import { BookOpenText, Home, Map } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import { getURLForLanguage } from '$lib/languages';
import { _, locale } from 'svelte-i18n';
</script>
<nav class="w-full sticky top-0 bg-background z-10">
<div class="mx-6 py-2 flex flex-row items-center border-b gap-4 md:gap-8">
<a href="./" class="shrink-0">
<a href={getURLForLanguage('/[...language]', $locale)} class="shrink-0">
<Logo class="h-8 sm:hidden" iconOnly={true} />
<Logo class="h-7 hidden sm:block" />
</a>
<Button variant="link" class="text-base px-0" href="./">
<Button
variant="link"
class="text-base px-0"
href={getURLForLanguage('/[...language]', $locale)}
>
<Home size="18" class="mr-1.5" />
{$_('homepage.home')}
</Button>
<Button variant="link" class="text-base px-0" href="./app">
<Button
variant="link"
class="text-base px-0"
href={getURLForLanguage('/[...language]/app', $locale)}
>
<Map size="18" class="mr-1.5" />
{$_('homepage.app')}
</Button>
<Button variant="link" class="text-base px-0" href="./documentation">
<Button
variant="link"
class="text-base px-0"
href={getURLForLanguage('/[...language]/documentation', $locale)}
>
<BookOpenText size="18" class="mr-1.5" />
{$_('homepage.documentation')}
</Button>

View File

@@ -1,5 +1,5 @@
import { dbUtils, getFile } from "$lib/db";
import { castDraft, freeze } from "immer";
import { freeze } from "immer";
import { GPXFile, Track, TrackSegment, Waypoint } from "gpx";
import { selection } from "./Selection";
import { newGPXFile } from "$lib/stores";

View File

@@ -1,18 +1,18 @@
export const languages: Record<string, string> = {
'en': 'English',
'fr': 'Français',
};
export function getURLForLanguage(lang?: string): string {
let currentPath = window.location.pathname;
let currentPathArray = currentPath.split('/');
if (currentPathArray.length > 1 && languages.hasOwnProperty(currentPathArray[1])) {
currentPathArray.splice(1, 1);
export function getURLForLanguage(route: string | null, lang: string | null | undefined): string {
if (route === null) {
return '/';
}
if (lang !== undefined && lang !== 'en') {
currentPathArray.splice(1, 0, lang);
}
let url = route.replace('[...language]', (lang === null || lang === undefined) ? 'en' : lang).replace('/en', '');
return currentPathArray.join('/');
if (url === '') {
return '/';
} else {
return url;
}
}

View File

@@ -1,31 +1,9 @@
import fs from 'fs';
import { languages } from "./languages";
function getURL(lang: string, path: string = '/') {
return 'https://gpx.studio' + (lang === 'en' ? '' : ('/' + lang)) + path;
}
function generateSitemap() {
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">` +
console.log('Generating sitemap...');
Object.keys(languages).map((lang) => `
<url>
<loc>${getURL(lang)}</loc>${Object.keys(languages).map((lang2) => `
<xhtml:link rel="alternate" hreflang="${lang2}" href="${getURL(lang2)}"/>`).join('')}
</url>`).join('') +
Object.keys(languages).map((lang) => `
<url>
<loc>${getURL(lang, '/about')}</loc>${Object.keys(languages).map((lang2) => `
<xhtml:link rel="alternate" hreflang="${lang2}" href="${getURL(lang2, '/about')}"/>`).join('')}
</url>`).join('') +
`
</urlset>
`;
return sitemap;
return '';
}
fs.writeFileSync('build/sitemap.xml', generateSitemap());

View File

@@ -1,7 +1,8 @@
{
"metadata": {
"app_title": "the online GPX file editor",
"home_title": "home",
"app_title": "the online GPX file editor",
"documentation_title": "documentation",
"description": "View, edit and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
},
"menu": {

View File

@@ -2,18 +2,20 @@
import { ModeWatcher } from 'mode-watcher';
import { isLoading, locale, _ } from 'svelte-i18n';
import { page } from '$app/stores';
import Head from '$lib/components/Head.svelte';
import Nav from '$lib/components/Nav.svelte';
import Footer from '$lib/components/Footer.svelte';
$: if ($page.params.language === '' && $locale !== 'en') {
locale.set('en');
} else if ($page.params.language && $locale !== $page.params.language) {
locale.set($page.params.language);
locale.set($page.params.language.replace('/', ''));
}
const appRoute = '/[...language]/app';
</script>
<Head />
<ModeWatcher />
{#if !$isLoading}

View File

@@ -1,15 +1,13 @@
<script lang="ts">
import { base } from '$app/paths';
import { Button } from '$lib/components/ui/button';
import DocsLoader from '$lib/components/docs/DocsLoader.svelte';
import Logo from '$lib/components/Logo.svelte';
import ElevationProfile from '$lib/components/ElevationProfile.svelte';
import GPXStatistics from '$lib/components/GPXStatistics.svelte';
import Routing from '$lib/components/toolbar/tools/routing/Routing.svelte';
import { languages } from '$lib/languages';
import { settings } from '$lib/db';
import { BookOpenText, Heart, Map } from 'lucide-svelte';
import { _ } from 'svelte-i18n';
import { _, locale } from 'svelte-i18n';
import { exampleGPXFile } from '$lib/assets/example';
import { writable } from 'svelte/store';
import Toolbar from '$lib/components/toolbar/Toolbar.svelte';
@@ -22,6 +20,7 @@
import cyclosmMap from '$lib/assets/img/cyclosm.png?enhanced';
import waymarkedMap from '$lib/assets/img/waymarked.png?enhanced';
import mapScreenshot from '$lib/assets/img/map.png?enhanced';
import { getURLForLanguage } from '$lib/languages';
let gpxStatistics = writable(exampleGPXFile.getStatistics());
let slicedGPXStatistics = writable(undefined);
@@ -39,24 +38,6 @@
});
</script>
<svelte:head>
<title>gpx.studio — {$_('metadata.home_title')}</title>
<meta name="description" content={$_('metadata.description')} />
<meta property="og:title" content="gpx.studio — {$_('metadata.home_title')}" />
<meta property="og:description" content={$_('metadata.description')} />
<meta name="twitter:title" content="gpx.studio — {$_('metadata.home_title')}" />
<meta name="twitter:description" content={$_('metadata.description')} />
<link rel="alternate" hreflang="x-default" href="{base}/" />
{#each Object.keys(languages) as lang}
{#if lang === 'en'}
<link rel="alternate" hreflang="en" href="{base}/" />
{:else}
<link rel="alternate" hreflang={lang} href="{base}/{lang}/" />
{/if}
{/each}
</svelte:head>
<div class="space-y-24 my-24">
<div class="px-12 w-full flex flex-col items-center">
<div class="flex flex-col gap-6 items-center max-w-3xl">
@@ -65,11 +46,15 @@
{$_('metadata.description')}
</div>
<div class="w-full flex flex-row justify-center gap-3">
<Button href="./app" class="w-1/3 min-w-fit">
<Button href={getURLForLanguage('/[...language]/app', $locale)} class="w-1/3 min-w-fit">
<Map size="18" class="mr-1.5" />
{$_('homepage.app')}
</Button>
<Button variant="secondary" href="./documentation" class="w-1/3 min-w-fit">
<Button
variant="secondary"
href={getURLForLanguage('/[...language]/documentation', $locale)}
class="w-1/3 min-w-fit"
>
<BookOpenText size="18" class="mr-1.5" />
<span>{$_('homepage.documentation')}</span>
</Button>
@@ -190,7 +175,7 @@
</div>
<div class="px-12 flex flex-col items-center">
<div class="max-w-5xl flex flex-col items-center gap-6">
<DocsLoader path="about/funding.md" />
<DocsLoader path="home/funding.md" />
<Button
href="https://ko-fi.com/gpxstudio"
target="_blank"
@@ -203,7 +188,7 @@
</div>
<div class="px-12 flex flex-col items-center">
<div class="max-w-5xl">
<DocsLoader path="about/translation.md" />
<DocsLoader path="home/translation.md" />
</div>
</div>
<div class="px-12 md:px-24 flex flex-col items-center">
@@ -218,7 +203,7 @@
<Logo company="mapbox" class="w-60" />
</a>
</div>
<DocsLoader path="about/mapbox.md" />
<DocsLoader path="home/mapbox.md" />
</div>
</div>
</div>

View File

@@ -1,26 +1,5 @@
<script lang="ts">
import App from '$lib/components/App.svelte';
import { base } from '$app/paths';
import { languages } from '$lib/languages';
import { _ } from 'svelte-i18n';
</script>
<svelte:head>
<title>gpx.studio — {$_('metadata.app_title')}</title>
<meta name="description" content={$_('metadata.description')} />
<meta property="og:title" content="gpx.studio — {$_('metadata.app_title')}" />
<meta property="og:description" content={$_('metadata.description')} />
<meta name="twitter:title" content="gpx.studio — {$_('metadata.app_title')}" />
<meta name="twitter:description" content={$_('metadata.description')} />
<link rel="alternate" hreflang="x-default" href="{base}/" />
{#each Object.keys(languages) as lang}
{#if lang === 'en'}
<link rel="alternate" hreflang="en" href="{base}/" />
{:else}
<link rel="alternate" hreflang={lang} href="{base}/{lang}/" />
{/if}
{/each}
</svelte:head>
<App />