Compare commits
No commits in common. "41585a9cd33d534111530383e58f88c9fdab9615" and "cf231feb8e5a7fc372df44ea7cc3e2ed5643d70a" have entirely different histories.
41585a9cd3
...
cf231feb8e
7 changed files with 77 additions and 95 deletions
21
hooks.server.ts
Normal file
21
hooks.server.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
|
// Correct origin behind Nginx
|
||||||
|
const host = event.request.headers.get('x-forwarded-host') ?? event.request.headers.get('host');
|
||||||
|
const proto = event.request.headers.get('x-forwarded-proto') ?? 'https';
|
||||||
|
const origin = `${proto}://${host}`;
|
||||||
|
|
||||||
|
// Example: force HTTPS (optional, Nginx should already do this)
|
||||||
|
if (proto === 'http') {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 308,
|
||||||
|
headers: {
|
||||||
|
Location: origin + event.url.pathname + event.url.search
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with default behavior
|
||||||
|
return resolve(event);
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "embroidery-viewer",
|
"name": "embroidery-viewer",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.1.0",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"paypal.link": "Open Donation link",
|
"paypal.link": "Open Donation link",
|
||||||
"seo.title": "💖 Donate – Support Embroidery Viewer",
|
"seo.title": "💖 Donate – Support Embroidery Viewer",
|
||||||
"seo.description": "Help keep Embroidery Viewer free and improving by making a donation. Choose from Bitcoin, Monero, PayPal, or other secure options to support ongoing development and hosting.",
|
"seo.description": "Help keep Embroidery Viewer free and improving by making a donation. Choose from Bitcoin, Monero, PayPal, or other secure options to support ongoing development and hosting.",
|
||||||
"seo.keywords": "donate embroidery viewer, support embroidery viewer, embroidery viewer donations, help embroidery viewer, fund embroidery viewer, bitcoin donation embroidery, monero donation embroidery, paypal donation embroidery",
|
"keywords": "donate embroidery viewer, support embroidery viewer, embroidery viewer donations, help embroidery viewer, fund embroidery viewer, bitcoin donation embroidery, monero donation embroidery, paypal donation embroidery",
|
||||||
"url": "https://embroideryviewer.xyz/donate",
|
"url": "https://embroideryviewer.xyz/donate",
|
||||||
"image": "https://embroideryviewer.xyz/og/donate.png"
|
"image": "https://embroideryviewer.xyz/og/donate.png"
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,10 +127,7 @@ export const {
|
||||||
} = new i18n(config);
|
} = new i18n(config);
|
||||||
|
|
||||||
locale.subscribe(($locale) => {
|
locale.subscribe(($locale) => {
|
||||||
if (typeof localStorage !== 'undefined' && $locale) {
|
// if (typeof document !== 'undefined') {
|
||||||
const existing = localStorage.getItem('locale');
|
// document.cookie = `locale=${$locale}; path=/; SameSite=None; Secure`;
|
||||||
if (existing !== $locale) {
|
// }
|
||||||
localStorage.setItem('locale', $locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,51 +1,17 @@
|
||||||
import { browser } from '$app/environment';
|
import { setLocale, setRoute } from '$lib/translations';
|
||||||
import { loadTranslations, setLocale, setRoute } from '$lib/translations';
|
|
||||||
import { SUPPORTED_LOCALES } from '$lib/translations';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard that checks if a string is a supported locale.
|
* @typedef {Object} LayoutData
|
||||||
* @param {string | null} locale
|
* @property {string} route
|
||||||
* @returns {locale is "en-US" | "pt-BR"}
|
* @property {string} language
|
||||||
*/
|
*/
|
||||||
function isSupportedLocale(locale) {
|
|
||||||
// @ts-ignore
|
|
||||||
return locale !== null && Object.values(SUPPORTED_LOCALES).includes(locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** @type {import('@sveltejs/kit').Load<LayoutData>} */
|
||||||
* Client-side load function to initialize translations based on localStorage or server fallback.
|
export const load = async ({ data }) => {
|
||||||
*
|
const { route, language } = data ?? {};
|
||||||
* This function runs in the browser and:
|
|
||||||
* - Prioritizes locale from localStorage (if valid)
|
|
||||||
* - Falls back to the server-provided fallbackLanguage (from Accept-Language)
|
|
||||||
* - Initializes translations and routing
|
|
||||||
*
|
|
||||||
* @type {import('@sveltejs/kit').Load}
|
|
||||||
*/
|
|
||||||
export const load = async ({ data, url }) => {
|
|
||||||
/** @type {string} */
|
|
||||||
const route = url.pathname;
|
|
||||||
|
|
||||||
/** @type {"en-US" | "pt-BR"} */
|
if (route) await setRoute(route);
|
||||||
let language;
|
if (language) await setLocale(language);
|
||||||
|
|
||||||
if (browser) {
|
return data ?? {};
|
||||||
/**
|
|
||||||
* Locale stored in the browser, if any.
|
|
||||||
* @type {string | null}
|
|
||||||
*/
|
|
||||||
const stored = localStorage.getItem('locale');
|
|
||||||
|
|
||||||
if (isSupportedLocale(stored)) {
|
|
||||||
language = stored; // Type narrowed here
|
|
||||||
} else {
|
|
||||||
language = data?.fallbackLanguage ?? SUPPORTED_LOCALES.EN_US;
|
|
||||||
}
|
|
||||||
|
|
||||||
await loadTranslations(language, route);
|
|
||||||
await setLocale(language);
|
|
||||||
await setRoute(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,44 +1,55 @@
|
||||||
import { parse } from 'accept-language-parser';
|
import { parse } from 'accept-language-parser';
|
||||||
|
import { loadTranslations, setLocale, setRoute } from '$lib/translations';
|
||||||
import { SUPPORTED_LOCALES } from '$lib/translations';
|
import { SUPPORTED_LOCALES } from '$lib/translations';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Set of all supported locale codes for quick validation.
|
* A set of all supported locale codes, used to validate and match against
|
||||||
|
* user preferences from cookies or Accept-Language headers. We're using a
|
||||||
|
* Set for better performance in lookup.
|
||||||
|
*
|
||||||
* Example values: "en-US", "pt-BR"
|
* Example values: "en-US", "pt-BR"
|
||||||
* @type {Set<string>}
|
* @type {Set<string>}
|
||||||
*/
|
*/
|
||||||
const SUPPORTED_LOCALE_SET = new Set(Object.values(SUPPORTED_LOCALES));
|
const SUPPORTED_LOCALE_SET = new Set(Object.values(SUPPORTED_LOCALES));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the best matching locale from an Accept-Language HTTP header.
|
* Returns a valid locale from cookies, or null if not valid/found.
|
||||||
*
|
* @param {{ get: (cookies: string) => any; }} cookies
|
||||||
* @param {string | null} header - The Accept-Language header value.
|
|
||||||
* @returns {string | null} - A supported locale string like "en-US", or null if none matched.
|
|
||||||
*/
|
*/
|
||||||
function localeFromHeader(header) {
|
function localeFromCookies(cookies) {
|
||||||
if (!header) return null;
|
const locale = cookies.get('locale');
|
||||||
const parsed = parse(header);
|
return locale && SUPPORTED_LOCALE_SET.has(locale) ? locale : null;
|
||||||
for (const { code, region } of parsed) {
|
|
||||||
const locale = region ? `${code}-${region}` : code;
|
|
||||||
if (SUPPORTED_LOCALE_SET.has(locale)) return locale;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server-side load function that returns the initial route and fallback language.
|
* Parses the Accept-Language header and returns the best matching locale.
|
||||||
* The language is inferred from the Accept-Language header.
|
* @param {string | null | undefined} header
|
||||||
* `localStorage` will take precedence on the client.
|
|
||||||
*
|
|
||||||
* @type {import('@sveltejs/kit').ServerLoad}
|
|
||||||
*/
|
*/
|
||||||
export async function load({ url, request }) {
|
function localeFromHeader(header) {
|
||||||
/** @type {string} */
|
if (!header) return null;
|
||||||
|
|
||||||
|
const parsedLanguages = parse(header);
|
||||||
|
for (const { code, region } of parsedLanguages) {
|
||||||
|
const locale = region ? `${code}-${region}` : code;
|
||||||
|
if (SUPPORTED_LOCALE_SET.has(locale)) {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').ServerLoad}*/
|
||||||
|
export async function load({ url, request, cookies }) {
|
||||||
|
// const cookieLocale = localeFromCookies(cookies);
|
||||||
|
// const headerLocale = localeFromHeader(request.headers.get('accept-language'));
|
||||||
|
// const language = cookieLocale || headerLocale || SUPPORTED_LOCALES.EN_US;
|
||||||
|
const language = SUPPORTED_LOCALES.EN_US;
|
||||||
const route = url.pathname;
|
const route = url.pathname;
|
||||||
|
|
||||||
/** @type {string} */
|
await loadTranslations(language, route);
|
||||||
const fallbackLanguage =
|
setLocale(language);
|
||||||
localeFromHeader(request.headers.get('accept-language')) ||
|
setRoute(route);
|
||||||
SUPPORTED_LOCALES.EN_US;
|
|
||||||
|
|
||||||
return { fallbackLanguage, route };
|
return { language, route };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
import Footer from '$lib/components/Footer.svelte';
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
|
|
||||||
let mounted = false;
|
|
||||||
|
|
||||||
if (browser) {
|
|
||||||
onMount(() => {
|
|
||||||
mounted = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if mounted}
|
<Header />
|
||||||
<Header />
|
<main>
|
||||||
<main>
|
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
main {
|
main {
|
||||||
|
|
Loading…
Add table
Reference in a new issue