diff --git a/css/form.css b/css/form.css index bc9398e..720789b 100644 --- a/css/form.css +++ b/css/form.css @@ -31,11 +31,50 @@ header { margin-bottom: 32px; } +.header-top { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + margin-bottom: 10px; +} + +.lang-switcher { + display: flex; + align-items: center; + gap: 4px; + font-size: 0.8rem; +} + +.lang-sep { + color: #4a4a6a; +} + +.lang-btn { + background: none; + border: none; + color: #4a4a6a; + cursor: pointer; + font-size: 0.8rem; + font-weight: 600; + padding: 2px 4px; + letter-spacing: 0.05em; + transition: color 0.2s ease; +} + +.lang-btn:hover { + color: #e94560; +} + +.lang-btn.active { + color: #e94560; +} + h1 { font-size: 2.2rem; font-weight: 700; color: #e94560; - margin-bottom: 10px; + margin-bottom: 0; } header p { diff --git a/index.html b/index.html index 06d87a8..fa9648e 100644 --- a/index.html +++ b/index.html @@ -10,8 +10,15 @@
-

CV Generator

-

Remplissez vos informations pour générer votre CV

+
+

CV Generator

+
+ + | + +
+
+

Remplissez vos informations pour générer votre CV

@@ -20,11 +27,12 @@
- +
- +
- +
- + - +

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();