i18n: german ui, readme overhaul

compose: unless-stopped
add source code, license, author notice at the bottom of index page
This commit is contained in:
NetMan 2025-12-17 17:48:06 +01:00
parent 1100abf141
commit 08e2060245
4 changed files with 148 additions and 127 deletions

View file

@ -2,40 +2,35 @@
## Required
The [db-prawo-jazdy](https://git.mandarynki.eu/netman/db-prawo-jazdy) project is designed for this one in mind, so use it in conjunction with this - visit it for more details
### db
^^ Above is kind of outdated, project moved from PostgreSQL to SQLite - I included a `database.db` file in the `db/` folder, although it has older dataset of questions from the Ministry - foreshadowing...
A python script at [db-prawo-jazdy](https://git.mandarynki.eu/netman/db-prawo-jazdy) is designed to convert the CSV(s) to a SQLite database for use here. When you acquire your database via that script, insert it at your desired path (default `./db/database.db`) and change the `DATABASE_URL` environment value in `.env`. Visit [db-prawo-jazdy](https://git.mandarynki.eu/netman/db-prawo-jazdy) for more details.
You also need the exam media files from the (Ministry of Infrasture)[https://www.gov.pl/web/infrastruktura/prawo-jazdy] - the latest files should be there
The database from November 2025 is included here at `./db/database.db`
The newest at the moment of me writing this (December 16th 2025) are the (visualisations for questions from November 2025)[https://www.gov.pl/pliki/mi/pytania_egzaminacyjne_na_prawo_jazdy_11_2025.zip]
### media
# To-do:
A shell script at [media-prawo-jazdy](https://git.mandarynki.eu/netman/media-prawo-jazdy) for media files ([available on the website of the Ministry of Infrastructure](https://www.gov.pl/web/infrastruktura/prawo-jazdy) under the link title `Pytania egzaminacyjne na prawo jazdy`) will convert (copy) all `.wmv` files to `.mp4` and copy all `.jpeg` files to `.jpg`. When you have all media and the script completed successfully, host these files on a webserver of your choice (e.g. apache, nginx), and change the `CDN_URL` environment value in `.env` to your webserver URL including the folder path to the media files. Visit [media-prawo-jazdy](https://git.mandarynki.eu/netman/media-prawo-jazdy) for more details.
## To-do:
- [x] re-forge database structure (good for now)
- [x] choose category (good for now)
- [x] come up with how to show results appropriately
- [x] better answer click recognition
- [x] beautify website (good for now)
- [x] <b>Fixed?</b> Needs testing, but should be fine question-mark? - fix pinia middleware between pages, MAJOR ISSUE - finishing exam sometimes redirects to homepage instead of results
- [x] <b>Fixed?</b> Needs more testing, but should be fine. (question-mark?) - middleware between pages; finishing exam sometimes redirects to homepage instead of results, major issue
- [x] question timers
- [x] exam (& results?) warning leave message on exit and timer end (and definitely on refresh)
- [x] add keybinds:
- S - start
- D - nast.pyt
- X - koniec egzaminu (na pewno chcesz zakonczyc egzamin?)
- T - Tak
- N - Nie
- A - A
- B - B
- C - C
- S - start, D - next question, X - exam end, T/Y - yes, N - no, A - A, B - B, C - C
- [ ] i18n - pl, en, de, ua (not all questions are available in ua, api handle)
- [ ] UI i18n
- [x] pl
- [x] en
- [ ] de
- [x] de
- [ ] ua
- [x] db: examstore add language field, api handle languages (questions lang)
- [ ] nuxt3 -> nuxt4
- [ ] clean up js code in exam.vue and result.vue (currently a little bit of a mess)
## Some information about the project
@ -50,16 +45,14 @@ This project is a website mimicking an official driver's license theoritical exa
## Setup
Copy `.env.example` to `.env` and modify it to values specified in the `Required#db` and `Required#media` sections according to your case.
This project utilizes `pnpm`, thus it is recommended
```bash
pnpm install
```
Check out `.env.example`, mainly CDN_URL for media http url
As of Dec. 16 2025 a test database is included at `./db/database.db`
### Development Server
Start the development server on `http://localhost:3000`:

View file

@ -1,5 +1,6 @@
services:
prawojazdy:
restart: unless-stopped
container_name: prawojazdy
build: .
ports:

View file

@ -1,79 +1,79 @@
{
"mainTitle": "Test na prawo jazdy",
"loading": "Ładowanie",
"keybinds": "Skróty klawiszowe",
"mainTitle": "Führerscheinprüfung",
"loading": "Laden",
"keybinds": "Tastenkürzel",
"bindedKeys": {
"S": "niebieski przycisk start",
"D": "następne pytanie",
"X": "zakończ egzamin",
"T / Y": "Tak",
"N": "Nie",
"S": "blauer „Start“-Button",
"D": "nächste Frage",
"X": "Prüfung beenden",
"T / Y": "Ja",
"N": "Nein",
"A": "A",
"B": "B",
"C": "C"
},
"yes": "Tak",
"no": "Nie",
"theme": "Motyw",
"light": "Jasny",
"dark": "Ciemny",
"auto": "Automatyczny",
"endExam": "Zakończ egzamin",
"examEnd": "Koniec egzaminu",
"basicQuestions": "Pytania podstawowe",
"advancedQuestions": "Pytania specjalistyczne",
"timeToGetAcquaintedWithTheQuestion": "Czas na zapoznanie się z pytaniem",
"timeForAnswer": "Czas na udzielenie odpowiedzi",
"yes": "Ja",
"no": "Nein",
"theme": "Design",
"light": "Hell",
"dark": "Dunkel",
"auto": "Automatisch",
"endExam": "Prüfung beenden",
"examEnd": "Prüfung beendet",
"basicQuestions": "Grundfragen",
"advancedQuestions": "Spezialfragen",
"timeToGetAcquaintedWithTheQuestion": "Zeit zum Lesen der Frage",
"timeForAnswer": "Antwortzeit",
"startBtn": "START",
"second": "s",
"nextQuestion": "Następne pytanie",
"goBackToHomePage": "Wróć na stronę główną",
"points": "Punkty",
"pointValue": "Wartość punktowa",
"currentCategory": "Aktualna kategoria",
"timeToExamEnd": "Czas do końca egzaminu",
"result": "Wynik",
"startAgain": "Rozpocznij jeszcze raz",
"viewAnswers": "Przejrzyj odpowiedzi",
"doYouReallyWantToEndExam": "Czy na pewno chcesz zakończyć egzamin?",
"questionWithoutVisual": "Pytanie bez wizualizacji",
"categoryWord": "Kategoria",
"anAnomalyHasOccured": "Nastąpiła anomalia",
"redirectFrom": "Przekierowanie z",
"end": "Koniec",
"question": "Pytanie",
"anAPIErrorOccured": "Wystąpił błąd z API",
"positive": "pozytywny",
"negative": "negatywny",
"theoreticalExam": "Egzamin teorytyczny",
"nextQuestion": "Nächste Frage",
"goBackToHomePage": "Zur Startseite",
"points": "Punkte",
"pointValue": "Punktwert",
"currentCategory": "Aktuelle Kategorie",
"timeToExamEnd": "Verbleibende Prüfungszeit",
"result": "Ergebnis",
"startAgain": "Erneut starten",
"viewAnswers": "Antworten anzeigen",
"doYouReallyWantToEndExam": "Möchten Sie die Prüfung wirklich beenden?",
"questionWithoutVisual": "Frage ohne Visualisierung",
"categoryWord": "Kategorie",
"anAnomalyHasOccured": "Ein unerwarteter Fehler ist aufgetreten",
"redirectFrom": "Weiterleitung von",
"end": "Ende",
"question": "Frage",
"anAPIErrorOccured": "API-Fehler ist aufgetreten",
"positive": "Positiv",
"negative": "Negativ",
"theoreticalExam": "Theoretische Prüfung",
"category": {
"description": {
"A": "motocykle bez ograniczeń mocy",
"B": "⭐ samochody osobowe do 3,5 t",
"C": "pojazdy ciężarowe powyżej 3,5 t",
"D": "autobusy",
"T": "ciągniki rolnicze i pojazdy wolnobieżne",
"AM": "motorowery i lekkie czterokołowce",
"A1": "motocykle do 125 cm³ i 11 kW",
"A2": "motocykle do 35 kW",
"B1": "czterokołowce (np. quady)",
"C1": "pojazdy od 3,5 t do 7,5 t",
"D1": "autobusy do 16 pasażerów",
"PT": "tramwaje"
"A": "Motorräder ohne Leistungsbegrenzung",
"B": "⭐ Personenkraftwagen bis 3,5 t",
"C": "Lastkraftwagen über 3,5 t",
"D": "Busse",
"T": "Landwirtschaftliche Traktoren und langsam fahrende Fahrzeuge",
"AM": "Mopeds und leichte Quadricycles",
"A1": "Motorräder bis 125 cm³ und 11 kW",
"A2": "Motorräder bis 35 kW",
"B1": "Quadricycles (z. B. Quads)",
"C1": "Fahrzeuge von 3,5 t bis 7,5 t",
"D1": "Busse mit bis zu 16 Fahrgastplätzen",
"PT": "Straßenbahnen"
},
"age": {
"A": "(24 lata; lub 20 lat jeśli masz kat. A2 min. 2 lata)",
"B": "(18 lat)",
"C": "(21 lat; lub 18 lat z kwalifikacją wstępną)",
"D": "(24 lata; lub 21 lat z kwalifikacją wstępną)",
"T": "(16 lat)",
"AM": "(14 lat)",
"A1": "(16 lat)",
"A2": "(18 lat)",
"B1": "(16 lat)",
"C1": "(18 lat)",
"D1": "(21 lat; lub 18 lat z kwalifikacją wstępną)",
"PT": "(21 lat)"
"A": "(24 Jahre alt; oder 20 Jahre bei mindestens 2 Jahren Besitz der Klasse A2)",
"B": "(18 Jahre alt)",
"C": "(21 Jahre alt; oder 18 Jahre mit Grundqualifikation)",
"D": "(24 Jahre alt; oder 21 Jahre mit Grundqualifikation)",
"T": "(16 Jahre alt)",
"AM": "(14 Jahre alt)",
"A1": "(16 Jahre alt)",
"A2": "(18 Jahre alt)",
"B1": "(16 Jahre alt)",
"C1": "(18 Jahre alt)",
"D1": "(21 Jahre alt; oder 18 Jahre alt mit Grundqualifikation)",
"PT": "(21 Jahre alt)"
}
}
}

View file

@ -63,53 +63,80 @@ function themeAuto() {
<template>
<div>
<div v-if="!loading" class="text-3xl m-2 flex flex-col gap-2">
<span>{{ $t('mainTitle') }}</span>
<div class="flex gap-2">
<CountryFlag
class="block border border-1 border-black"
:country="langSelect != 'en' ? langSelect : 'gb'"
size="big"
/>
<select v-model="langSelect" class="select" @change="changeLanguage">
<option value="pl">Polish (Polski)</option>
<option value="en">English</option>
<option value="de">German (Deutsch)</option>
<option value="ua">Ukrainian (Українська)</option>
</select>
</div>
<div class="flex gap-2">
<label class="flex cursor-pointer gap-2 text-lg items-center">
{{ $t('theme') }}: {{ $t('light') }}
<input
v-model="dark"
type="checkbox"
value="dark"
class="toggle theme-controller"
@change="themeStore.set(dark ? 'dark' : 'light')"
<div v-if="!loading" class="text-3xl flex flex-col gap-2 min-h-dvh">
<div class="flex flex-col p-2 gap-2">
<span>{{ $t('mainTitle') }}</span>
<div class="flex gap-2">
<CountryFlag
class="block border border-1 border-black"
:country="langSelect != 'en' ? langSelect : 'gb'"
size="big"
/>
{{ $t('dark') }}
</label>
<div class="btn btn-soft btn-sm" @click="themeAuto()">
{{ $t('auto') }}
<select v-model="langSelect" class="select" @change="changeLanguage">
<option value="pl">Polish (Polski)</option>
<option value="en">English</option>
<option value="de">German (Deutsch)</option>
<option value="ua">Ukrainian (Українська)</option>
</select>
</div>
</div>
<div
class="flex flex-col flex-wrap gap-2 items-start p-4 bg-base-300 border-1 border-slate-500 rounded-xl w-fit"
>
<div
v-for="category in categories"
:key="`btn-${category}`"
class="flex flex-row gap-3 items-center"
>
<button class="btn btn-xl btn-secondary" @click="setAndGo(category)">
{{ category }}
</button>
<div class="flex flex-col text-sm">
<div>{{ $t(`category.description.${category}`) }}</div>
<div>{{ $t(`category.age.${category}`) }}</div>
<div class="flex gap-2">
<label class="flex cursor-pointer gap-2 text-lg items-center">
{{ $t('theme') }}: {{ $t('light') }}
<input
v-model="dark"
type="checkbox"
value="dark"
class="toggle theme-controller"
@change="themeStore.set(dark ? 'dark' : 'light')"
/>
{{ $t('dark') }}
</label>
<div class="btn btn-soft btn-sm" @click="themeAuto()">
{{ $t('auto') }}
</div>
</div>
<div
class="flex flex-col flex-wrap gap-2 items-start p-4 bg-base-300 border-1 border-slate-500 rounded-xl w-fit"
>
<div
v-for="category in categories"
:key="`btn-${category}`"
class="flex flex-row gap-3 items-center"
>
<button
class="btn btn-xl btn-secondary"
@click="setAndGo(category)"
>
{{ category }}
</button>
<div class="flex flex-col text-sm">
<div>{{ $t(`category.description.${category}`) }}</div>
<div>{{ $t(`category.age.${category}`) }}</div>
</div>
</div>
</div>
</div>
<div class="flex-1"></div>
<div class="text-sm text-center bg-base-200 p-1">
<!--
You need to leave such an unavoidable mention as one below of the
original software, its creator and license - this is required under AGPL.
START MENTION
-->
<NuxtLink
class="link"
to="https://git.mandarynki.eu/netman/nuxt-prawo-jazdy"
>
nuxt-prawo-jazdy
</NuxtLink>
by
<NuxtLink class="link" to="https://netman.ovh">netman</NuxtLink>
(<NuxtLink
class="link"
to="https://git.mandarynki.eu/netman/nuxt-prawo-jazdy/src/branch/main/LICENSE"
>AGPL-3.0-only</NuxtLink
>)
<!-- END MENTION -->
</div>
</div>
<LoadingScreen v-else />