You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add deep-merge utility for profile-based metadata overrides
Replace the full-copy-per-profile approach with a deep-merge function
that recursively merges sparse profile TOML files on top of a shared
root metadata.toml. This lets users vary any field (personal.info,
layout, inject, etc.) per language or target role without duplicating
the entire configuration.
- Add src/utils/merge.typ with recursive deep-merge function
- Export deep-merge from src/lib.typ
- Update template/cv.typ and letter.typ with profile-aware loading
- Restore root metadata.toml and modules_<lang>/ directories
- Remove profile_<lang>/ directories (replaced by profiles/ overrides)
- Add example profile files (en, fr, zh) in template/profiles/
- Update docs: recipes, getting-started, migration guide, API reference
Closes#142
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/web/docs/getting-started.md
+5-1Lines changed: 5 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -71,7 +71,11 @@ The most important keys to set first:
71
71
typst compile cv.typ
72
72
```
73
73
74
-
## Step 7: Go Beyond
74
+
## Step 7: (Optional) Set Up Profiles
75
+
76
+
If you maintain CVs in multiple languages or for different target roles, you can create **profile overrides** — sparse TOML files that only contain the fields that differ from your root `metadata.toml`. See [Recipes → Profile-Based Overrides](recipes.md#profile-based-overrides) for details.
Copy file name to clipboardExpand all lines: docs/web/docs/migration.md
+34-73Lines changed: 34 additions & 73 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,19 +2,15 @@
2
2
3
3
## Migration from v3
4
4
5
-
v4 replaces the language-based switching system with a **profile-based**architecture. Each profile is a self-contained folder with its own `metadata.toml` and module files, enabling full customization per variant — not just language-specific quotes, but also different personalinfo, layout, and keywords per target role or industry.
5
+
v4 adds a **`deep-merge` utility function**to the package, enabling profile-based overrides. This lets you vary any metadata field per language or target role — not just `header_quote` and footers, but also `[personal.info]`, `[layout]`, `[inject]`, and anything else ([#142](https://github.com/yunanwg/brilliant-CV/issues/142)).
6
6
7
-
### Why this change?
7
+
### What changed?
8
8
9
-
In v3, all CV variants share a single `metadata.toml`. The `[lang.<code>]` sections only allow varying `header_quote`, `cv_footer`, and `letter_footer` per language. Fields like `[personal.info]`, `[layout]`, and `[inject]` are global and cannot differ between languages or target roles ([#142](https://github.com/yunanwg/brilliant-CV/issues/142)).
9
+
The package now exports `deep-merge`, a recursive dictionary merge function. Your root `metadata.toml` stays as the single source of truth; optional profile files contain only the fields that differ.
10
10
11
-
The new profile system makes each variant fully independent: a `profile_en/` folder for your English CV, a `profile_fr/` for French, a `profile_swe/` tailored for software engineering roles — each with its own metadata, personal info, and content modules.
The v4 package is **fully backward compatible** with the v3 metadata format. If you don't need per-profile customization, just update the version number:
13
+
v4 is **fully backward compatible**. If you don't need per-profile customization, just update the version number:
18
14
19
15
```typ
20
16
// Before
@@ -24,97 +20,62 @@ The v4 package is **fully backward compatible** with the v3 metadata format. If
24
20
#import "@preview/brilliant-cv:4.0.0": cv
25
21
```
26
22
27
-
Your existing `metadata.toml` with `[lang.<code>]` sections, `modules_<lang>/` folders, and `--input language=xx` CLI pattern will continue to work as before.
28
-
29
-
#### Option B: Migrate to profile-based structure
23
+
Your existing `metadata.toml`, `modules_<lang>/` folders, `[lang.<code>]` sections, and `--input language=xx` all continue to work unchanged.
30
24
31
-
Follow these steps to adopt the new architecture:
25
+
### Adopting profile overrides (optional)
32
26
33
-
**1. Rename module folders**
34
-
35
-
```
36
-
modules_en/ → profile_en/
37
-
modules_fr/ → profile_fr/
38
-
```
27
+
To vary fields like `personal.info.location` per language:
39
28
40
-
**2. Create per-profile `metadata.toml`**
41
-
42
-
Copy your root `metadata.toml` into each profile folder and make these changes:
29
+
**1. Create sparse profile files** in a `profiles/` directory:
43
30
44
31
```toml
45
-
# Before (v3 root metadata.toml)
46
-
language = "en"
47
-
48
-
[lang.en]
49
-
header_quote = "Experienced Data Analyst..."
50
-
cv_footer = "Curriculum vitae"
51
-
letter_footer = "Cover letter"
52
-
53
-
[lang.non_latin]
54
-
name = "王道尔"
55
-
font = "Heiti SC"
56
-
57
-
# After (profile_en/metadata.toml) — flat, no [lang] nesting
58
-
language = "en"
59
-
header_quote = "Experienced Data Analyst..."
60
-
cv_footer = "Curriculum vitae"
61
-
letter_footer = "Cover letter"
62
-
```
32
+
# profiles/fr.toml — only the fields that differ
33
+
language = "fr"
63
34
64
-
For non-Latin profiles (zh, ja, ko, ru), move the non-Latin settings to top-level:
35
+
[personal.info]
36
+
location = "Paris, France"
65
37
66
-
```toml
67
-
# profile_zh/metadata.toml
68
-
language = "zh"
69
-
header_quote = "具有丰富经验的数据分析师,随时可入职"
70
-
cv_footer = "简历"
71
-
letter_footer = "申请信"
72
-
non_latin_name = "王道尔"
73
-
non_latin_font = "Heiti SC"
38
+
[personal.info.custom-1]
39
+
awesomeIcon = "car"
40
+
text = "Permis B"
74
41
```
75
42
76
-
Remove the `[lang.*]` sections entirely from each profile's `metadata.toml`. You can now customize `[personal]`, `[layout]`, `[inject]`, and all other sections independently per profile.
For Chinese, Japanese, Korean, or Russian, also configure `[lang.non_latin]` in `metadata.toml` with `name` and `font`.
16
16
17
+
## Profile-Based Overrides
18
+
19
+
If you need different `personal.info` fields per language or target role — for example, "Permis B" instead of "Driver License", or "Paris" instead of "San Francisco" — you can use **profile overrides**.
20
+
21
+
A profile is a sparse TOML file that gets [deep-merged](api-reference.md) on top of your root `metadata.toml`. Only the fields that differ need to be specified.
22
+
23
+
### Setup
24
+
25
+
**1. Create a `profiles/` directory** with one TOML file per variant:
26
+
27
+
```
28
+
profiles/
29
+
en.toml ← minimal (language is already "en" in root)
30
+
fr.toml ← overrides language + French-specific fields
31
+
```
32
+
33
+
**2. Write sparse overrides.** For example, `profiles/fr.toml`:
34
+
35
+
```toml
36
+
language = "fr"
37
+
38
+
[personal.info]
39
+
location = "Paris, France"
40
+
41
+
[personal.info.custom-1]
42
+
awesomeIcon = "car"
43
+
text = "Permis B"
44
+
```
45
+
46
+
Everything not specified (layout, fonts, email, GitHub, etc.) is inherited from root `metadata.toml`.
47
+
48
+
**3. Build with a profile:**
49
+
50
+
```bash
51
+
typst compile cv.typ --input profile=fr
52
+
```
53
+
54
+
Or set a default in `metadata.toml`:
55
+
56
+
```toml
57
+
profile = "en"
58
+
```
59
+
60
+
### How deep-merge works
61
+
62
+
The `deep-merge` function recursively combines two dictionaries:
63
+
64
+
-**No conflict** (key only in one dict) → value is kept as-is
65
+
-**Both have the key, both are dicts** → merge recursively (go deeper)
66
+
-**Both have the key, not both dicts** → profile value wins
67
+
68
+
This means `profiles/fr.toml` only overrides `personal.info.location` and `personal.info.custom-1` — all other `personal.info` fields (email, phone, github, etc.) are preserved from root.
69
+
70
+
### Tips
71
+
72
+
-**Profile ≠ language.** You can have `profiles/us.toml` and `profiles/uk.toml` both with `language = "en"` but different locations or phone numbers.
73
+
-**`--input language=xx` still works** as a final override on top of a profile, for backward compatibility.
74
+
-**Profiles are optional.** If you don't use them, everything works exactly as before.
75
+
17
76
## Skills with Inline Separators
18
77
19
78
Use `#h-bar()` to separate skill items within `cv-skill`:
0 commit comments