+/** Tab sidebar for the social page (friends / orgs / requests). */
+export default function SocialSidebar({ current, setCurrent }: Props): React.JSX.Element {
+ const { t } = useTranslation()
+ return (
+
- Social
+ {t('universe.socialPage.sidebarTitle')}
-
- Amis, organisations, demandes.
+ {t('universe.socialPage.sidebarSubtitle')}
- {items.map((item) => (
+ {TAB_KEYS.map((item) => (
))}
)
-}
\ No newline at end of file
+}
diff --git a/src/renderer/src/content/lore/origins.md b/src/renderer/content/lore/origins.md
similarity index 82%
rename from src/renderer/src/content/lore/origins.md
rename to src/renderer/content/lore/origins.md
index bbfd2e7..0611b15 100644
--- a/src/renderer/src/content/lore/origins.md
+++ b/src/renderer/content/lore/origins.md
@@ -12,4 +12,4 @@ Ils dominaient la réalité avant la fracture.
## La fracture
-> Un événement qui a changé l’univers à jamais.
\ No newline at end of file
+> Un événement qui a changé l’univers à jamais.
diff --git a/src/renderer/env.d.ts b/src/renderer/env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/renderer/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/src/renderer/src/hooks/useFitWindowToContent.ts b/src/renderer/hooks/useFitWindowToContent.ts
similarity index 61%
rename from src/renderer/src/hooks/useFitWindowToContent.ts
rename to src/renderer/hooks/useFitWindowToContent.ts
index 18b13f2..012c2e7 100644
--- a/src/renderer/src/hooks/useFitWindowToContent.ts
+++ b/src/renderer/hooks/useFitWindowToContent.ts
@@ -1,17 +1,21 @@
import { useEffect } from 'react'
-/** Agrandit la fenêtre si le contenu dépasse la taille de base (min = variables d'env). */
+/**
+ * Observes layout changes and asks the main process to resize the frameless window
+ * so content is not clipped (respects min/max dimensions from env).
+ */
export function useFitWindowToContent(): void {
useEffect(() => {
let rafId = 0
+ /** Measures `#root` and invokes IPC resize on the next animation frame. */
const report = (): void => {
cancelAnimationFrame(rafId)
rafId = requestAnimationFrame(() => {
const root = document.getElementById('root')
if (!root) return
void window.api.fitWindowToContent({
- width: root.scrollWidth,
+ width: root.scrollWidth,
height: root.scrollHeight
})
})
@@ -21,7 +25,12 @@ export function useFitWindowToContent(): void {
ro.observe(document.body)
const mo = new MutationObserver(report)
- mo.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true })
+ mo.observe(document.body, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ characterData: true
+ })
report()
diff --git a/src/renderer/i18n/en.json b/src/renderer/i18n/en.json
index 499fbf4..a0e3636 100644
--- a/src/renderer/i18n/en.json
+++ b/src/renderer/i18n/en.json
@@ -1,102 +1,194 @@
-{
- "navbar": {
- "home": "Home",
- "social": "Social",
- "lore": "Lore",
- "support": "Donate",
- "openWebsite": "Open official website",
- "openDiscord": "Open Discord",
- "openWiki": "Open wiki",
- "donateUnavailable": "Support link not configured",
- "quit": "Quit application",
- "minimize": "Minimize",
- "close": "Close"
+{
+ "installProgress": {
+ "connecting": "Connecting to server…",
+ "checkingUpdates": "Checking for updates…",
+ "downloading": "Downloading… ({{downloaded}} / {{total}})",
+ "downloadingIndeterminate": "Downloading… ({{downloaded}})",
+ "preparing": "Preparing game folder…",
+ "extracting": "Extracting… ({{current}} / {{total}} files)",
+ "cleaning": "Cleaning up…",
+ "completeInstall": "Installation complete — v{{version}}",
+ "completeUpdate": "Update complete — v{{version}}"
+ },
+ "common": {
+ "cancel": "Cancel",
+ "loading": "...",
+ "close": "Close",
+ "send": "Send",
+ "create": "Create"
+ },
+ "navbar": {
+ "home": "Home",
+ "social": "Social",
+ "lore": "Lore",
+ "support": "Donate",
+ "openWebsite": "Open official website",
+ "openDiscord": "Open Discord",
+ "openWiki": "Open wiki",
+ "donateUnavailable": "Support link not configured",
+ "quit": "Quit application",
+ "minimize": "Minimize",
+ "close": "Close",
+ "languageFr": "Français",
+ "languageEn": "English",
+ "switchToFrench": "Switch to French",
+ "switchToEnglish": "Switch to English",
+ "envUniverse": "Universe",
+ "envTesting": "Universe Testing",
+ "brandDiscord": "Discord",
+ "brandWiki": "Wiki"
+ },
+ "universeTesting": {
+ "banner": "Testing environment — data is not from the live server"
+ },
+ "lore": {
+ "sidebarTitle": "Lore",
+ "sidebarSubtitle": "Articles, chronicles, archives.",
+ "selectArticle": "Select a lore article",
+ "fileNotFound": "File not found: {{file}}",
+ "articles": {
+ "origins": "Origins"
+ }
+ },
+ "friendStatus": {
+ "online": "Online",
+ "offline": "Offline",
+ "ingame": "In game",
+ "ingameWithGame": "In game • {{game}}"
+ },
+ "universe": {
+ "title": "Universe",
+ "account": {
+ "title": "Account",
+ "disconnected": "Not connected",
+ "connectedAs": "Logged in as {{username}}",
+ "login": "Login with Discord",
+ "logout": "Logout",
+ "subscription": "Subscription",
+ "loading": "Connecting...",
+ "cancel": "Cancel",
+ "unavailable": "Authentication not available for this environment.",
+ "unavailableBtn": "Unavailable"
},
- "universeTesting": {
- "banner": "Testing environment — data is not from the live server"
+ "updateAlert": {
+ "dismiss": "Dismiss alert",
+ "launcher": {
+ "title": "Launcher update available",
+ "discordHint": "The new version is available here:",
+ "discordLink": "Discord"
+ },
+ "game": {
+ "title": "New game version available"
+ },
+ "releaseOn": "Released {{date}}"
},
- "universe": {
- "title": "Universe",
- "account": {
- "title": "Account",
- "disconnected": "Not connected",
- "connectedAs": "Logged in as {{username}}",
- "login": "Login with Discord",
- "logout": "Logout",
- "subscription": "Subscription",
- "loading": "Connecting...",
- "cancel": "Cancel",
- "unavailable": "Authentication not available for this environment.",
- "unavailableBtn": "Unavailable"
+ "game": {
+ "title": "Game",
+ "play": "Play",
+ "playDisabledMaintenance": "The server is under maintenance — launching the game is disabled.",
+ "playDisabledUpdate": "A game update is available — install it before playing.",
+ "playDisabledAuth": "Sign in to launch the game.",
+ "playDisabledAuthLoading": "Checking your session…",
+ "playDisabledRunning": "The game is already running.",
+ "notInstalled": "Game not installed",
+ "players": "{{count}} players online",
+ "status": {
+ "online": "Available",
+ "degraded": "Issues ongoing",
+ "offline": "Offline",
+ "maintenance": "Under maintenance",
+ "unknown": "Unknown",
+ "unavailable": "Unavailable"
},
- "updateAlert": {
- "launcher": {
- "title": "Launcher update available",
- "discordHint": "The new version is available here:",
- "discordLink": "Discord"
+ "viewStatus": "View status"
+ },
+ "files": {
+ "title": "Game files",
+ "version": "Version: {{version}}",
+ "releaseDate": "Release: {{date}}",
+ "notInstalled": "Game not installed",
+ "install": "Install",
+ "update": "Update",
+ "verify": "Verify files",
+ "clearCache": "Clear cache",
+ "clearCacheToastOk": "Cache cleared.",
+ "clearCacheToastPartial": "Cache partially cleared (some folders could not be removed).",
+ "clearCacheToastError": "Could not clear the cache.",
+ "changelog": "View changelog",
+ "changelogModalTitle": "Changelog — v{{version}}",
+ "changelogLoading": "Loading changelog…",
+ "changelogMissing": "No CHANGELOG.md was found in the install folder (make sure it is included in the game archive).",
+ "installing": "Installing...",
+ "installPath": "Install folder",
+ "installPathPlaceholder": "/home/user/games/dyingstar",
+ "browse": "Browse",
+ "unavailable": "Installation not available for this environment.",
+ "unavailableBtn": "Unavailable"
+ },
+ "social": {
+ "title": "Social",
+ "noFriends": "No friends",
+ "seeAll": "See all",
+ "notifications": "{{count}} notifications"
+ },
+ "socialPage": {
+ "sidebarTitle": "Social",
+ "sidebarSubtitle": "Friends, organizations, requests.",
+ "friends": "Friends",
+ "organizations": "Organizations",
+ "requests": "Requests",
+ "addFriend": "Add friend",
+ "createOrga": "Create org",
+ "createOrgaLong": "Create organization",
+ "joinOrga": "Join",
+ "accept": "Accept",
+ "decline": "Decline",
+ "tabs": {
+ "friends": {
+ "label": "Friends",
+ "subtitle": "Manage your friends and status."
},
- "game": {
- "title": "New game version available"
+ "organizations": {
+ "label": "Organizations",
+ "subtitle": "Organizations, members, and invites."
},
- "releaseOn": "Released {{date}}"
+ "requests": {
+ "label": "Requests",
+ "subtitle": "Pending friend requests."
+ }
},
- "game": {
- "title": "Game",
- "play": "Play",
- "playDisabledMaintenance": "The server is under maintenance — launching the game is disabled.",
- "playDisabledUpdate": "A game update is available — install it before playing.",
- "playDisabledAuth": "Sign in to launch the game.",
- "playDisabledAuthLoading": "Checking your session…",
- "playDisabledRunning": "The game is already running.",
- "notInstalled": "Game not installed",
- "players": "{{count}} players online",
- "status": {
- "online": "Available",
- "degraded": "Issues ongoing",
- "offline": "Offline",
- "maintenance": "Under maintenance",
- "unknown": "Unknown",
- "unavailable": "Unavailable"
- },
- "viewStatus": "View status"
+ "addFriendForm": {
+ "title": "Add a friend",
+ "usernameLabel": "Exact username",
+ "usernamePlaceholder": "Enter a username...",
+ "success": "Request sent!"
+ },
+ "createOrgaForm": {
+ "title": "Create an organization",
+ "nameLabel": "Organization name",
+ "namePlaceholder": "Enter a unique name...",
+ "success": "Organization created!"
},
- "files": {
- "title": "Game files",
- "version": "Version: {{version}}",
- "releaseDate": "Release: {{date}}",
- "notInstalled": "Game not installed",
- "install": "Install",
- "update": "Update",
- "verify": "Verify files",
- "clearCache": "Clear cache",
- "clearCacheToastOk": "Cache cleared.",
- "clearCacheToastPartial": "Cache partially cleared (some folders could not be removed).",
- "clearCacheToastError": "Could not clear the cache.",
- "changelog": "View changelog",
- "changelogModalTitle": "Changelog — v{{version}}",
- "changelogLoading": "Loading changelog…",
- "changelogMissing": "No CHANGELOG.md was found in the install folder (make sure it is included in the game archive).",
- "installing": "Installing...",
- "installPath": "Install folder",
- "browse": "Browse",
- "unavailable": "Installation not available for this environment.",
- "unavailableBtn": "Unavailable"
+ "pendingRequests_one": "{{count}} pending request",
+ "pendingRequests_other": "{{count}} pending requests",
+ "emptyFriends": "No friends yet.",
+ "emptyOrganizations": "No organizations.",
+ "emptyRequests": "No pending requests.",
+ "orga": {
+ "members_one": "{{count}} member",
+ "members_other": "{{count}} members",
+ "memberBadge": "Member",
+ "joinAria": "Join organization {{name}}"
},
- "social": {
- "title": "Social",
- "noFriends": "No friends",
- "seeAll": "See all",
- "notifications": "{{count}} notifications"
+ "request": {
+ "acceptAria": "Accept request from {{name}}",
+ "declineAria": "Decline request from {{name}}"
},
- "socialPage": {
- "friends": "Friends",
- "organizations": "Organizations",
- "requests": "Requests",
- "addFriend": "Add friend",
- "createOrga": "Create organization",
- "joinOrga": "Join organization",
- "accept": "Accept",
- "decline": "Decline"
+ "errors": {
+ "friendRequestFailed": "User not found or request already sent.",
+ "orgaNameTaken": "This organization name is already taken.",
+ "joinOrgaFailed": "Could not join this organization."
}
}
- }
\ No newline at end of file
+ }
+}
diff --git a/src/renderer/i18n/fr.json b/src/renderer/i18n/fr.json
index 49d2a57..af40d76 100644
--- a/src/renderer/i18n/fr.json
+++ b/src/renderer/i18n/fr.json
@@ -1,102 +1,194 @@
{
- "navbar": {
- "home": "Accueil",
- "social": "Social",
- "lore": "Lore",
- "support": "Faire un don",
- "openWebsite": "Ouvrir le site officiel",
- "openDiscord": "Ouvrir Discord",
- "openWiki": "Ouvrir le wiki",
- "donateUnavailable": "Lien de soutien non configuré",
- "quit": "Quitter l'application",
- "minimize": "Réduire",
- "close": "Fermer"
+ "installProgress": {
+ "connecting": "Connexion au serveur…",
+ "checkingUpdates": "Recherche de mises à jour…",
+ "downloading": "Téléchargement… ({{downloaded}} / {{total}})",
+ "downloadingIndeterminate": "Téléchargement… ({{downloaded}})",
+ "preparing": "Préparation du dossier du jeu…",
+ "extracting": "Extraction… ({{current}} / {{total}} fichiers)",
+ "cleaning": "Nettoyage…",
+ "completeInstall": "Installation terminée — v{{version}}",
+ "completeUpdate": "Mise à jour terminée — v{{version}}"
+ },
+ "common": {
+ "cancel": "Annuler",
+ "loading": "...",
+ "close": "Fermer",
+ "send": "Envoyer",
+ "create": "Créer"
+ },
+ "navbar": {
+ "home": "Accueil",
+ "social": "Social",
+ "lore": "Lore",
+ "support": "Faire un don",
+ "openWebsite": "Ouvrir le site officiel",
+ "openDiscord": "Ouvrir Discord",
+ "openWiki": "Ouvrir le wiki",
+ "donateUnavailable": "Lien de soutien non configuré",
+ "quit": "Quitter l'application",
+ "minimize": "Réduire",
+ "close": "Fermer",
+ "languageFr": "Français",
+ "languageEn": "English",
+ "switchToFrench": "Passer en français",
+ "switchToEnglish": "Switch to English",
+ "envUniverse": "Universe",
+ "envTesting": "Universe Testing",
+ "brandDiscord": "Discord",
+ "brandWiki": "Wiki"
+ },
+ "universeTesting": {
+ "banner": "Environnement de test — les données ne sont pas celles du serveur live"
+ },
+ "lore": {
+ "sidebarTitle": "Lore",
+ "sidebarSubtitle": "Articles, chroniques, archives.",
+ "selectArticle": "Sélectionne un article du lore",
+ "fileNotFound": "Fichier introuvable : {{file}}",
+ "articles": {
+ "origins": "Origines"
+ }
+ },
+ "friendStatus": {
+ "online": "En ligne",
+ "offline": "Hors ligne",
+ "ingame": "En jeu",
+ "ingameWithGame": "En jeu • {{game}}"
+ },
+ "universe": {
+ "title": "Universe",
+ "account": {
+ "title": "Compte",
+ "disconnected": "Non connecté",
+ "connectedAs": "Connecté en tant que {{username}}",
+ "login": "Se connecter avec Discord",
+ "logout": "Se déconnecter",
+ "subscription": "Abonnement",
+ "loading": "Connexion en cours...",
+ "cancel": "Annuler",
+ "unavailable": "Authentification non disponible pour cet environnement.",
+ "unavailableBtn": "Indisponible"
},
- "universeTesting": {
- "banner": "Environnement de test — les données ne sont pas celles du serveur live"
+ "updateAlert": {
+ "dismiss": "Fermer l'alerte",
+ "launcher": {
+ "title": "Mise à jour du launcher disponible",
+ "discordHint": "La nouvelle version est disponible ici :",
+ "discordLink": "Discord"
+ },
+ "game": {
+ "title": "Nouvelle version du jeu disponible"
+ },
+ "releaseOn": "Sortie le {{date}}"
},
- "universe": {
- "title": "Universe",
- "account": {
- "title": "Compte",
- "disconnected": "Non connecté",
- "connectedAs": "Connecté en tant que {{username}}",
- "login": "Se connecter avec Discord",
- "logout": "Se déconnecter",
- "subscription": "Abonnement",
- "loading": "Connexion en cours...",
- "cancel": "Annuler",
- "unavailable": "Authentification non disponible pour cet environnement.",
- "unavailableBtn": "Indisponible"
+ "game": {
+ "title": "Jeu",
+ "play": "Jouer",
+ "playDisabledMaintenance": "Le serveur est en maintenance — le lancement du jeu est désactivé.",
+ "playDisabledUpdate": "Une mise à jour du jeu est disponible — installez-la avant de jouer.",
+ "playDisabledAuth": "Connectez-vous pour lancer le jeu.",
+ "playDisabledAuthLoading": "Vérification de la session…",
+ "playDisabledRunning": "Le jeu est déjà lancé.",
+ "notInstalled": "Jeu non installé",
+ "players": "{{count}} joueurs en ligne",
+ "status": {
+ "online": "Opérationnel",
+ "degraded": "Dégradé",
+ "offline": "Hors ligne",
+ "maintenance": "En maintenance",
+ "unknown": "Inconnu",
+ "unavailable": "Non disponible"
},
- "updateAlert": {
- "launcher": {
- "title": "Mise à jour du launcher disponible",
- "discordHint": "La nouvelle version est disponible ici :",
- "discordLink": "Discord"
+ "viewStatus": "Voir le statut"
+ },
+ "files": {
+ "title": "Fichiers du jeu",
+ "version": "Version : {{version}}",
+ "releaseDate": "Release : {{date}}",
+ "notInstalled": "Jeu non installé",
+ "install": "Installer",
+ "update": "Mettre à jour",
+ "verify": "Vérifier les fichiers",
+ "clearCache": "Vider le cache",
+ "clearCacheToastOk": "Cache vidé.",
+ "clearCacheToastPartial": "Cache partiellement vidé (certains dossiers n'ont pas pu être supprimés).",
+ "clearCacheToastError": "Impossible de vider le cache.",
+ "changelog": "Voir le changelog",
+ "changelogModalTitle": "Changelog — v{{version}}",
+ "changelogLoading": "Chargement du changelog…",
+ "changelogMissing": "Aucun fichier CHANGELOG.md n'a été trouvé dans l'installation (vérifie qu'il est bien inclus dans l'archive du jeu).",
+ "installing": "Installation en cours...",
+ "installPath": "Répertoire d'installation",
+ "installPathPlaceholder": "/home/utilisateur/jeux/dyingstar",
+ "browse": "Parcourir",
+ "unavailable": "Installation non disponible pour cet environnement.",
+ "unavailableBtn": "Indisponible"
+ },
+ "social": {
+ "title": "Social",
+ "noFriends": "Aucun ami",
+ "seeAll": "Voir tout",
+ "notifications": "{{count}} notifications"
+ },
+ "socialPage": {
+ "sidebarTitle": "Social",
+ "sidebarSubtitle": "Amis, organisations, demandes.",
+ "friends": "Amis",
+ "organizations": "Organisations",
+ "requests": "Demandes",
+ "addFriend": "Ajouter un ami",
+ "createOrga": "Créer une orga",
+ "createOrgaLong": "Créer une organisation",
+ "joinOrga": "Rejoindre",
+ "accept": "Accepter",
+ "decline": "Refuser",
+ "tabs": {
+ "friends": {
+ "label": "Amis",
+ "subtitle": "Gère ta liste d'amis et ton statut."
},
- "game": {
- "title": "Nouvelle version du jeu disponible"
+ "organizations": {
+ "label": "Organisations",
+ "subtitle": "Organisations, membres et invitations."
},
- "releaseOn": "Sortie le {{date}}"
+ "requests": {
+ "label": "Demandes",
+ "subtitle": "Demandes d'amis en attente."
+ }
},
- "game": {
- "title": "Jeu",
- "play": "Jouer",
- "playDisabledMaintenance": "Le serveur est en maintenance — le lancement du jeu est désactivé.",
- "playDisabledUpdate": "Une mise à jour du jeu est disponible — installez-la avant de jouer.",
- "playDisabledAuth": "Connectez-vous pour lancer le jeu.",
- "playDisabledAuthLoading": "Vérification de la session…",
- "playDisabledRunning": "Le jeu est déjà lancé.",
- "notInstalled": "Jeu non installé",
- "players": "{{count}} joueurs en ligne",
- "status": {
- "online": "Opérationnel",
- "degraded": "Dégradé",
- "offline": "Hors ligne",
- "maintenance": "En maintenance",
- "unknown": "Inconnu",
- "unavailable": "Non disponible"
- },
- "viewStatus": "Voir le statut"
+ "addFriendForm": {
+ "title": "Ajouter un ami",
+ "usernameLabel": "Pseudo exact",
+ "usernamePlaceholder": "Entrez un pseudo...",
+ "success": "Demande envoyée !"
+ },
+ "createOrgaForm": {
+ "title": "Créer une organisation",
+ "nameLabel": "Nom de l'organisation",
+ "namePlaceholder": "Entrez un nom unique...",
+ "success": "Organisation créée !"
},
- "files": {
- "title": "Fichiers du jeu",
- "version": "Version : {{version}}",
- "releaseDate": "Release : {{date}}",
- "notInstalled": "Jeu non installé",
- "install": "Installer",
- "update": "Mettre à jour",
- "verify": "Vérifier les fichiers",
- "clearCache": "Vider le cache",
- "clearCacheToastOk": "Cache vidé.",
- "clearCacheToastPartial": "Cache partiellement vidé (certains dossiers n’ont pas pu être supprimés).",
- "clearCacheToastError": "Impossible de vider le cache.",
- "changelog": "Voir le changelog",
- "changelogModalTitle": "Changelog — v{{version}}",
- "changelogLoading": "Chargement du changelog…",
- "changelogMissing": "Aucun fichier CHANGELOG.md n’a été trouvé dans l’installation (vérifie qu’il est bien inclus dans l’archive du jeu).",
- "installing": "Installation en cours...",
- "installPath": "Répertoire d'installation",
- "browse": "Parcourir",
- "unavailable": "Installation non disponible pour cet environnement.",
- "unavailableBtn": "Indisponible"
+ "pendingRequests_one": "{{count}} demande en attente",
+ "pendingRequests_other": "{{count}} demandes en attente",
+ "emptyFriends": "Aucun ami pour le moment.",
+ "emptyOrganizations": "Aucune organisation.",
+ "emptyRequests": "Aucune demande en attente.",
+ "orga": {
+ "members_one": "{{count}} membre",
+ "members_other": "{{count}} membres",
+ "memberBadge": "Membre",
+ "joinAria": "Rejoindre l'organisation {{name}}"
},
- "social": {
- "title": "Social",
- "noFriends": "Aucun ami",
- "seeAll": "Voir tout",
- "notifications": "{{count}} notifications"
+ "request": {
+ "acceptAria": "Accepter la demande de {{name}}",
+ "declineAria": "Refuser la demande de {{name}}"
},
- "socialPage": {
- "friends": "Amis",
- "organizations": "Organisations",
- "requests": "Requêtes",
- "addFriend": "Ajouter un ami",
- "createOrga": "Créer une organisation",
- "joinOrga": "Rejoindre",
- "accept": "Accepter",
- "decline": "Refuser"
+ "errors": {
+ "friendRequestFailed": "Utilisateur introuvable ou demande déjà envoyée.",
+ "orgaNameTaken": "Ce nom d'organisation est déjà pris.",
+ "joinOrgaFailed": "Erreur lors de la tentative."
}
}
- }
\ No newline at end of file
+ }
+}
diff --git a/src/renderer/i18n/index.ts b/src/renderer/i18n/index.ts
new file mode 100644
index 0000000..e42865a
--- /dev/null
+++ b/src/renderer/i18n/index.ts
@@ -0,0 +1,39 @@
+/** i18next instance: FR/EN translations for the renderer. */
+import i18n from 'i18next'
+import { initReactI18next } from 'react-i18next'
+
+import en from '../i18n/en.json'
+import fr from '../i18n/fr.json'
+
+const STORAGE_KEY = 'ds-language'
+
+function detectLanguage(): 'en' | 'fr' {
+ if (typeof localStorage !== 'undefined') {
+ const stored = localStorage.getItem(STORAGE_KEY)
+ if (stored === 'en' || stored === 'fr') return stored
+ }
+ if (typeof navigator !== 'undefined' && navigator.language.toLowerCase().startsWith('fr')) {
+ return 'fr'
+ }
+ return 'en'
+}
+
+void i18n.use(initReactI18next).init({
+ resources: {
+ en: { translation: en },
+ fr: { translation: fr }
+ },
+ lng: detectLanguage(),
+ fallbackLng: 'en',
+ interpolation: {
+ escapeValue: false
+ }
+})
+
+i18n.on('languageChanged', (lng) => {
+ if (typeof localStorage !== 'undefined') {
+ localStorage.setItem(STORAGE_KEY, lng)
+ }
+})
+
+export default i18n
diff --git a/src/renderer/index.html b/src/renderer/index.html
index 1a36909..1d065f0 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -12,6 +12,6 @@
-
+