diff --git a/.gitignore b/.gitignore index 568664f..05ed680 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ frontend/node_modules frontend/node_modules/* frontend/dist frontend/dist/* +frontend/scripts/.i18n-de-fr-cache.json +frontend/scripts/.falukant-fr-smooth-cache.json frontedtree.txt backend/dist/ backend/data/model-cache diff --git a/backend/migrations/20260402120000-add-ui-locale-fr-user-param-value.cjs b/backend/migrations/20260402120000-add-ui-locale-fr-user-param-value.cjs new file mode 100644 index 0000000..434b874 --- /dev/null +++ b/backend/migrations/20260402120000-add-ui-locale-fr-user-param-value.cjs @@ -0,0 +1,30 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface) { + await queryInterface.sequelize.query(` + INSERT INTO type.user_param_value (user_param_type_id, value, order_id) + SELECT upt.id, 'fr', COALESCE( + (SELECT MAX(v.order_id) FROM type.user_param_value v WHERE v.user_param_type_id = upt.id), + 0 + ) + 1 + FROM type.user_param upt + WHERE upt.description = 'language' + AND NOT EXISTS ( + SELECT 1 FROM type.user_param_value x + WHERE x.user_param_type_id = upt.id AND x.value = 'fr' + ); + `); + }, + + async down(queryInterface) { + await queryInterface.sequelize.query(` + DELETE FROM type.user_param_value v + USING type.user_param upt + WHERE v.user_param_type_id = upt.id + AND upt.description = 'language' + AND v.value = 'fr'; + `); + }, +}; diff --git a/backend/services/authService.js b/backend/services/authService.js index 78af60a..1c45785 100644 --- a/backend/services/authService.js +++ b/backend/services/authService.js @@ -144,7 +144,7 @@ export const loginUser = async ({ username, password }) => { const mappedParams = params.map(param => { return { 'name': param.paramType.description, 'value': param.value }; }); - const uiLocaleCodes = ['de', 'en', 'ceb', 'es']; + const uiLocaleCodes = ['de', 'en', 'ceb', 'es', 'fr']; const langEntry = mappedParams.find((p) => p.name === 'language'); if (langEntry?.value && !uiLocaleCodes.includes(langEntry.value)) { const idNum = parseInt(langEntry.value, 10); diff --git a/backend/utils/initializeTypes.js b/backend/utils/initializeTypes.js index a4e15c1..9a2dec7 100644 --- a/backend/utils/initializeTypes.js +++ b/backend/utils/initializeTypes.js @@ -67,7 +67,7 @@ const initializeTypes = async () => { } const valuesList = { gender: ['male', 'female', 'transfemale', 'transmale', 'nonbinary'], - language: ['de', 'en', 'ceb', 'es'], + language: ['de', 'en', 'ceb', 'es', 'fr'], eyecolor: ['blue', 'green', 'brown', 'black', 'grey', 'hazel', 'amber', 'red', 'other'], haircolor: ['black', 'brown', 'blonde', 'red', 'grey', 'white', 'other'], hairlength: ['short', 'medium', 'long', 'bald', 'other'], diff --git a/docs/KONZEPT_UI_SPRACHE_FRANZOESISCH.md b/docs/KONZEPT_UI_SPRACHE_FRANZOESISCH.md new file mode 100644 index 0000000..7d63c68 --- /dev/null +++ b/docs/KONZEPT_UI_SPRACHE_FRANZOESISCH.md @@ -0,0 +1,113 @@ +# Konzept: Französisch (`fr`) als Bedien-/UI-Sprache + +**Stand:** April 2026 +**Bezug:** Bestehende Locales `de`, `en`, `es`, `ceb` (Vue I18n, Store, SEO, Header). + +--- + +## 1. Ziel + +- Nutzer können die Oberfläche auf **Französisch** stellen (Endonym: **Français**). +- **Vollständige Key-Parität** mit der deutschen Referenz (`de`), analog zu **Spanisch** (`es`): keine stillen Lücken, die nur über Fallback sichtbar werden. +- **Locale-Code:** `fr` (BCP 47). Regionale Varianten (`fr-CA`, `fr-CH`) nur bei Bedarf später; vorerst eine gemeinsame `fr`-UI. + +--- + +## 2. Inhaltliche Leitplanken + +| Thema | Empfehlung | +|--------|------------| +| **Anrede** | Einheitlich festlegen: **„vous“** (formell) oder **„tu“** (duzt); für eine Community-Plattform oft **vous** bei Systemtexten, ggf. Ausnahmen in Spieldialogen abstimmen. **Umsetzung:** Die erste französische UI-Version wurde maschinell aus dem Deutschen erzeugt (`google-translate-api-x`); menschliche Nachbearbeitung sollte **vous** durchgängig prüfen (v. a. Social/Vocab/Falukant). | +| **Referenz für Keys** | `de` als maßgebliche Struktur; Übersetzung nach Französisch. | +| **Qualität** | Große Module (Falukant, Admin, Social/Vocab) iterativ oder durch TMS/Übersetzer; CI-Check auf Key-Parität `de` ↔ `fr`. | + +--- + +## 3. Technische Umsetzung (Frontend) + +### 3.1 Neue Übersetzungsdateien + +- Ordner: `frontend/src/i18n/locales/fr/` +- Dieselben **19 JSON-Dateien** wie unter `en` / `de` / `es` / `ceb` (z. B. `general.json`, `header.json`, … `seo.json`). + +### 3.2 I18n-Bundling + +- Datei: `frontend/src/i18n/index.js` + - Alle `fr*` importieren. + - Block `messages.fr = { ...frGeneral, …, ...frSeo }` analog zu `es`. + - **`fallbackLocale`:** gesetzt als **`fr: ['de']`** (Lücken fallen auf Deutsch zurück). +- **`ceb`** nutzt `deepMerge` über `en`+`ceb`; **`fr`** wie **`es`**: nur `fr`-Bundles, kein deepMerge nötig. + +### 3.3 Zentrale Liste unterstützter UI-Locales + +Aktuell mehrfach dieselbe Liste (`de`, `en`, `ceb`, `es`), u. a. in: + +- `frontend/src/store/index.js` +- `frontend/src/main.js` (`?lang=`, localStorage) +- `frontend/src/components/AppHeader.vue` +- `frontend/src/components/SettingsWidget.vue` +- `frontend/src/utils/seo.js` + +**Empfehlung:** Eine exportierte Konstante z. B. `frontend/src/i18n/supportedLocales.js` (`SUPPORTED_UI_LOCALES`, ggf. `SEO_UI_LOCALES` daraus ableiten oder gemeinsam pflegen) und alle Stellen darauf umstellen – **vor oder beim** Einführen von `fr`, damit keine Stelle vergessen wird. + +### 3.4 Sprachauswahl UI + +- **`AppHeader.vue`:** Option `{ value: 'fr', nativeLabel: 'Français' }`; `supported` aus Zentralmodul. +- **`SettingsWidget.vue`:** Nach Speichern der Profil-Sprache Store synchron halten; `languagesList` aktuell unvollständig vs. Header – **alle** UI-Sprachen (`de`, `en`, `ceb`, `es`, `fr`) konsistent anbieten oder vollständig aus API-`setting.options` speisen. + +### 3.5 Register / Einstellungen (JSON) + +- In `settings.personal.language` (und falls nötig `register.languages`) in **allen** relevanten Locales den Eintrag **`fr`** / **„Français“** ergänzen. + +### 3.6 SEO & Einstieg per URL + +- **`frontend/src/utils/seo.js`:** `fr` in die Locale-Liste aufnehmen; `hreflang` für `fr`; `og:locale` / Alternates wie bei bestehenden Sprachen. +- **`main.js`:** Query `?lang=fr` über zentrale erlaubte Locales freischalten. +- **`index.html`:** falls hreflang/noscript manuell gepflegt wird – `fr` ergänzen. + +### 3.7 Browser-Sprache (optional) + +- In `store` / `main.js`: Wenn `navigator.language` mit `fr` beginnt (z. B. `fr`, `fr-FR`, `fr-BE`, `fr-CH`), Standard-UI auf `fr` setzen – analog zu bestehender Logik für Deutsch und Bisaya. + +--- + +## 4. Backend + +- Falls die **Account-Sprache** serverseitig validiert oder als feste Optionsliste ausgeliefert wird: **`fr`** zur Whitelist / zu den Optionen hinzufügen. +- **Vocab-Sprachen** in der Datenbank (Kurse, Zielsprachen) sind unabhängig von der **UI-Locale**; keine Pflichtänderung für reines UI-Französisch. + +--- + +## 5. Qualitätssicherung & Abnahme + +- Skript: flache Key-Parität `de` vs. `fr` pro JSON-Datei (wie bereits für `es` genutzt). +- `npm run build` ohne Fehler. +- Manuell: `?lang=fr`, Wechsel im Header ohne vollständigen Reload, Smoke über Login, Einstellungen, eine Social-/Falukant-Ansicht. + +--- + +## 6. Definition of Done (Checkliste) + +- [x] `frontend/src/i18n/locales/fr/*.json` vollständig (Key-Parität zu `de`) +- [x] `i18n/index.js`: `fr` eingebunden, `fallbackLocale` für `fr` gesetzt (`['de']`) +- [x] Zentrale `SUPPORTED_UI_LOCALES` in `frontend/src/i18n/supportedLocales.js`; Store, `main.js`, Header, Settings-Widget, `seo.js` importieren daraus +- [x] `AppHeader`, `main.js`, `store`, `seo.js`, `RegisterDialog` um `fr` / Browser-Erkennung `fr*` erweitert +- [x] `settings` / `register` JSON: Sprache `fr` (und Register um `es` wo gefehlt) ergänzt +- [x] `SettingsWidget`: `languagesList` über alle UI-Sprachen aus `SUPPORTED_UI_LOCALES` +- [x] Backend: `initializeTypes` + `authService` + Migration `type.user_param_value` für `fr` +- [x] SEO/hreflang/`?lang=fr` + `index.html` Alternate + noscript `lang="fr"` +- [x] QA: `npm run i18n:check-parity` (de↔fr), `npm run build` +- [x] Anrede / MT-Hinweis in Abschnitt 2 (Tabelle) und Skript `frontend/scripts/generate-fr-locale-from-de.mjs` für Regenerierung + +--- + +## 7. Nächste Schritte (optional als Tickets) + +1. **Infra:** `supportedLocales.js` + `fr` in allen technischen Stellen + leere/kopierte `fr`-JSONs aus `de` als Stub. +2. **SEO:** hreflang, `seo.json` `fr`, `main.js` / `index.html`. +3. **Übersetzung:** modulweise (Core → Social → Falukant → Admin). +4. **QA & CI:** Key-Check im Workflow. + +--- + +*Ende des Konzepts.* diff --git a/frontend/index.html b/frontend/index.html index 2ce889f..0cfff60 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -29,6 +29,7 @@ + @@ -56,6 +57,11 @@
Ang YourPart usa ka plataporma alang sa komunidad, chat, forum, blog, trainer sa bokabularyo, ang browser game nga Falukant ug minigames.
Mga bahin: Blogs, Bokabularyo, Falukant, Minigames.
+YourPart est une plateforme pour la communauté, le chat, les forums, les blogs, l’entraînement au vocabulaire, le jeu de construction Falukant dans le navigateur et les mini-jeux.
+Zones principales : Blogs, Vocabulaire, Falukant et Mini-jeux.
+