-
+
diff --git a/js/i18n.js b/js/i18n.js
new file mode 100644
index 0000000..28a887f
--- /dev/null
+++ b/js/i18n.js
@@ -0,0 +1,60 @@
+const SUPPORTED = ['fr', 'en'];
+const FALLBACK = 'fr';
+const STORAGE_KEY = 'cv-lang';
+
+let translations = {};
+let currentLang = FALLBACK;
+
+function detectLang() {
+ const stored = localStorage.getItem(STORAGE_KEY);
+ if (stored && SUPPORTED.includes(stored)) return stored;
+ const browser = (navigator.language || '').slice(0, 2).toLowerCase();
+ return SUPPORTED.includes(browser) ? browser : FALLBACK;
+}
+
+async function loadTranslations(lang) {
+ const res = await fetch(`js/locales/${lang}.json`);
+ return res.json();
+}
+
+// Resolve a dot-notation key like "form.nom"
+function resolve(key) {
+ return key.split('.').reduce((obj, k) => obj?.[k], translations) ?? key;
+}
+
+// Replace {{var}} placeholders
+export function t(key, vars = {}) {
+ let str = resolve(key);
+ for (const [k, v] of Object.entries(vars)) {
+ str = str.replaceAll(`{{${k}}}`, v);
+ }
+ return str;
+}
+
+export function getLang() {
+ return currentLang;
+}
+
+// Apply translations to all elements with [data-i18n] and [data-i18n-placeholder]
+function applyTranslations() {
+ document.querySelectorAll('[data-i18n]').forEach(el => {
+ el.textContent = t(el.dataset.i18n);
+ });
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
+ el.placeholder = t(el.dataset.i18nPlaceholder);
+ });
+ document.documentElement.lang = currentLang;
+}
+
+export async function changeLanguage(lang) {
+ if (!SUPPORTED.includes(lang)) return;
+ translations = await loadTranslations(lang);
+ currentLang = lang;
+ localStorage.setItem(STORAGE_KEY, lang);
+ applyTranslations();
+}
+
+export async function initI18n() {
+ const lang = detectLang();
+ await changeLanguage(lang);
+}
diff --git a/js/locales/en.json b/js/locales/en.json
new file mode 100644
index 0000000..72e07bd
--- /dev/null
+++ b/js/locales/en.json
@@ -0,0 +1,19 @@
+{
+ "header": {
+ "title": "CV Generator",
+ "subtitle": "Fill in your details to generate your CV"
+ },
+ "form": {
+ "nom": "Last name",
+ "nomPlaceholder": "Smith",
+ "prenom": "First name",
+ "prenomPlaceholder": "John",
+ "email": "Email",
+ "emailPlaceholder": "john.smith@email.com",
+ "titre": "Job title",
+ "titrePlaceholder": "Full Stack Developer",
+ "submit": "Generate my CV"
+ },
+ "success": "CV ready to be generated for {{prenom}} {{nom}}!",
+ "footer": "CV Generator © 2026"
+}
diff --git a/js/locales/fr.json b/js/locales/fr.json
new file mode 100644
index 0000000..25533d2
--- /dev/null
+++ b/js/locales/fr.json
@@ -0,0 +1,19 @@
+{
+ "header": {
+ "title": "CV Generator",
+ "subtitle": "Remplissez vos informations pour générer votre CV"
+ },
+ "form": {
+ "nom": "Nom",
+ "nomPlaceholder": "Dupont",
+ "prenom": "Prénom",
+ "prenomPlaceholder": "Jean",
+ "email": "Email",
+ "emailPlaceholder": "jean.dupont@email.com",
+ "titre": "Titre du poste",
+ "titrePlaceholder": "Développeur Full Stack",
+ "submit": "Générer mon CV"
+ },
+ "success": "CV prêt à être généré pour {{prenom}} {{nom}} !",
+ "footer": "CV Generator © 2026"
+}
diff --git a/js/main.js b/js/main.js
index de8c496..4a4c045 100644
--- a/js/main.js
+++ b/js/main.js
@@ -3,6 +3,7 @@ import { collectCompetences, renderCompetences } from './sections/competences.js
import { collectFormation, renderFormation } from './sections/formation.js';
import { collectExperience, renderExperience } from './sections/experience.js';
import { collectHobbies, renderHobbies } from './sections/hobbies.js';
+import { initI18n, t, changeLanguage, getLang } from './i18n.js';
function generateCV() {
const identite = collectIdentite();
@@ -19,12 +20,29 @@ function generateCV() {
const success = document.getElementById('success');
if (success) {
- success.textContent = `CV prêt à être généré pour ${identite.prenom} ${identite.nom} !`;
+ success.textContent = t('success', { prenom: identite.prenom, nom: identite.nom });
success.style.display = 'block';
}
}
-document.addEventListener('DOMContentLoaded', () => {
+function updateActiveLangButton() {
+ const lang = getLang();
+ document.querySelectorAll('.lang-btn').forEach(btn => {
+ btn.classList.toggle('active', btn.dataset.lang === lang);
+ });
+}
+
+document.addEventListener('DOMContentLoaded', async () => {
+ await initI18n();
+ updateActiveLangButton();
+
+ document.querySelectorAll('.lang-btn').forEach(btn => {
+ btn.addEventListener('click', async () => {
+ await changeLanguage(btn.dataset.lang);
+ updateActiveLangButton();
+ });
+ });
+
document.getElementById('cv-form')?.addEventListener('submit', (e) => {
e.preventDefault();
generateCV();