This repository is a single-repo setup that contains:
- the upstream LibreLingo Community web app (as a fork),
- a custom Japanese course for Italian speakers,
- scripts + a JSON “source of truth” used to generate all course files.
The course content (text + images) is my own work-in-progress study material.
LibreLingo courses are ultimately represented as YAML (course → modules → skills).
In this repository, I keep course content in a single JSON specification and generate the LibreLingo YAML from it.
- Reproducibility: the course can be regenerated deterministically from one file.
- Bulk editing: easier to refactor categories/lessons/items than editing many YAML files by hand.
- Consistency: IDs, slugs, thumbnails, and image variants are produced in a uniform way.
- Upstream compatibility: the output is still the standard LibreLingo YAML + the standard web export format.
- Node.js + npm (to run the web app)
- Python 3 for the generator scripts (
ll02), pluspip git(and optionallygit-lfsif your fork uses LFS assets)
LibreLingo’s exporter tooling (invoked by exportYamlCourse.sh) uses the repo’s own Python tooling under src/.
It may create a dedicated virtualenv under src/.venv automatically.
From the repo root:
- Install Node dependencies:
npm install- Create a Python venv for the generator scripts and install requirements:
python3 -m venv .venv
./.venv/bin/pip install --upgrade pip wheel setuptools
./.venv/bin/pip install -r data/course-script/requirements.txt- Install the SvelteKit static adapter:
npm -w @librelingo/web i -D @sveltejs/adapter-static- Installation of the Japanese TTS used by
ll02_generate_course_from_json.py
- Info:
./setup-aivis-jp-tts.sh --help - Setup:
./setup-aivis-jp-tts.sh --no-interactive --voice-uuid "59f96896-64d2-4378-830a-4d5feb3d81aa"(It needs to perform a very heavy download. If it times out, run it again) - Test 1:
aivis-jp-tts "こんにちは。テストです。" ~/Music/test1.mp3 - Test 2:
aivis-jp-tts --speed 1.0 "こんにちは。テストです。" ~/Music/test2.mp3
- Edit (JSON source-of-truth):
- GUI editor:
python3 data/course-json/jp_course_editor.py data/course-json/jp_course.json - Smoke test (no GUI):
python3 data/course-json/jp_course_editor.py data/course-json/jp_course.json --self-test
- Images
- The images referenced by the JSON (PNG or JPG) are placed in
data/course-img/. - Images should ideally be square and high-resolution.
- The generator will create 3 resized variants (for performance) under
apps/web/static/images/.
- Generate YAML: run:
./.venv/bin/python data/course-script/ll02_generate_course_from_json.py \
--repo . \
--spec data/course-json/jp_course.json \
--prune-courseto produce:
courses/japanese-from-italian/(generated LibreLingo YAML course)apps/web/static/images/(generated image variants used by the web app)
- Export for the web app: run:
bash scripts/exportYamlCourse.sh japanese-from-italianto produce:
apps/web/src/courses/japanese-from-italian/(web-exported JSONcourseData.json, challenges, introductions, etc.)
- Run the web app (development) run:
npm run web-serveThen open:
http://localhost:5173/
- Build the production static web app locally (for Apache or Nginx) run:
npm run -w @librelingo/web buildThen the static output is in:
apps/web/build
Deploy: copy the contents of apps/web/build/ to your web server docroot (example: /var/www/nihonjindes/) and serve it at /.
Local preview:
npm run -w @librelingo/web previewThen open:
http://localhost:4173/
Important: this build uses
200.htmlas the SPA fallback. Make sure both the index and the fallback point to200.html.
server {
listen 80;
server_name example.com;
root /var/www/nihonjindes;
index index.html 200.html;
location / {
try_files $uri $uri/ /200.html;
}
# Optional: cache immutable assets aggressively
location /_app/immutable/ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
}<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/nihonjindes
<Directory "/var/www/nihonjindes">
Require all granted
Options FollowSymLinks
AllowOverride None
# Serve 200.html at /
DirectoryIndex index.html 200.html
# SPA fallback
FallbackResource /200.html
</Directory>
</VirtualHost>If FallbackResource is not available/enabled on your Apache build, use a rewrite fallback instead:
<Directory "/var/www/nihonjindes">
Require all granted
Options FollowSymLinks
AllowOverride None
DirectoryIndex 200.html
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^ /200.html [L]
</Directory>- Do not edit generated YAML under
courses/japanese-from-italian/manually. Always update the JSON spec and regenerate. Here are two screenshots of the GUI editor that I created for this course:
-
data/course-json/jp_course.json
Single source of truth (“database”) for categories, lessons, items, and image paths. -
data/course-img/
Custom images referenced by the JSON. -
data/course-script/
Helper scripts:ll02_generate_course_from_json.py→ JSON → LibreLingo YAML course (and imports images)- (other helper scripts may be added over time)
-
courses/japanese-from-italian/
Generated LibreLingo YAML course (do not edit by hand; regenerate from JSON). -
apps/web/src/courses/japanese-from-italian/
Generated web-export JSON (created byexportYamlCourse.sh).
I added this file to redirect directly to the Japanese course:
apps/web/src/routes/+page.ts
import { redirect } from '@sveltejs/kit';
import { base } from '$app/paths';
export const load = () => {
throw redirect(302, `${base}/course/japanese-from-italian`);
};- Upstream: LibreLingo Community
- Live instance: https://www.nihonjindes.net/

