Implement en/fr localization across admin and user UIs

Introduce client-side localization helper and en/fr locale bundles.

Route admin control, picks, movies, stats, winners and settings UIs through i18n keys instead of hardcoded text.

Remove French text from the English UI and standardize shared messages (saving, network errors, confirmations).

Update .gitignore to ignore editor metadata and localization helper artifacts.
This commit is contained in:
Mathieu Moran
2026-02-01 20:20:02 -05:00
parent dd313f8f5e
commit 4308d6babf
31 changed files with 3063 additions and 811 deletions
+34 -18
View File
@@ -3,11 +3,26 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pool Oscars</title>
<title data-i18n="common.oscarPool">Oscar Pool</title>
<script src="./js/app_theme.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="./css/app.css" rel="stylesheet">
<style>
/* Hide translatable content until translations are ready - prevents flash during page navigation */
body:not(.i18n-ready) [data-i18n] {
opacity: 0;
transition: opacity 0.1s ease-in;
}
body.i18n-ready [data-i18n] {
opacity: 1;
}
/* Fallback: show content after 2 seconds even if JS fails */
body:not(.i18n-ready) [data-i18n] {
animation: show-after-delay 0.1s 2s forwards;
}
@keyframes show-after-delay {
to { opacity: 1; }
}
/* Ensure the body takes the full height */
html, body {
height: 100%;
@@ -22,9 +37,10 @@
}
</style>
<script src="./js/localization.js" defer></script>
</head>
<body class="bg-light">
<button class="theme-toggle theme-toggle--floating" type="button" data-theme-toggle role="switch" aria-checked="false" aria-label="Basculer le mode sombre">
<button class="theme-toggle theme-toggle--floating" type="button" data-theme-toggle role="switch" aria-checked="false" data-i18n-aria-label="common.toggleDarkMode" aria-label="Toggle dark mode">
<span class="theme-toggle-icon theme-toggle-icon-sun" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="4"></circle>
@@ -50,52 +66,52 @@
<!-- First-time setup card (hidden by default) -->
<div id="setup-card" class="card p-4 d-none" style="max-width: 450px; margin: auto;">
<div class="text-center mb-3">
<img src="img/logo.png" class="logo-invert" style="width: 80%;" alt="Pool Oscars">
<img src="img/logo.png" class="logo-invert" style="width: 80%;" alt="Oscar Pool">
</div>
<div class="alert alert-info mb-3">
<strong>Première installation</strong><br>
Aucun administrateur n'existe. Créez le premier compte admin pour commencer.
<strong data-i18n="auth.firstTimeSetup">First time setup</strong><br>
<span data-i18n="auth.noAdminExists">No administrator exists. Create the first admin account to get started.</span>
</div>
<form id="setup-form">
<div class="mb-3">
<label for="setup-name" class="form-label">Nom</label>
<input type="text" class="form-control" id="setup-name" placeholder="Votre nom" required>
<label for="setup-name" class="form-label" data-i18n="auth.name">Name</label>
<input type="text" class="form-control" id="setup-name" data-i18n-placeholder="auth.namePlaceholder" placeholder="Your name" required>
</div>
<div class="mb-3">
<label for="setup-email" class="form-label">Courriel</label>
<input type="email" class="form-control" id="setup-email" placeholder="admin@exemple.com" required>
<label for="setup-email" class="form-label" data-i18n="auth.email">Email</label>
<input type="email" class="form-control" id="setup-email" data-i18n-placeholder="auth.emailPlaceholder" placeholder="admin@example.com" required>
</div>
<div class="mb-3">
<label for="setup-password" class="form-label">Mot de passe</label>
<label for="setup-password" class="form-label" data-i18n="auth.password">Password</label>
<input type="password" class="form-control" id="setup-password" minlength="6" required>
<div class="form-text">Minimum 6 caractères</div>
<div class="form-text" data-i18n="auth.passwordMin" data-i18n-params='{"min": 6}'>Minimum 6 characters</div>
</div>
<div class="mb-3">
<label for="setup-password-confirm" class="form-label">Confirmer le mot de passe</label>
<label for="setup-password-confirm" class="form-label" data-i18n="auth.confirmPassword">Confirm password</label>
<input type="password" class="form-control" id="setup-password-confirm" minlength="6" required>
</div>
<div id="setup-error" class="alert alert-danger d-none" role="alert"></div>
<button type="submit" class="btn btn-warning w-100">Créer le compte admin</button>
<button type="submit" class="btn btn-warning w-100" data-i18n="auth.createAdminAccount">Create admin account</button>
</form>
</div>
<!-- Normal login card -->
<div id="login-card" class="card p-4" style="max-width: 400px; margin: auto;">
<div class="text-center mb-4">
<img src="img/logo.png" class="logo-invert" style="width: 80%;" alt="Pool Oscars">
<img src="img/logo.png" class="logo-invert" style="width: 80%;" alt="Oscar Pool">
</div>
<form id="login-form">
<div class="mb-3">
<label for="email" class="form-label">Courriel</label>
<label for="email" class="form-label" data-i18n="auth.email">Email</label>
<input type="email" class="form-control" id="email" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Mot de passe</label>
<label for="password" class="form-label" data-i18n="auth.password">Password</label>
<input type="password" class="form-control" id="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Connexion</button>
<button type="submit" class="btn btn-primary w-100" data-i18n="auth.login">Login</button>
</form>
<p class="mt-3 text-center">Pas de compte? <a href="/register.html">Enregistrez-vous</a></p>
<p class="mt-3 text-center"><span data-i18n="auth.noAccount">No account?</span> <a href="/register.html" data-i18n="auth.signUp">Sign up</a></p>
</div>
</div>