diff --git a/.gitignore b/.gitignore index 7fddb09..b633661 100644 --- a/.gitignore +++ b/.gitignore @@ -165,6 +165,13 @@ eng.traineddata Thumbs.db desktop.ini +# VS Code project settings +.vscode/ + +# AI assistant/editor metadata +.claude/ +.cursor/ + # Python cache files __pycache__/ *.pyc @@ -173,4 +180,11 @@ __pycache__/ .Python # Temporary scripts -temp/ \ No newline at end of file +temp/ + +# Localization helper artifacts (not part of app) +BEFORE_AFTER_COMPARISON.md +LOCALIZATION_COMPLETE.md +LOCALIZATION_PROGRESS.md +VERIFICATION_REPORT.md +localize_remaining.py \ No newline at end of file diff --git a/README.md b/README.md index 7075e8c..5d367fb 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ ## Oscars Pool 2026 Little nodejs + mongodb app that I hack together with ChatGPT and Cursor to keep track of watched movies for an incoming Oscars Pool with some friends. -**Note:** The application interface is in French. +**Note:** The application interface now supports **English and French** via JSON locale bundles (default is English; the admin can force the site language from the Settings → Language tab). -**Disclaimer:** This was a fun, experimental project built with “AI slop” (Cursor), so it may have security issues. For example, `python_video_server` uses the default token‑in‑URL approach to serve video files, which can leave user tokens in logs or browser history. There are ways to improve this, but at this point I’m blinder than Ray Charles. +**Disclaimer:** This was a fun, experimental project built with “AI slop” (Cursor, Claude and Copilot), so it may have security issues. For example, `python_video_server` uses the default token‑in‑URL approach to serve video files, which can leave user tokens in logs or browser history. There are ways to improve this, but at this point I’m blinder than Ray Charles. - +|  |  | +| --- | --- | +|  |  | ## Features @@ -38,6 +40,41 @@ Little nodejs + mongodb app that I hack together with ChatGPT and Cursor to keep - **App version control**: manage app version display (footer) from admin panel (no GitHub dependency) - **Player UI settings**: control player page admin status display +## Localization (i18n) + +The app ships with a lightweight client-side localization system and two locales: **English** and **French**. + +- Locale files live in: + - `public/locales/en.json` + - `public/locales/fr.json` +- The i18n runtime is implemented in `public/js/localization.js` and exposed as `window.i18n`. +- The admin can choose the **site language** (currently `en` or `fr`) from the **Admin → Settings → Language** tab. This value is stored in MongoDB and used by the client to pick the active locale. +- Markup is localized via `data-i18n` attributes (and friends): + - `data-i18n` → element text content + - `data-i18n-placeholder` → input placeholders + - `data-i18n-aria-label` → ARIA labels + - `data-i18n-title` → `title` attributes +- JavaScript code uses a small helper to translate keys with an English fallback, for example: + + ```js + // inside public/js/* files + function t(key, fallback, params) { + if (window.i18n && typeof window.i18n.t === 'function') { + const base = window.i18n.t(key, params || {}); + return base && base !== key ? base : (fallback || key); + } + return fallback || key; + } + + showResponse('success', t('admin.movies.updatedSuccessfully', 'Movie updated successfully.')); + ``` + +To add new text: + +1. Add an English entry in `public/locales/en.json` (this is the base source of truth). +2. Optionally add the corresponding French translation to `public/locales/fr.json` using the same key. +3. In HTML, wire it with `data-i18n="your.key"`, or in JS call `t('your.key', 'English fallback')`. + ## Setup Create a `.env` file at the project root (same folder as `package.json`). @@ -66,6 +103,38 @@ Generate a good `JWT_SECRET`: openssl rand -hex 32 ``` +## First-time admin setup + +On a brand new database (no users with `role: 69`), the app exposes a simple first-time setup flow to create the initial admin account. + +1. **Start the app** + - Make sure `.env` has at least `MONGO_URI` and `JWT_SECRET`. + - Run: + + ```bash + npm install + node index.js + ``` + +2. **Open the home page** + - Visit `http://localhost:5050/` (or `http://localhost:${PORT}/` if you changed the port). + - If no admin user exists yet, you'll see an **admin setup card** instead of the regular login form. + - Fill in **name**, **email**, and **password**, then submit to create the first admin. The backend will: + - create a `User` document with `role: 69` (admin), and + - return a JWT so you're logged in immediately and redirected into the app. + +3. **Access the admin panel** + - Once logged in as admin, open the **Admin / Control** page (or navigate to `/control.html`). + - From there you can: + - Set the **active Oscar year** (Settings → Active year). + - Configure the **Oscar date** for the countdown. + - Adjust **points configuration** (points per movie / per correct pick). + - Control **picks and version visibility**. + - Choose the **site language** (Settings → Language → `en` / `fr`). + - Manage movies, categories, winners, users, and the 100% completion modal. + +After the first admin is created, the setup card disappears and the index page behaves as a normal login screen. + ## Database (MongoDB) This app uses **MongoDB** via Mongoose. Provide a connection string in `MONGO_URI`. @@ -85,7 +154,7 @@ This app uses **MongoDB** via Mongoose. Provide a connection string in `MONGO_UR The app also reads these environment variables (all optional): ```bash -PORT=5000 # server port (default: 5000) +PORT=5050 # server port (default: 5050) JWT_EXPIRES_IN=8h # JWT expiry (default: 8h) VIDEO_FILES_DIR=/abs/path/to/video # where server video files live (default: ./public/video) VIDEO_SESSION_MAX_AGE_SECONDS=28800 # video_auth cookie max-age (default: 8h) @@ -93,7 +162,7 @@ MOVIES_CACHE_TTL_MS=30000 # movies route cache TTL (default: 30000ms) ``` Notes: -- The server serves the UI from `public/` (open `http://localhost:5000` by default). +- The server serves the UI from `public/` (open `http://localhost:5050` by default unless you override `PORT`). - If you use `video_file` in a movie record, it must be a **relative path** under `VIDEO_FILES_DIR` (the backend blocks traversal/absolute paths). ## Python video server (optional) diff --git a/img/preview.gif b/img/preview.gif deleted file mode 100644 index 77b0ea2..0000000 Binary files a/img/preview.gif and /dev/null differ diff --git a/img/preview_admin.png b/img/preview_admin.png new file mode 100644 index 0000000..0d914dc Binary files /dev/null and b/img/preview_admin.png differ diff --git a/img/preview_checklist.png b/img/preview_checklist.png new file mode 100644 index 0000000..1739161 Binary files /dev/null and b/img/preview_checklist.png differ diff --git a/img/preview_movies.png b/img/preview_movies.png new file mode 100644 index 0000000..1b6789e Binary files /dev/null and b/img/preview_movies.png differ diff --git a/img/preview_stats.png b/img/preview_stats.png new file mode 100644 index 0000000..8a3f1a7 Binary files /dev/null and b/img/preview_stats.png differ diff --git a/public/change-password.html b/public/change-password.html index 7a2b011..c4aa2ef 100644 --- a/public/change-password.html +++ b/public/change-password.html @@ -3,13 +3,31 @@
-