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