Builds a static CV HTML from JSON data, an html/template layout file, and an optional photo. The serve command renders every locale once on startup, watches the inputs, and re-renders only when those files change; HTTP requests are served from an in-memory cache (locale switches do not trigger a re-render).
| Input | Purpose |
|---|---|
data/data.json |
CV content (schema) |
data/template.html |
HTML/CSS and {{ .Name }}-style template |
data/photo.jpg (or .png / .gif) |
Photo embedded as a data URL |
Generated outputs are written next to OUTPUT_FILE_PATH, one file per locale: e.g. with the default cv.html you get cv-en.html, cv-ru.html, … (the locale code is inserted before the extension).
From the repository root (with Go installed):
# Defaults expect files at ./data.json, ./template.html, ./photo.jpg — use env for ./data/…
export DATA_FILE_PATH=data/data.json
export TEMPLATE_FILE_PATH=data/template.html
export PHOTO_FILE_PATH=data/photo.jpg
go run ./cmd/cv serve # watch + HTTP (see SERVER_PORT)
go run ./cmd/cv generate # cv-<locale>.html for every supported locale
go run ./cmd/cv generate -lang=ru # only cv-ru.html (requires locales in data.json)generatewrites one HTML file per locale, usingOUTPUT_FILE_PATHas the base (cv.html→cv-en.html,cv-ru.html, …). Without-lang, every locale indata.json(plusdefaultLocale) is rendered. With-lang=<code>, only that locale is written (must exist inlocalesor bedefaultLocalewhenlocalesis set). Rungo run ./cmd/cv generate -hfor flags.serveruns the same render once for every locale on startup and on any change todata.json/template.html/ the photo, then serves the requested locale from the in-memory cache (locale switches via?lang=are zero-cost lookups).
Environment variables (see internal/config.go):
| Variable | Default | Description |
|---|---|---|
DATA_FILE_PATH |
data.json |
Path to CV JSON |
TEMPLATE_FILE_PATH |
template.html |
Path to HTML template |
PHOTO_FILE_PATH |
photo.jpg |
Path to image |
OUTPUT_FILE_PATH |
cv.html |
Base path for generate; -{locale} is inserted before the extension (e.g. cv-en.html). Also used as the on-disk mirror of the cache when serve regenerates. |
SERVER_PORT |
:80 |
Listen address for serve (include leading : if you pass only a port) |
In data.json, optional fields:
defaultLocale— e.g.en(used bygeneratewithout-lang, and as fallback inserve).locales— map of locale code →LocaleBundle: optionalui,about,location,languages,experience,education.
When locales is non-empty, the UI shows a language switcher. Active locale is chosen in order:
?lang=query (valid values: keys inlocalesplusdefaultLocale)cv_langcookie (set after a valid?lang=visit)Accept-Languageheader (first supported match)defaultLocale
Layout strings (section titles, employment type labels, “present” → Russian “настоящее время”, etc.) live alongside the rest of the content in data.json under the ui field, the same overlay style used by about, location, etc. — root values are the canonical defaults; per-locale ui overrides win field-by-field, and any key the locale omits falls back to root. Schema:
{
"ui": {
"contact": "...", "socials": "...", "languages": "...",
"about": "...", "experience": "...", "education": "...",
"savePDF": "...", "remote": "...",
"email": "...", "phone": "...", "location": "...",
"employmentTypes": { "full-time": "...", "part-time": "...", "contract": "...", "internship": "...", "freelance": "..." },
"endDate": { "present": "..." }
}
}Inside locales.<code>.ui, the same shape applies but every field is optional — only set the keys you want to override. Lookups in employmentTypes and endDate fall back to the raw input when no override is defined, so those maps can be partial too. Glue: internal/i18n.go, merge logic in internal/locale_merge.go.
Root JSON is the canonical CV (usually your primary language). Each locale under locales can override whole fields (about, …) or supply experience / education entries merged by index with the root list.
For locales.<code>.experience (and .education), each row uses LocalExperience / LocalEducation: omit a JSON key to keep the value from the default row at the same index (e.g. type, startDate, endDate, isRemote, skills). Present keys replace the default. Merge logic lives in internal/locale_merge.go.
Build from the repository root:
docker build -t cv .The image runs the cv binary; default command is serve on port 80 in the container.
Compose (from repo root):
docker compose -f ops/docker/docker-compose.yaml up --build -dThe sample compose sets TEMPLATE_FILE_PATH=/app/data/template.html so it matches the template volume mount (data/template.html → /app/data/template.html).
App URL: http://localhost:8000 (host port mapped in compose).
Run with Docker directly (adjust paths to match your mounts):
docker run --rm -p 8000:80 \
-e TEMPLATE_FILE_PATH=/app/data/template.html \
-v "$(pwd)/data/data.json:/app/data.json" \
-v "$(pwd)/data/template.html:/app/data/template.html" \
-v "$(pwd)/data/photo.jpg:/app/photo.jpg" \
cvOne-shot generate (example writes to ./out on the host):
mkdir -p out
docker run --rm \
-e TEMPLATE_FILE_PATH=/app/data/template.html \
-v "$(pwd)/data/data.json:/app/data.json" \
-v "$(pwd)/data/template.html:/app/data/template.html" \
-v "$(pwd)/data/photo.jpg:/app/photo.jpg" \
-v "$(pwd)/out:/out" \
-e OUTPUT_FILE_PATH=/out/cv.html \
cv generateAppend generate -lang=ru (or another code) when locales is set in data.json (see Multi-language).
Compose one-shot:
mkdir -p out
docker compose -f ops/docker/docker-compose.yaml run --rm \
-v "$(pwd)/out:/out" \
-e OUTPUT_FILE_PATH=/out/cv.html \
cv generateHelm values and mount paths: ops/helm/values.yaml (appConfig.dataPath, templatePath, photoPath, outputPath, port).
helm upgrade --install \
-f values.yaml \
cv ./ops/helm \
-n cv
kubectl cp ./data/data.json <pod-name>:/data/data.json
kubectl cp ./data/template.html <pod-name>:/data/template.html
kubectl cp ./data/photo.jpg <pod-name>:/data/photo.jpg