mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-30 23:30:04 +00:00
seo
This commit is contained in:
41
website/package-lock.json
generated
41
website/package-lock.json
generated
@@ -51,6 +51,7 @@
|
||||
"svelte-check": "^3.8.1",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.15.7",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
@@ -3180,6 +3181,18 @@
|
||||
"resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
|
||||
"integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg=="
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.7.5",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
|
||||
"integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/gl-matrix": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
|
||||
@@ -4919,6 +4932,15 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-protobuf-schema": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
|
||||
@@ -6203,6 +6225,25 @@
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.15.7",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.15.7.tgz",
|
||||
"integrity": "sha512-u3H0iSFDZM3za+VxkZ1kywdCeHCn+8/qHQS1MNoO2sONDgD95HlWtt8aB23OzeTmFP9IU4/8bZUdg58Uu5J4cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.21.4",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tweakpane": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-4.0.3.tgz",
|
||||
|
@@ -5,6 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"postbuild": "npx tsx src/lib/sitemap.ts",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
@@ -36,6 +37,7 @@
|
||||
"svelte-check": "^3.8.1",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.15.7",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.3.1"
|
||||
},
|
||||
@@ -59,4 +61,4 @@
|
||||
"tailwind-merge": "^2.3.0",
|
||||
"tailwind-variants": "^0.2.1"
|
||||
}
|
||||
}
|
||||
}
|
31
website/src/lib/sitemap.ts
Normal file
31
website/src/lib/sitemap.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
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">` +
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fs.writeFileSync('build/sitemap.xml', generateSitemap());
|
@@ -1,4 +1,9 @@
|
||||
{
|
||||
"metadata": {
|
||||
"app_title": "the online GPX file editor",
|
||||
"about_title": "about",
|
||||
"description": "View, edit and create GPX files online with advanced route planning capabilities, file processing tools and beautiful maps."
|
||||
},
|
||||
"menu": {
|
||||
"new": "New",
|
||||
"new_file": "New file",
|
||||
|
@@ -1,12 +1,5 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import '../app.pcss';
|
||||
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import { isLoading } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<ModeWatcher />
|
||||
|
||||
{#if !$isLoading}
|
||||
<slot />
|
||||
{/if}
|
||||
<slot />
|
||||
|
15
website/src/routes/[...language]/+layout.svelte
Normal file
15
website/src/routes/[...language]/+layout.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import { isLoading, locale, _ } from 'svelte-i18n';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
if ($page.params.language) {
|
||||
locale.set($page.params.language);
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModeWatcher />
|
||||
|
||||
{#if !$isLoading}
|
||||
<slot />
|
||||
{/if}
|
@@ -1,12 +1,35 @@
|
||||
<script lang="ts">
|
||||
import App from '$lib/components/App.svelte';
|
||||
|
||||
import { locale } from 'svelte-i18n';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
if ($page.params.language) {
|
||||
locale.set($page.params.language);
|
||||
}
|
||||
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 property="og:image" content="{base}/og_logo.png" />
|
||||
<meta property="og:url" content="{base}/" />
|
||||
<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:title" content="gpx.studio — {$_('metadata.app_title')}" />
|
||||
<meta name="twitter:description" content={$_('metadata.description')} />
|
||||
<meta name="twitter:image" content="{base}/og_logo.png" />
|
||||
<meta name="twitter:url" content="{base}/" />
|
||||
<meta name="twitter:site" content="@gpxstudio" />
|
||||
<meta name="twitter:creator" content="@gpxstudio" />
|
||||
|
||||
<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 />
|
||||
|
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { languages } from '$lib/languages';
|
||||
import { _ } from 'svelte-i18n';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>gpx.studio — {$_('metadata.about_title')}</title>
|
||||
<meta name="description" content={$_('metadata.description')} />
|
||||
<meta property="og:title" content="gpx.studio — {$_('metadata.about_title')}" />
|
||||
<meta property="og:description" content={$_('metadata.description')} />
|
||||
<meta property="og:image" content="{base}/og_logo.png" />
|
||||
<meta property="og:url" content="{base}/" />
|
||||
<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:title" content="gpx.studio — {$_('metadata.about_title')}" />
|
||||
<meta name="twitter:description" content={$_('metadata.description')} />
|
||||
<meta name="twitter:image" content="{base}/og_logo.png" />
|
||||
<meta name="twitter:url" content="{base}/" />
|
||||
<meta name="twitter:site" content="@gpxstudio" />
|
||||
<meta name="twitter:creator" content="@gpxstudio" />
|
||||
|
||||
<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>
|
||||
|
BIN
website/static/og_logo.png
Normal file
BIN
website/static/og_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 123 KiB |
Reference in New Issue
Block a user