diff --git a/src/App.svelte b/src/App.svelte
index fa512e0..7118944 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -5,6 +5,7 @@
import Header from '@components/layout/Header.svelte'
import Cursor from '@components/layout/Cursor.svelte'
+ import LanguageSwitcher from '@components/ui/LanguageSwitcher.svelte'
import Home from '@views/Home.svelte'
import About from '@views/About.svelte'
@@ -94,6 +95,7 @@
{/each}
+
diff --git a/src/i18n/index.js b/src/i18n/index.js
new file mode 100644
index 0000000..aa73357
--- /dev/null
+++ b/src/i18n/index.js
@@ -0,0 +1,51 @@
+import { locale } from '@state/locale.svelte'
+
+const dict = {
+ // Article
+ 'published_on': { fr: 'Publié le', en: 'Published on' },
+ 'link_copied': { fr: 'Lien copié !', en: 'Link copied!' },
+ 'copy_link': { fr: 'Copier le lien', en: 'Copy link' },
+ 'share_article': { fr: 'Partager cet article', en: 'Share this article' },
+ 'related': { fr: 'Nos recommandations', en: 'Our recommendations' },
+ 'share_whatsapp': { fr: 'Partager sur WhatsApp', en: 'Share on WhatsApp' },
+ 'share_x': { fr: 'Partager sur X', en: 'Share on X' },
+ 'share_facebook': { fr: 'Partager sur Facebook', en: 'Share on Facebook' },
+ 'share_linkedin': { fr: 'Partager sur LinkedIn', en: 'Share on LinkedIn' },
+ // Blog
+ 'loading': { fr: 'Chargement…', en: 'Loading…' },
+ 'read_article': { fr: "Lire l'article", en: 'Read article' },
+ // Play
+ 'play': { fr: 'Jouer', en: 'Play' },
+ 'coming_soon': { fr: 'Coming soon', en: 'Coming soon' },
+ // Header
+ 'close_menu': { fr: 'Fermer le menu', en: 'Close menu' },
+ 'open_menu': { fr: 'Ouvrir le menu', en: 'Open menu' },
+ // Footer
+ 'location': { fr: 'Adresse', en: 'Location' },
+ 'contact': { fr: 'Contact', en: 'Contact' },
+ 'follow_us': { fr: 'Réseaux', en: 'Follow us' },
+ 'newsletter_heading': { fr: 'Inscrivez-vous à notre newsletter !', en: 'Subscribe to our newsletter!' },
+ 'newsletter_placeholder': { fr: 'Votre email', en: 'Enter your email' },
+ 'newsletter_submit': { fr: "S'inscrire", en: 'Subscribe' },
+ 'newsletter_success': { fr: 'Merci pour votre inscription !', en: 'Thank you for subscribing!' },
+ 'newsletter_error': { fr: 'Une erreur est survenue.', en: 'An error occurred.' },
+ 'copyright': { fr: 'World Game © {year}. Tous droits réservés.', en: 'World Game © {year}. All rights reserved.' },
+ 'legal': { fr: 'Mentions légales', en: 'Legal notice' },
+ 'cookies': { fr: 'Préférences cookies', en: 'Cookie preferences' },
+ 'privacy': { fr: 'Confidentialité', en: 'Privacy' },
+ // Menu
+ 'menu': { fr: 'MENU', en: 'MENU' },
+ 'connect': { fr: 'CONNECT', en: 'CONNECT' },
+ 'address': { fr: 'ADRESSE', en: 'LOCATION' },
+ 'mail': { fr: 'MAIL', en: 'MAIL' },
+ 'socials': { fr: 'RÉSEAUX', en: 'SOCIALS' },
+}
+
+export function t(key, vars = {}) {
+ const lang = locale.current
+ let str = dict[key]?.[lang] ?? dict[key]?.fr ?? key
+ for (const [k, v] of Object.entries(vars)) {
+ str = str.replace(`{${k}}`, v)
+ }
+ return str
+}
diff --git a/src/router/index.js b/src/router/index.js
index d035676..cb54766 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -5,7 +5,12 @@ import { locale } from "@state/locale.svelte";
let siteInitialized = false;
function normalizePath(path) {
- return path === "/" ? "/home" : path;
+ const stripped = path.replace(/^\/en(\/|$)/, '$1') || '/';
+ return stripped === '/' ? '/home' : stripped;
+}
+
+function apiPrefix() {
+ return locale.current === 'en' ? '/en' : '';
}
/**
@@ -47,8 +52,7 @@ async function loadSlide(path) {
}
try {
- // Fetch the actual slide path (parent), not the sub-page
- const response = await fetch(`${slidePath}.json`);
+ const response = await fetch(`${apiPrefix()}${slidePath}.json`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
@@ -76,7 +80,10 @@ export function slideTo(path, { skipHistory = false } = {}) {
path = normalizePath(path);
if (!skipHistory) {
- history.pushState({}, "", path === "/home" ? "/" : path);
+ const historyPath = locale.current === 'en'
+ ? (path === '/home' ? '/en' : `/en${path}`)
+ : (path === '/home' ? '/' : path);
+ history.pushState({}, '', historyPath);
}
const idx = findSlideIndex(path);
@@ -94,6 +101,22 @@ export function slideTo(path, { skipHistory = false } = {}) {
}
export async function initRouter() {
+ // Language detection: URL prefix > localStorage > navigator
+ const hasEnPrefix = window.location.pathname.startsWith('/en');
+ if (hasEnPrefix) {
+ locale.setLanguage('en');
+ localStorage.setItem('wg_lang', 'en');
+ } else if (!localStorage.getItem('wg_lang')) {
+ const navLang = navigator.language || navigator.languages?.[0] || 'fr';
+ if (navLang.startsWith('en')) {
+ window.location.replace('/en' + window.location.pathname);
+ return;
+ }
+ } else if (localStorage.getItem('wg_lang') === 'en') {
+ window.location.replace('/en' + window.location.pathname);
+ return;
+ }
+
const initialPath = normalizePath(window.location.pathname);
await loadSlide(initialPath);
diff --git a/src/views/Article.svelte b/src/views/Article.svelte
index 3abeee5..79b7d29 100644
--- a/src/views/Article.svelte
+++ b/src/views/Article.svelte
@@ -4,6 +4,7 @@
* Reçoit les données article via props, pas via le slide system.
*/
import Footer from '@components/layout/Footer.svelte'
+ import { t } from '@i18n'
let { data, onBack } = $props()
@@ -25,27 +26,27 @@
-
+
{#if copySuccess}
-
Lien copié !
+
{t('link_copied')}
{/if}
@@ -74,23 +75,23 @@
-
Partager cet article
+
{t('share_article')}
@@ -98,7 +99,7 @@
{#if data.related?.length}
- Nos recommandations
+ {t('related')}