jekyll-theme-resume 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/403.html +6 -0
- data/404.html +6 -0
- data/500.html +6 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/LICENSE.txt +22 -0
- data/README.md +184 -0
- data/SECURITY.md +30 -0
- data/_config.sample.yml +329 -0
- data/_data/locales/ar.yml +85 -0
- data/_data/locales/de.yml +86 -0
- data/_data/locales/en.yml +84 -0
- data/_data/locales/es.yml +86 -0
- data/_data/locales/fr.yml +86 -0
- data/_data/locales/ur.yml +86 -0
- data/_data/social_networks.yml +83 -0
- data/_includes/analytics-body.html +12 -0
- data/_includes/analytics-head.html +36 -0
- data/_includes/avatar.html +84 -0
- data/_includes/dark-mode-toggle.html +202 -0
- data/_includes/data-loader.html +42 -0
- data/_includes/date-formatter.html +63 -0
- data/_includes/grouped-item-list.html +53 -0
- data/_includes/hreflang.html +58 -0
- data/_includes/language-switcher.html +72 -0
- data/_includes/print-social-links.html +23 -0
- data/_includes/resume-section.html +547 -0
- data/_includes/safe-url.html +21 -0
- data/_includes/shared-head.html +51 -0
- data/_includes/social-links.html +54 -0
- data/_includes/vendors/svg-icons/ATTRIBUTION.md +34 -0
- data/_includes/vendors/svg-icons/dev.svg +3 -0
- data/_includes/vendors/svg-icons/dribbble-symbol.svg +3 -0
- data/_includes/vendors/svg-icons/envelope.svg +4 -0
- data/_includes/vendors/svg-icons/facebook.svg +3 -0
- data/_includes/vendors/svg-icons/flickr.svg +4 -0
- data/_includes/vendors/svg-icons/github.svg +3 -0
- data/_includes/vendors/svg-icons/globe-1.svg +3 -0
- data/_includes/vendors/svg-icons/instagram.svg +4 -0
- data/_includes/vendors/svg-icons/linkedin.svg +3 -0
- data/_includes/vendors/svg-icons/medium.svg +3 -0
- data/_includes/vendors/svg-icons/phone.svg +13 -0
- data/_includes/vendors/svg-icons/pinterest.svg +3 -0
- data/_includes/vendors/svg-icons/postcard.svg +14 -0
- data/_includes/vendors/svg-icons/telegram.svg +3 -0
- data/_includes/vendors/svg-icons/whatsapp.svg +3 -0
- data/_includes/vendors/svg-icons/x.svg +3 -0
- data/_includes/vendors/svg-icons/youtube.svg +3 -0
- data/_layouts/default.html +65 -0
- data/_layouts/error.html +138 -0
- data/_layouts/profile.html +123 -0
- data/_layouts/resume.html +238 -0
- data/_plugins/error_pages_generator.rb +71 -0
- data/_plugins/json_resume_generator.rb +79 -0
- data/_plugins/resume_pages_generator.rb +71 -0
- data/_plugins/resume_validator.rb +40 -0
- data/_sass/_all-pages.scss +335 -0
- data/_sass/_base.scss +130 -0
- data/_sass/_dark-mode.scss +387 -0
- data/_sass/_layout.scss +116 -0
- data/_sass/_mixins.scss +136 -0
- data/_sass/_normalize.scss +379 -0
- data/_sass/_profile-page.scss +60 -0
- data/_sass/_resume-ltr.scss +436 -0
- data/_sass/_resume-rtl.scss +91 -0
- data/_sass/_variables.scss +45 -0
- data/assets/css/cv-ltr.scss +31 -0
- data/assets/css/cv-rtl.scss +36 -0
- data/assets/css/main.scss +8 -0
- data/assets/css/profile.scss +9 -0
- data/assets/favicon/resume/about.txt +6 -0
- data/assets/favicon/resume/android-chrome-192x192.png +0 -0
- data/assets/favicon/resume/android-chrome-512x512.png +0 -0
- data/assets/favicon/resume/apple-touch-icon.png +0 -0
- data/assets/favicon/resume/favicon-16x16.png +0 -0
- data/assets/favicon/resume/favicon-32x32.png +0 -0
- data/assets/favicon/resume/favicon.ico +0 -0
- data/assets/favicon/resume/site.webmanifest +1 -0
- data/bin/validate-resume +89 -0
- data/bin/verify +27 -0
- data/docs/README.md +89 -0
- data/docs/explanation/accessibility-decisions.md +27 -0
- data/docs/explanation/architecture.md +44 -0
- data/docs/explanation/dark-mode-approach.md +25 -0
- data/docs/explanation/data-driven-model.md +25 -0
- data/docs/explanation/multilingual-and-rtl-design.md +40 -0
- data/docs/how-to/add-a-language.md +57 -0
- data/docs/how-to/add-a-section.md +35 -0
- data/docs/how-to/add-a-social-platform.md +32 -0
- data/docs/how-to/add-a-test.md +34 -0
- data/docs/how-to/create-a-custom-layout.md +64 -0
- data/docs/how-to/enable-dark-mode.md +43 -0
- data/docs/how-to/link-translations-with-hreflang.md +25 -0
- data/docs/how-to/migrate-v0.9-to-v1.0.md +46 -0
- data/docs/how-to/override-locale-strings.md +51 -0
- data/docs/how-to/override-sass-partials.md +26 -0
- data/docs/how-to/proof-built-html.md +22 -0
- data/docs/how-to/publish-json-resume.md +38 -0
- data/docs/how-to/show-language-proficiency-in-header.md +13 -0
- data/docs/how-to/switch-resume-versions.md +42 -0
- data/docs/how-to/troubleshoot-builds.md +39 -0
- data/docs/how-to/validate-in-ci.md +38 -0
- data/docs/how-to/verify-accessibility.md +28 -0
- data/docs/reference/accessibility-coverage.md +39 -0
- data/docs/reference/config.md +378 -0
- data/docs/reference/data-schemas.md +529 -0
- data/docs/reference/glossary.md +39 -0
- data/docs/reference/includes.md +197 -0
- data/docs/reference/json-resume-fields.md +148 -0
- data/docs/reference/layouts.md +144 -0
- data/docs/reference/locale-keys.md +76 -0
- data/docs/reference/repository-map.md +83 -0
- data/docs/reference/sass-tokens.md +279 -0
- data/docs/reference/testing-suites.md +62 -0
- data/docs/reference/validator-cli.md +203 -0
- data/docs/tutorials/getting-started.md +173 -0
- data/lib/jekyll-theme-resume/json_resume_exporter.rb +334 -0
- data/lib/jekyll-theme-resume/resume_validator.rb +853 -0
- data/lib/jekyll-theme-resume/schemas/LICENSE.md +21 -0
- data/lib/jekyll-theme-resume/schemas/json_resume_v1.0.0.json +500 -0
- data/lib/jekyll-theme-resume.rb +21 -0
- metadata +371 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Includes reference (`_includes/`)
|
|
2
|
+
|
|
3
|
+
*Audience: theme developers*
|
|
4
|
+
|
|
5
|
+
The theme's reusable partials in [`_includes/`](../../_includes): what each one renders and its parameters.
|
|
6
|
+
|
|
7
|
+
`avatar.html`, `date-formatter.html` and `resume-section.html` resolve language from an optional `include.lang`, then `page.lang`, `site.default_lang`, or `en`; the other locale-aware includes skip `include.lang`. See [accessibility coverage](accessibility-coverage.md) for current labeling limitations. See [`layouts.md`](layouts.md#language-resolution).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Include Map
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
_layouts/resume.html
|
|
15
|
+
├── data-loader.html (binds resume_data from languages.<lang>.data_path)
|
|
16
|
+
├── shared-head.html (anti-FOUC script, metadata, favicon suite)
|
|
17
|
+
├── hreflang.html (alternate-language links)
|
|
18
|
+
├── analytics-head.html (GTM / GA4 head script)
|
|
19
|
+
├── analytics-body.html (GTM noscript iframe)
|
|
20
|
+
├── dark-mode-toggle.html (floating theme toggle)
|
|
21
|
+
├── language-switcher.html (dropdown links to the other languages)
|
|
22
|
+
├── avatar.html (profile image)
|
|
23
|
+
│ └── safe-url.html (URL scheme allowlist)
|
|
24
|
+
├── date-formatter.html (date of birth)
|
|
25
|
+
├── social-links.html (header social icons)
|
|
26
|
+
│ └── safe-url.html
|
|
27
|
+
├── resume-section.html (one call per entry in resume_section_order)
|
|
28
|
+
│ ├── grouped-item-list.html (Experience and Volunteering)
|
|
29
|
+
│ │ └── date-formatter.html
|
|
30
|
+
│ └── date-formatter.html
|
|
31
|
+
└── print-social-links.html (print-only text list)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`default.html` and `profile.html` use `shared-head.html`, their own inline stylesheet `<link>` (`main.css` / `profile.css`), the analytics includes, `dark-mode-toggle.html`, and `language-switcher.html`.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Component Inventory
|
|
39
|
+
|
|
40
|
+
### 1. `shared-head.html`
|
|
41
|
+
|
|
42
|
+
- **Consumed by:** every layout.
|
|
43
|
+
- Charset, viewport, and `color-scheme` meta.
|
|
44
|
+
- **Anti-FOUC script:** reads `localStorage['color-scheme']` synchronously and sets `data-color-scheme` / `data-theme` on `<html>` before CSS loads.
|
|
45
|
+
- **Favicons:** `favicon`, `apple_touch_icon`, `favicon_32`, `favicon_16`, and the web manifest, all through `relative_url`.
|
|
46
|
+
- **Robots:** `noindex noarchive nosnippet noimageindex` when `page.noindex: true`.
|
|
47
|
+
|
|
48
|
+
### 2. Stylesheet links (inline in each layout)
|
|
49
|
+
|
|
50
|
+
- `default.html` (therefore also error pages) links `assets/css/main.css` directly, no include.
|
|
51
|
+
- `profile.html` links `assets/css/profile.css` directly, no include.
|
|
52
|
+
|
|
53
|
+
### 3. `avatar.html`
|
|
54
|
+
|
|
55
|
+
- **Consumed by:** `resume.html` and `profile.html` when `site.resume_avatar == true`.
|
|
56
|
+
- **Parameters:** `lang` (default: active language), `link` (`false` renders a bare `<img>`), `class` (extra CSS classes).
|
|
57
|
+
- **Source:** `site.avatar_url`, default `/assets/images/Profile-min.jpg` (the theme does not ship this file; supply it or set `avatar_url`). Values containing a scheme (`:`) are used as-is; others pass through `relative_url`. Only `http` and `https` are allowed; any other scheme drops the image.
|
|
58
|
+
- **Alt text:** `languages.<lang>.avatar_alt`, then `languages.<lang>.name`, then `locale.ui.photo_alt`.
|
|
59
|
+
- **Link:** wraps the image in a link to `site.avatar_link` (default `/`) with `site.avatar_link_target` (default `_self`), unless `site.avatar_link: false` or `link=false`. `avatar_link` may use `http`, `https`, `mailto` or `tel`; any other scheme renders the image unlinked.
|
|
60
|
+
|
|
61
|
+
### 3a. `safe-url.html`
|
|
62
|
+
|
|
63
|
+
- **Consumed by:** `avatar.html`, `social-links.html`, and the Mastodon `rel="me"` link in `default.html` and `profile.html`.
|
|
64
|
+
- **Parameters:** `url` (value to check), `schemes` (comma-separated allowlist, e.g. `"http,https"`).
|
|
65
|
+
- **Output:** sets `safe_url` to the trimmed value, or nil when it is blank or disallowed. A value containing `:` must start with an allowed scheme (case-insensitive); a value without `:` is a relative path and passes. `ResumeValidator` applies the same rule to config.
|
|
66
|
+
|
|
67
|
+
```liquid
|
|
68
|
+
{% include avatar.html lang=lang %}
|
|
69
|
+
{% include avatar.html link=false class="avatar-large" %}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 4. `dark-mode-toggle.html`
|
|
73
|
+
|
|
74
|
+
- **Consumed by:** `resume.html`, `default.html`, `profile.html`.
|
|
75
|
+
- Renders only when `site.dark_mode` is `"enabled"` or `true`, or front matter `dark_mode` is `true` / `"enabled"`; front matter `dark_mode: false` suppresses it.
|
|
76
|
+
- Two states: unpinned (follows `prefers-color-scheme`, live via `matchMedia`) and pinned (`"dark"` / `"light"` saved to `localStorage['color-scheme']`). Clicking a pinned toggle clears the pin.
|
|
77
|
+
- `aria-label` and `title` from `locale.ui.dark_mode_toggle`. Hidden in print via `.no-print`.
|
|
78
|
+
|
|
79
|
+
### 5. `language-switcher.html`
|
|
80
|
+
|
|
81
|
+
- **Consumed by:** `resume.html`, `default.html`, `profile.html`. Present on error pages too. Hidden when `site.resume_language_switcher: false` or front matter sets `language_switcher: false`.
|
|
82
|
+
- Renders as a `<details class="language-switcher">`/`<summary class="language-switcher-trigger">` disclosure — zero JavaScript. The trigger's visible label is the current locale's `ui.language_switcher`; opening it reveals a `<nav class="language-switcher-panel">` with one link for every entry in `site.languages` except the current one, each labelled with the target locale's `ui.language_name`.
|
|
83
|
+
- **Link resolution per target language:** the page in `site.pages` with the same `t_id` and the target `lang`; otherwise `languages.<lang>.url`.
|
|
84
|
+
- Fixed top-left in every locale, LTR and RTL alike (the dark mode toggle is fixed top-right); positions no longer mirror by direction. Hidden in print.
|
|
85
|
+
- Loop variables are prefixed `switch_`; see [Liquid pitfalls](../explanation/architecture.md#liquid-pitfalls) for why.
|
|
86
|
+
|
|
87
|
+
### 6. `date-formatter.html`
|
|
88
|
+
|
|
89
|
+
- **Consumed by:** `resume-section.html` (every date) and `resume.html` (date of birth).
|
|
90
|
+
- **Parameters:** `date` (required), `style` (`"MY"` default: `<month> <year>`; `"MDY"`: `<month> <day>, <year>`), `lang` (default: active language).
|
|
91
|
+
- A `date` matching any of the locale's `present_values` (case-insensitive) renders `locale.ui.present`.
|
|
92
|
+
- Otherwise the formatter splits the ISO value itself (it does not use Liquid's `date` filter; see [Liquid pitfalls](../explanation/architecture.md#liquid-pitfalls)): `YYYY-MM-DD` renders `<month> <year>` (or `<month> <day>, <year>` with `MDY`), `YYYY-MM` renders `<month> <year>`, `YYYY` renders the year, and anything else is printed as written. YAML date objects behave like `YYYY-MM-DD`.
|
|
93
|
+
- Tested in [`test/test_rendered_site.rb`](../../test/test_rendered_site.rb).
|
|
94
|
+
|
|
95
|
+
```liquid
|
|
96
|
+
{% include date-formatter.html date=role.startdate %}
|
|
97
|
+
{% include date-formatter.html date=site.contact_info.dob style="MDY" lang=lang %}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### 7. `social-links.html`
|
|
102
|
+
|
|
103
|
+
- **Consumed by:** `resume.html` and `profile.html`, inside `<ul class="social-links">`, when `site.social_links` is set.
|
|
104
|
+
- `email` renders a `mailto:` link with `itemprop="email"`. The other 14 platforms (`github`, `linkedin`, `telegram`, `twitter`, `medium`, `dribbble`, `facebook`, `instagram`, `website`, `whatsapp`, `devto`, `flickr`, `pinterest`, `youtube`) open in a new tab with `rel="noopener nofollow noreferrer"`.
|
|
105
|
+
- Every icon link carries `aria-label`, `title`, and a `.sr-only` text span, all taken from the page locale's `ui.social_labels.<key>` (falling back to the English `label` in `_data/social_networks.yml`).
|
|
106
|
+
- `profile.html` adds one extra email icon for `contact_info.email` only when `social_links.email` is not set, so the profile never shows two email icons.
|
|
107
|
+
|
|
108
|
+
### 8. `print-social-links.html`
|
|
109
|
+
|
|
110
|
+
- **Consumed by:** `resume.html` print-only section when `site.resume_print_social_links` is set.
|
|
111
|
+
- One line per configured platform (including `email`), labelled from `locale.ui.social_labels`, with the value wrapped in `<span dir="ltr">`.
|
|
112
|
+
|
|
113
|
+
### 9. `hreflang.html`
|
|
114
|
+
|
|
115
|
+
- **Consumed by:** `resume.html` and `profile.html` heads.
|
|
116
|
+
- Runs only when the page has a `t_id`. Emits `<link rel="alternate" hreflang="<lang>">` for every page sharing that `t_id`, and `hreflang="x-default"` for the one whose `lang` is `site.default_lang`. URLs are absolute (`absolute_url`), so `site.url` must be set.
|
|
117
|
+
|
|
118
|
+
### 10. `data-loader.html`
|
|
119
|
+
|
|
120
|
+
- **Consumed by:** `resume.html`.
|
|
121
|
+
- **Parameter:** `path`, a dot-separated data path. Default: `site.languages[page.lang or default_lang].data_path`.
|
|
122
|
+
- Sets `resume_data` in the caller's scope by walking `site.data` with bracket access. Details in [`layouts.md`](layouts.md#dynamic-data-resolution).
|
|
123
|
+
|
|
124
|
+
### 11. `analytics-head.html` & `analytics-body.html`
|
|
125
|
+
|
|
126
|
+
- **Head:** Google Tag Manager (`site.analytics.gtm`) or Google Analytics 4 (`site.analytics.gtag`). Universal Analytics (`analytics.ga`) is retired; use `analytics.gtag` for GA4.
|
|
127
|
+
- **Body:** the GTM `<noscript><iframe>` right after `<body>` in every layout.
|
|
128
|
+
|
|
129
|
+
### 12. `vendors/` (SVG Icons)
|
|
130
|
+
|
|
131
|
+
- `vendors/svg-icons/`: one flat folder of Lineicons SVGs (MIT-licensed; see `ATTRIBUTION.md` inside it, packaged with the gem but not built into a consuming site's output) — contact row icons (`envelope`, `phone`, `postcard`) plus every social platform icon listed in [`_data/social_networks.yml`](../../_data/social_networks.yml).
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Multilingual SEO with hreflang
|
|
136
|
+
|
|
137
|
+
When translations of a page share a `t_id` (see [Add a language](../how-to/add-a-language.md)) and `default_lang: en`, each page emits:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<link rel="alternate" hreflang="en" href="https://your-domain.com/en/cv/" />
|
|
141
|
+
<link rel="alternate" hreflang="ar" href="https://your-domain.com/ar/cv/" />
|
|
142
|
+
<link rel="alternate" hreflang="x-default" href="https://your-domain.com/en/cv/" />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The same `t_id` also lets the language switcher find the exact counterpart page instead of falling back to `languages.<lang>.url`.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Section Dispatcher (`resume-section.html`)
|
|
150
|
+
|
|
151
|
+
`resume.html` renders sections by looping over `site.resume_section_order`:
|
|
152
|
+
|
|
153
|
+
```liquid
|
|
154
|
+
{%- for section_name in site.resume_section_order -%}
|
|
155
|
+
{% include resume-section.html section_name=section_name lang=lang %}
|
|
156
|
+
{%- endfor -%}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
[`_includes/resume-section.html`](../../_includes/resume-section.html) is one `{% if %} / {% elsif %}` chain. A branch renders when `include.section_name` matches and `site.resume_section.<name>` is truthy:
|
|
160
|
+
|
|
161
|
+
```liquid
|
|
162
|
+
{% if include.section_name == "experience" and site.resume_section.experience %}
|
|
163
|
+
<!-- Experience -->
|
|
164
|
+
{% elsif include.section_name == "education" and site.resume_section.education %}
|
|
165
|
+
<!-- Education -->
|
|
166
|
+
...
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Inside every branch:
|
|
170
|
+
|
|
171
|
+
- The heading is `locale.ui.section_titles.<name>`.
|
|
172
|
+
- Dates go through `date-formatter.html`; a blank `enddate` renders as the locale's "Present" in Experience and Volunteering (courses omit the end date).
|
|
173
|
+
- When `locale.direction == 'rtl'`, URLs and credential IDs are wrapped in `dir="ltr"`.
|
|
174
|
+
- Only items with `active: true` render; an item without the flag is hidden (`interests.yml` has no flag).
|
|
175
|
+
|
|
176
|
+
### Section Dispatch Inventory
|
|
177
|
+
|
|
178
|
+
| `section_name` | Config toggle (`resume_section`) | Data file | Data expression |
|
|
179
|
+
|---|---|---|---|
|
|
180
|
+
| `experience` | `experience` | `experience.yml` | `resume_data.experience` |
|
|
181
|
+
| `education` | `education` | `education.yml` | `resume_data.education` |
|
|
182
|
+
| `certifications` | `certifications` | `certifications.yml` | `resume_data.certifications` |
|
|
183
|
+
| `courses` | `courses` | `courses.yml` | `resume_data.courses` |
|
|
184
|
+
| `volunteering` | `volunteering` | `volunteering.yml` | `resume_data.volunteering` |
|
|
185
|
+
| `projects` | `projects` | `projects.yml` | `resume_data.projects` |
|
|
186
|
+
| `skills` | `skills` | `skills.yml` | `resume_data.skills` |
|
|
187
|
+
| `recognitions` | `recognitions` | `recognitions.yml` | `resume_data.recognitions` |
|
|
188
|
+
| `associations` | `associations` | `associations.yml` | `resume_data.associations` |
|
|
189
|
+
| `interests` | `interests` | `interests.yml` | `resume_data.interests` |
|
|
190
|
+
| `languages` | `languages` (and `lang_header` not `true`) | `languages.yml` | `resume_data.languages` |
|
|
191
|
+
| `links` | `links` | `links.yml` | `resume_data.links` |
|
|
192
|
+
|
|
193
|
+
### Shared Grouped Items (`grouped-item-list.html`)
|
|
194
|
+
|
|
195
|
+
Experience and Volunteering both call this include with `items` and `lang`. It filters `active: true`, groups by `company`, and sorts roles within each group by `startdate` descending. Explicit `durations[].duration` strings take precedence over formatted start/end dates. Summaries require `enable_summary: true`.
|
|
196
|
+
|
|
197
|
+
To add a section, see [Add a resume section](../how-to/add-a-section.md).
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# JSON Resume export reference
|
|
2
|
+
|
|
3
|
+
*Audience: site owners*
|
|
4
|
+
|
|
5
|
+
Reference for the localized JSON Resume export: configuration, visibility, field mappings, normalization, and standard conformance.
|
|
6
|
+
|
|
7
|
+
The theme generates one JSON Resume document per configured language at
|
|
8
|
+
`/<lang>/resume.json`. It also generates `/resume.json` from the default language.
|
|
9
|
+
These are static build outputs, prefixed by `baseurl` when served. Existing YAML
|
|
10
|
+
keys and HTML layouts continue working without migration.
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Exports are enabled by default. The `json_resume` and `social_usernames` settings, their defaults, and the language allowlist rules are in [JSON Resume export](config.md#13-json-resume-export) in the configuration reference.
|
|
15
|
+
|
|
16
|
+
The root copy is generated only if the default language is allowed and its
|
|
17
|
+
localized export succeeds. A collision with an authored page, static file,
|
|
18
|
+
collection document, or existing generated page preserves that resource and logs
|
|
19
|
+
a warning. A collision at the localized route also suppresses its root copy.
|
|
20
|
+
A root-only collision does not suppress the localized export.
|
|
21
|
+
|
|
22
|
+
Discovery links on CV pages and the `application/json` hosting requirement are covered in [Publish the JSON Resume export](../how-to/publish-json-resume.md).
|
|
23
|
+
|
|
24
|
+
## Visibility and privacy
|
|
25
|
+
|
|
26
|
+
- Data comes from `languages.<lang>.data_path`, including nested dot paths and an
|
|
27
|
+
empty path for root data.
|
|
28
|
+
- Sections must be enabled and present in `resume_section_order`. Entries require
|
|
29
|
+
exactly `active: true`, except interests, whose HTML list has no active filter.
|
|
30
|
+
- Header languages follow `display_header_contact_info` and `resume_section.lang_header`.
|
|
31
|
+
Section languages follow the existing section-placement rules.
|
|
32
|
+
- The biography is `header.intro` only when `header_intro: true`; profile `about`
|
|
33
|
+
content is not used as a fallback. Work and volunteer summaries require
|
|
34
|
+
`enable_summary: true`. The avatar requires `resume_avatar: true` and uses the
|
|
35
|
+
same default image as the CV.
|
|
36
|
+
- Email is exported when the contact bar or contact-me CTA is visible. Phone and
|
|
37
|
+
location are exported only when the contact bar is visible. `enable_live: true`
|
|
38
|
+
selects each live contact value independently, falling back to its base value
|
|
39
|
+
when the live key is absent or false.
|
|
40
|
+
- `json_resume.privacy.export_contact_info: false` omits email, phone, the entire location object, and
|
|
41
|
+
WhatsApp profiles. `social_links.email` is never exported as a social profile.
|
|
42
|
+
This setting does not redact free-form narrative text, usernames, or arbitrary
|
|
43
|
+
URLs, and it does not change the HTML site's contact visibility.
|
|
44
|
+
|
|
45
|
+
### Contact fields by privacy setting
|
|
46
|
+
|
|
47
|
+
Two settings decide what personal data leaves in `resume.json`: `display_header_contact_info` (and `resume_looking_for_work`) in `_config.yml`, and `json_resume.privacy.export_contact_info`. Each cell says whether the field is exported.
|
|
48
|
+
|
|
49
|
+
| Field in `resume.json` | Source | Default export | `display_header_contact_info: false` | `export_contact_info: false` |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| `basics.phone` | `contact_info.phone` (or `phone_live`) | Only when `display_header_contact_info: true` | Omitted | Omitted |
|
|
52
|
+
| `basics.email` | `contact_info.email` (or `email_live`) | When `display_header_contact_info: true` **or** `resume_looking_for_work: true` | Omitted unless `resume_looking_for_work: true` | Omitted |
|
|
53
|
+
| `basics.location.address`, `postalCode`, `city`, `region`, `countryCode` | `languages.<lang>.address`, `postal_code`, `city`, `region`, `country_code` | Only when `display_header_contact_info: true` | Whole `location` object omitted | Whole `location` object omitted |
|
|
54
|
+
| `basics.profiles[]` for `whatsapp` | `social_links.whatsapp` | Exported when configured | Exported | **Omitted** |
|
|
55
|
+
| `basics.profiles[]` for every other network | `social_links.<network>` | Exported when configured | Exported | **Still exported** (including `telegram`, `website`, `twitter`) |
|
|
56
|
+
| `basics.profiles[].username` | `social_usernames.<network>` | Exported with its profile | Exported with its profile | Exported with its profile |
|
|
57
|
+
| `contact_info.dob` | `contact_info.dob` | Never exported | Never exported | Never exported |
|
|
58
|
+
|
|
59
|
+
Things to know before publishing:
|
|
60
|
+
|
|
61
|
+
- A postal address is exported whenever the CV header shows contact details, even if you only meant to show it on the page. Set `export_contact_info: false` to keep address, phone and email out of the JSON.
|
|
62
|
+
- `export_contact_info: false` does not touch the HTML. The CV still shows whatever `display_header_contact_info` shows.
|
|
63
|
+
- Only WhatsApp is treated as a contact channel. Any other `social_links` entry (for example a Telegram handle) is public in the JSON unless you remove it from `social_links`.
|
|
64
|
+
- Free-form text (summaries, references, highlights) is exported as written. The setting does not redact it.
|
|
65
|
+
|
|
66
|
+
The export is a supported subset of the CV, not a lossless representation; see [the data-driven model](../explanation/data-driven-model.md).
|
|
67
|
+
|
|
68
|
+
## Field mappings
|
|
69
|
+
|
|
70
|
+
All field names below on the left are existing YAML names or optional additions;
|
|
71
|
+
camelCase names on the right belong only to the JSON output.
|
|
72
|
+
|
|
73
|
+
| Source | JSON Resume target |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `languages.<lang>.name`, `resume_title` | `basics.name`, `basics.label` |
|
|
76
|
+
| `avatar_url` or theme fallback | `basics.image` |
|
|
77
|
+
| Selected `contact_info.email`, `phone` | `basics.email`, `basics.phone` |
|
|
78
|
+
| Actual CV page URL, falling back to `languages.<lang>.url` | `basics.url`, `meta.canonical` |
|
|
79
|
+
| `header.intro` | `basics.summary` |
|
|
80
|
+
| Per-language `address`, `postal_code`, `city`, `country_code`, `region` | `basics.location.address`, `postalCode`, `city`, `countryCode`, `region` |
|
|
81
|
+
| Supported `social_links.<network>` and `social_usernames.<network>` | `basics.profiles[].network`, `url`, `username` |
|
|
82
|
+
| `experience.company`, `position`, `location`, `summary`, `url`, `highlights` | `work[].name`, `position`, `location`, `summary`, `url`, `highlights` |
|
|
83
|
+
| `volunteering.company`, `position`, `summary`, `url`, `highlights` | `volunteer[].organization`, `position`, `summary`, `url`, `highlights` |
|
|
84
|
+
| `education.uni`, `degree` (fallback `study_type`), `area`, `score` (fallback `gpa`), `courses`, `url` | `education[].institution`, `studyType`, `area`, `score`, `courses`, `url` |
|
|
85
|
+
| Work, volunteer, education, project `startdate`, `enddate` | Corresponding `startDate`, `endDate` |
|
|
86
|
+
| `certifications.name`, `issuing_organization`, `issue_date`, `credential_url` | `certificates[].name`, `issuer`, `date`, `url` |
|
|
87
|
+
| `recognitions.award` (fallback `title`, `recognition`), `organization`, `summary`, `date` | `awards[].title`, `awarder`, `summary`, `date` |
|
|
88
|
+
| Four-digit recognition `year`, when `date` is absent | `awards[].date` |
|
|
89
|
+
| `skills.skill`, `level_label`, `keywords` | `skills[].name`, `level`, `keywords` |
|
|
90
|
+
| `languages.language`, displayed `descrp_short` (header) or `description` (section) | `languages[].language`, `fluency` |
|
|
91
|
+
| `interests.name` (fallback `description`), `keywords` | `interests[].name`, `keywords` |
|
|
92
|
+
| `projects.project`, `description`, `url`, `highlights`, `keywords` | `projects[].name`, `description`, `url`, `highlights`, `keywords` |
|
|
93
|
+
| Project `roles`, or scalar `role` wrapped in an array | `projects[].roles` |
|
|
94
|
+
| `publications.name`, `publisher`, `release_date`, `url`, `summary` | `publications[].name`, `publisher`, `releaseDate`, `url`, `summary` |
|
|
95
|
+
| `references.name`, `reference` | `references[].name`, `reference` |
|
|
96
|
+
|
|
97
|
+
Experience and volunteering remain one record per role; company groups and
|
|
98
|
+
newest-first role ordering follow the HTML renderer. Supported social networks
|
|
99
|
+
are the ones rendered by the existing social include: GitHub, LinkedIn,
|
|
100
|
+
Telegram, Twitter, Medium, Dribbble, Facebook, Instagram, Website, WhatsApp,
|
|
101
|
+
Dev.to, Flickr, Pinterest, and YouTube. Their configured keys identify networks
|
|
102
|
+
in the export. Social URLs remain strings; nested `{url, username}` objects are
|
|
103
|
+
not introduced by this feature.
|
|
104
|
+
|
|
105
|
+
## Optional enrichment
|
|
106
|
+
|
|
107
|
+
The optional per-section fields that enrich the export are listed in [JSON Resume enrichment](data-schemas.md#json-resume-enrichment). A `skills.yml` example is in [Publish the JSON Resume export](../how-to/publish-json-resume.md#3-optionally-enrich-skills).
|
|
108
|
+
|
|
109
|
+
## Dates, text, and URLs
|
|
110
|
+
|
|
111
|
+
Dates must be real calendar dates in `YYYY`, `YYYY-MM`, or `YYYY-MM-DD` form.
|
|
112
|
+
**Certificate `issue_date` is the exception:** the pinned schema requires a full
|
|
113
|
+
`YYYY-MM-DD`. A partial certificate date remains valid website data but is
|
|
114
|
+
omitted from JSON with a warning; the exporter never guesses a month or day.
|
|
115
|
+
|
|
116
|
+
Blank end dates, `Present` (case-insensitive), and the effective locale's
|
|
117
|
+
`present_values` omit `endDate` silently. Free-form `duration`, `durations`, and
|
|
118
|
+
education `year` are not parsed into dates. Date of birth is not exported.
|
|
119
|
+
|
|
120
|
+
Raw HTML tags are removed, block boundaries preserved as newlines, and HTML
|
|
121
|
+
entities decoded to Unicode. Markdown and native multilingual characters remain.
|
|
122
|
+
Liquid-like text is serialized literally; generated JSON bypasses Liquid and
|
|
123
|
+
layouts during Jekyll rendering.
|
|
124
|
+
|
|
125
|
+
Links must resolve to absolute HTTP(S) URLs. Local paths use `site.url` and
|
|
126
|
+
`site.baseurl`; without `site.url`, relative links are omitted with a warning.
|
|
127
|
+
Social profile URLs must already be absolute. Invalid email addresses, country
|
|
128
|
+
codes, dates, and URLs are omitted with contextual warnings that do not print
|
|
129
|
+
field values. Empty fields, arrays, objects, and sections are omitted.
|
|
130
|
+
|
|
131
|
+
## Standard and omissions
|
|
132
|
+
|
|
133
|
+
Validation uses the vendored [JSON Resume 1.0.0 schema](https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json)
|
|
134
|
+
(Draft 4) through `json_schemer`. Builds perform no schema downloads. `$schema`
|
|
135
|
+
points to this pinned version; `meta.version` is `1.0.0`, and `meta.lastModified`
|
|
136
|
+
is the build timestamp in UTC. Schema validation is distinct from source YAML validation. If final schema validation unexpectedly fails,
|
|
137
|
+
the document is skipped and no discovery link is emitted.
|
|
138
|
+
|
|
139
|
+
Associations, standalone courses, generic links, date of birth, skill narrative
|
|
140
|
+
descriptions, education honors/summaries, certificate IDs/expiration, and
|
|
141
|
+
free-form display date ranges have no mapping in this exporter. Publications and
|
|
142
|
+
references export exactly as authored, with no extra fields; a publication's `summary` is exported regardless of `enable_summary`. References are
|
|
143
|
+
public once exported, so include only what each referee agreed to publish. Nested certificate courses
|
|
144
|
+
remain internal data and are not exported.
|
|
145
|
+
|
|
146
|
+
## Verification
|
|
147
|
+
|
|
148
|
+
Test coverage for the export is described in [JSON Resume export coverage](testing-suites.md#json-resume-export-coverage).
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Layouts reference (`_layouts/`)
|
|
2
|
+
|
|
3
|
+
*Audience: site owners and theme developers*
|
|
4
|
+
|
|
5
|
+
The theme's layouts in [`_layouts/`](../../_layouts): what each one renders, how the resume layout resolves its language and data, and where a custom layout fits. To build one, see [Create a custom layout](../how-to/create-a-custom-layout.md).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview & Layout Hierarchy
|
|
10
|
+
|
|
11
|
+
Hand-authored pages select a layout in front matter. The theme also generates missing CV/profile pages; see [page configuration](config.md#3-languages). A resume page is `layout: resume` plus `lang: <code>`; there is no per-language layout.
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
_layouts/default.html (base shell: <head>, anti-FOUC, dark mode, footer)
|
|
15
|
+
└── _layouts/error.html (HTTP 404, 403, 500, 503)
|
|
16
|
+
|
|
17
|
+
_layouts/profile.html (standalone landing page)
|
|
18
|
+
_layouts/resume.html (standalone resume, one layout for every language and direction)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Language Resolution
|
|
24
|
+
|
|
25
|
+
The common language-resolution pattern is shown below. Some includes accept `include.lang`; the default/profile layouts also fall back to the English locale if neither the page nor default locale exists:
|
|
26
|
+
|
|
27
|
+
```liquid
|
|
28
|
+
{% assign lang = page.lang | default: site.default_lang | default: 'en' %}
|
|
29
|
+
{% assign locale = site.data.locales[lang] | default: site.data.locales[site.default_lang] %}
|
|
30
|
+
{% assign lang_cfg = site.languages[lang] %}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `locale` is the merged locale file `_data/locales/<lang>.yml` (theme file with any site override on top). It supplies `direction`, fonts, line height, UI strings, month names, and error copy.
|
|
34
|
+
- `lang_cfg` is the `languages.<lang>` config block. It supplies `data_path`, `url`, `header_intro`, `name`, `resume_title`, `address`, and `avatar_alt`.
|
|
35
|
+
|
|
36
|
+
The active language resolves in this order: `page.lang`, then `site.default_lang`, then `en`. The repository's own demo pages are in [`demo/`](../../demo/). English and Arabic CVs are hand-authored there; Spanish, French, German, and Urdu CVs are generated.
|
|
37
|
+
|
|
38
|
+
The locale schema is in [Locale keys](locale-keys.md); the `languages` schema is in [Configuration reference](config.md#3-languages).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Layout Inventory
|
|
43
|
+
|
|
44
|
+
### 1. `default.html` (Base Layout)
|
|
45
|
+
|
|
46
|
+
- **File:** [`_layouts/default.html`](../../_layouts/default.html)
|
|
47
|
+
- **Role:** Shell for markdown pages and error pages.
|
|
48
|
+
- `<html lang="{{ lang }}" dir="{{ locale.direction }}">`, skip link text from `locale.ui.skip_to_content`.
|
|
49
|
+
- Includes [`shared-head.html`](../../_includes/shared-head.html), a stylesheet link to `assets/css/main.css`, `{% seo %}`, and the analytics includes.
|
|
50
|
+
- Emits `<link rel="me">` when `site.social_links.mastodon` is set.
|
|
51
|
+
- Includes [`dark-mode-toggle.html`](../../_includes/dark-mode-toggle.html) and [`language-switcher.html`](../../_includes/language-switcher.html) (also present on error pages unless disabled by site/page settings), and wraps content in `<main class="main-content" id="main-content">`.
|
|
52
|
+
|
|
53
|
+
### 2. `profile.html` (Portfolio Landing)
|
|
54
|
+
|
|
55
|
+
- **File:** [`_layouts/profile.html`](../../_layouts/profile.html)
|
|
56
|
+
- **Role:** Standalone landing page, independent of `default.html` so its centering styles do not leak.
|
|
57
|
+
- Same `lang` / `dir` resolution, skip link, dark mode toggle, and language switcher as `default.html`.
|
|
58
|
+
- Uses `languages.<lang>.name`, optional `name_html`, and `about` for its header; the CV button points to `languages.<lang>.url`. Calls `hreflang.html` for translated profile pages sharing `t_id: profile`.
|
|
59
|
+
- Social icons come from [`social-links.html`](../../_includes/social-links.html). When `social_links` is set, `contact_info.email` is set, and `social_links.email` is not, one extra labelled email icon is added.
|
|
60
|
+
- Stylesheet [`assets/css/profile.scss`](../../assets/css/profile.scss) (compiled to `assets/css/profile.css`, linked directly in the layout's `<head>`), styled by [`_sass/_profile-page.scss`](../../_sass/_profile-page.scss).
|
|
61
|
+
|
|
62
|
+
### 3. `resume.html` (Resume, Every Language)
|
|
63
|
+
|
|
64
|
+
- **File:** [`_layouts/resume.html`](../../_layouts/resume.html)
|
|
65
|
+
- **Role:** The resume for any configured language, LTR or RTL.
|
|
66
|
+
- **Head:**
|
|
67
|
+
- `<html lang="{{ lang }}" dir="{{ locale.direction }}">`.
|
|
68
|
+
- Loads `locale.font_url` when set, else the default Lora and Open Sans stylesheet; neither loads when `disable_google_fonts: true` or `resume_theme: no-custom-fonts`.
|
|
69
|
+
- Emits `--font-locale` (from `locale.font_family`, when non-empty) and `--line-height-locale` (from `locale.line_height`) in an inline `:root` style.
|
|
70
|
+
- Links `assets/css/cv-{{ locale.direction }}.css`, so LTR locales get `cv-ltr.css` and RTL locales get `cv-rtl.css`.
|
|
71
|
+
- Includes [`hreflang.html`](../../_includes/hreflang.html), `{% seo %}`, and the analytics head include.
|
|
72
|
+
- **Header:** avatar (when `resume_avatar: true`), `lang_cfg.name`, the contact row (when `display_header_contact_info: true`), the header language list (when `display_header_contact_info: true` and `resume_section.lang_header` is set), `lang_cfg.resume_title`, social icons (when `social_links` is set), the `header.yml` intro (when `lang_cfg.header_intro: true`), and the contact button (per `resume_looking_for_work`).
|
|
73
|
+
- **Contact row:** icon first, then text, in every direction. Phone numbers and emails carry `dir="ltr"`. The date of birth goes through [`date-formatter.html`](../../_includes/date-formatter.html).
|
|
74
|
+
- **Body:** loops `site.resume_section_order` through [`resume-section.html`](../../_includes/resume-section.html), then the print-only social links section when `resume_print_social_links` is set.
|
|
75
|
+
- **Footer:** localized "last generated" line and, when `enable_live == false`, a print-only footer with the page's permalink.
|
|
76
|
+
|
|
77
|
+
### 4. `error.html` (Multilingual HTTP Error Suite)
|
|
78
|
+
|
|
79
|
+
- **File:** [`_layouts/error.html`](../../_layouts/error.html), extends `default.html`. Used by `404.html`, `403.html`, and `500.html`.
|
|
80
|
+
- Reads `page.code` (default `"404"`) and server-renders a single heading/message block in `default_lang`.
|
|
81
|
+
- On `DOMContentLoaded`, the inline script removes `site.baseurl` from the start of `window.location.pathname`, then checks whether the rest begins with a configured `/<lang>/` prefix (so `/portfolio/ar/missing` on a `baseurl: /portfolio` site shows Arabic). It updates the block’s text, `lang`, and `dir`, plus the Home button’s label and destination. It does not use browser language or a stored preference.
|
|
82
|
+
- With JavaScript disabled, or without a matching prefix, the default-language content remains. The script changes the error block, not the outer page’s language or switcher label.
|
|
83
|
+
- The single Home link prefers the selected language’s `layout: profile` page, then `languages.<lang>.url`, then `/`. Reload is shown for `500`, `503`, or `page.show_reload: true`; its label remains in the default locale.
|
|
84
|
+
- There is no search form or per-language return-link list in the current error layout. Search is not currently supported.
|
|
85
|
+
- [`_plugins/error_pages_generator.rb`](../../_plugins/error_pages_generator.rb) creates missing `404.html`, `403.html`, and `500.html`. A manual `layout: error`, `code: 503` page is supported but is not generated automatically. Load the theme through the Gemfile’s `:jekyll_plugins` group or the config’s `plugins:` list.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Dynamic Data Resolution
|
|
90
|
+
|
|
91
|
+
`resume.html` calls [`_includes/data-loader.html`](../../_includes/data-loader.html) with `path=lang_cfg.data_path`. The include binds `resume_data` by walking `site.data` one dot-separated segment at a time:
|
|
92
|
+
|
|
93
|
+
```liquid
|
|
94
|
+
{%- assign resume_data = site.data -%}
|
|
95
|
+
{%- if data_path.size > 0 -%}
|
|
96
|
+
{%- assign path_parts = data_path | split: '.' -%}
|
|
97
|
+
{%- for part in path_parts -%}
|
|
98
|
+
{%- assign resume_data = resume_data[part] -%}
|
|
99
|
+
{%- endfor -%}
|
|
100
|
+
{%- endif -%}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
See [data-driven model](../explanation/data-driven-model.md) for why presence checks avoid `!= blank` and why bracket access is used.
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
languages:
|
|
107
|
+
en:
|
|
108
|
+
data_path: en # site.data.en
|
|
109
|
+
ar:
|
|
110
|
+
data_path: "2025-06.v1-ar" # site.data["2025-06"]["v1-ar"]
|
|
111
|
+
es:
|
|
112
|
+
data_path: "" # site.data (files directly in _data/)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
When called without `path`, the include falls back to `site.languages[page.lang or default_lang].data_path`.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Resume Rendering Pipeline
|
|
120
|
+
|
|
121
|
+
The order of operations for one resume page:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
1. Resolve lang, locale, lang_cfg; load resume_data from lang_cfg.data_path
|
|
125
|
+
2. <head>: shared-head, locale font + CSS variables, cv-<direction>.css, hreflang, SEO, analytics
|
|
126
|
+
3. Body start: analytics-body, dark-mode-toggle, language-switcher
|
|
127
|
+
4. Header: avatar, name, contact row, header languages, title, social icons, intro, contact button
|
|
128
|
+
5. Sections: for each name in site.resume_section_order
|
|
129
|
+
{% include resume-section.html section_name=section_name lang=lang %}
|
|
130
|
+
6. Print-only social links (print-social-links.html)
|
|
131
|
+
7. Footer and print-only permalink footer
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Dark Mode & Anti-FOUC Mechanics
|
|
137
|
+
|
|
138
|
+
Layouts include [`dark-mode-toggle.html`](../../_includes/dark-mode-toggle.html). To configure it, see [Enable dark mode](../how-to/enable-dark-mode.md); for the anti-FOUC script and the two activation tiers, see [dark mode approach](../explanation/dark-mode-approach.md).
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Creating Custom Layouts
|
|
143
|
+
|
|
144
|
+
See [Create a custom layout](../how-to/create-a-custom-layout.md).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Locale Keys
|
|
2
|
+
|
|
3
|
+
*Audience: site owners and theme developers*
|
|
4
|
+
|
|
5
|
+
Reference for the locale file schema (`_data/locales/<lang>.yml`), the shipped locales, and the rules that govern overriding them.
|
|
6
|
+
|
|
7
|
+
## Shipped locales
|
|
8
|
+
|
|
9
|
+
The theme ships six locales: English (`en`, LTR), Arabic (`ar`, RTL), Spanish (`es`, LTR), French (`fr`, LTR), German (`de`, LTR), and Urdu (`ur`, RTL). Any other language is added by the site alone, with no Ruby, HTML, or SCSS changes.
|
|
10
|
+
|
|
11
|
+
Shipped fonts and line heights:
|
|
12
|
+
|
|
13
|
+
| Locale | `direction` | `font_family` | `line_height` |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `en`, `es`, `fr`, `de` | `ltr` | empty (theme default stacks) | 1.5 |
|
|
16
|
+
| `ar` | `rtl` | `'Cairo', sans-serif` | 1.6 |
|
|
17
|
+
| `ur` | `rtl` | `'Noto Nastaliq Urdu', serif` | 2.0 |
|
|
18
|
+
|
|
19
|
+
## Locale file keys
|
|
20
|
+
|
|
21
|
+
Every locale file has the same key set. The validator warns when a language's effective locale is missing a key that the reference locale (`en` by default) has.
|
|
22
|
+
|
|
23
|
+
| Key | Purpose |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `direction` | `ltr` or `rtl`. Sets `<html dir>` and selects `assets/css/cv-ltr.css` or `cv-rtl.css`. |
|
|
26
|
+
| `font_family` | CSS font stack emitted as `--font-locale`. Empty string keeps the theme's default stacks (Lora and Open Sans). |
|
|
27
|
+
| `font_url` | Stylesheet URL for the font (usually Google Fonts). Empty string loads the default Lora and Open Sans stylesheet. |
|
|
28
|
+
| `line_height` | Emitted as `--line-height-locale` for resume body text. |
|
|
29
|
+
| `ui.*` | Every UI string the templates render: skip link, "Present", contact button, dark mode toggle label, language switcher label, `language_name` (the language's own name, shown in the switcher and on error page return links), `list_separator`, and more. |
|
|
30
|
+
| `ui.section_titles.*` | One heading per resume section (`experience`, `education`, ... `links`, `publications`, `references`). |
|
|
31
|
+
| `ui.social_labels.*` | Platform names: the accessible name (`aria-label`, `title`, screen-reader text) of each social icon and the labels of the print-only contact list. |
|
|
32
|
+
| `error_pages."404"` / `"403"` / `"500"` / `"503"` | `title` and `message` for each HTTP error page. |
|
|
33
|
+
| `present_values` | Case-insensitive words that mean "ongoing" in `enddate` fields. A match renders `ui.present` instead of a date. |
|
|
34
|
+
| `months` | The 12 month names, January first. Dates render as `<month> <year>`; a year-only date renders as the year. |
|
|
35
|
+
|
|
36
|
+
Read the shipped files in [`../../_data/locales/`](../../_data/locales/) for the full key list and current values.
|
|
37
|
+
|
|
38
|
+
## `error_pages`
|
|
39
|
+
|
|
40
|
+
Error page text is not in the data folders. Each locale file carries an `error_pages` map, and [`../../_layouts/error.html`](../../_layouts/error.html) server-renders one block in `default_lang`. JavaScript can replace that block using a configured language prefix at the start of the requested URL:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
# _data/locales/en.yml (excerpt)
|
|
44
|
+
error_pages:
|
|
45
|
+
"404":
|
|
46
|
+
title: "Page Not Found"
|
|
47
|
+
message: "The page you are looking for might have been removed, had its name changed, or is temporarily unavailable."
|
|
48
|
+
"403": { title: "...", message: "..." }
|
|
49
|
+
"500": { title: "...", message: "..." }
|
|
50
|
+
"503": { title: "...", message: "..." }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## "Present" values
|
|
54
|
+
|
|
55
|
+
An `enddate` may be a word meaning "ongoing" instead of a date. For a given language the accepted words are that language's effective locale `present_values` plus its `ui.present` label, compared case-insensitively. A language with no locale falls back to the `default_lang` locale. The shipped values:
|
|
56
|
+
|
|
57
|
+
| Language | `present_values` | `ui.present` |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `en` | `present`, `current` | `Present` |
|
|
60
|
+
| `ar` | `present`, `حتى الآن`, `حاليًا` | `حتى الآن` |
|
|
61
|
+
|
|
62
|
+
Read the other four in [`_data/locales/`](../../_data/locales). To accept more words, see [Override locale strings](../how-to/override-locale-strings.md#accept-more-present-words).
|
|
63
|
+
|
|
64
|
+
The template side ([`_includes/date-formatter.html`](../../_includes/date-formatter.html)) matches `present_values`. If you change `ui.present`, include that label in `present_values` too: the validator accepts the label automatically, but the formatter checks only the list.
|
|
65
|
+
|
|
66
|
+
## Overriding theme locales
|
|
67
|
+
|
|
68
|
+
The six locale files ship inside the theme gem. Jekyll reads theme data first and then deep-merges the site's `_data/` over it; the site wins.
|
|
69
|
+
|
|
70
|
+
- **Nested keys merge one by one.** A site file `_data/locales/<lang>.yml` containing only some keys changes those keys and leaves every other string intact.
|
|
71
|
+
- **Arrays are replaced whole, never merged.** Overriding `months` or `present_values` requires the complete list; a one-item `months` array leaves the other eleven months blank.
|
|
72
|
+
- **A new language has no theme file to merge with,** so its `_data/locales/<lang>.yml` must contain every key.
|
|
73
|
+
|
|
74
|
+
The validator builds each locale the same way (theme file, then site file deep-merged over it) and checks key parity on the merged result. A one-line override produces no warnings; an incomplete site-only locale does.
|
|
75
|
+
|
|
76
|
+
Task steps: [override locale strings](../how-to/override-locale-strings.md), [add a language](../how-to/add-a-language.md). Design background: [multilingual and RTL design](../explanation/multilingual-and-rtl-design.md).
|