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.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/403.html +6 -0
  3. data/404.html +6 -0
  4. data/500.html +6 -0
  5. data/CHANGELOG.md +498 -0
  6. data/CODE_OF_CONDUCT.md +92 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +184 -0
  9. data/SECURITY.md +30 -0
  10. data/_config.sample.yml +329 -0
  11. data/_data/locales/ar.yml +85 -0
  12. data/_data/locales/de.yml +86 -0
  13. data/_data/locales/en.yml +84 -0
  14. data/_data/locales/es.yml +86 -0
  15. data/_data/locales/fr.yml +86 -0
  16. data/_data/locales/ur.yml +86 -0
  17. data/_data/social_networks.yml +83 -0
  18. data/_includes/analytics-body.html +12 -0
  19. data/_includes/analytics-head.html +36 -0
  20. data/_includes/avatar.html +84 -0
  21. data/_includes/dark-mode-toggle.html +202 -0
  22. data/_includes/data-loader.html +42 -0
  23. data/_includes/date-formatter.html +63 -0
  24. data/_includes/grouped-item-list.html +53 -0
  25. data/_includes/hreflang.html +58 -0
  26. data/_includes/language-switcher.html +72 -0
  27. data/_includes/print-social-links.html +23 -0
  28. data/_includes/resume-section.html +547 -0
  29. data/_includes/safe-url.html +21 -0
  30. data/_includes/shared-head.html +51 -0
  31. data/_includes/social-links.html +54 -0
  32. data/_includes/vendors/svg-icons/ATTRIBUTION.md +34 -0
  33. data/_includes/vendors/svg-icons/dev.svg +3 -0
  34. data/_includes/vendors/svg-icons/dribbble-symbol.svg +3 -0
  35. data/_includes/vendors/svg-icons/envelope.svg +4 -0
  36. data/_includes/vendors/svg-icons/facebook.svg +3 -0
  37. data/_includes/vendors/svg-icons/flickr.svg +4 -0
  38. data/_includes/vendors/svg-icons/github.svg +3 -0
  39. data/_includes/vendors/svg-icons/globe-1.svg +3 -0
  40. data/_includes/vendors/svg-icons/instagram.svg +4 -0
  41. data/_includes/vendors/svg-icons/linkedin.svg +3 -0
  42. data/_includes/vendors/svg-icons/medium.svg +3 -0
  43. data/_includes/vendors/svg-icons/phone.svg +13 -0
  44. data/_includes/vendors/svg-icons/pinterest.svg +3 -0
  45. data/_includes/vendors/svg-icons/postcard.svg +14 -0
  46. data/_includes/vendors/svg-icons/telegram.svg +3 -0
  47. data/_includes/vendors/svg-icons/whatsapp.svg +3 -0
  48. data/_includes/vendors/svg-icons/x.svg +3 -0
  49. data/_includes/vendors/svg-icons/youtube.svg +3 -0
  50. data/_layouts/default.html +65 -0
  51. data/_layouts/error.html +138 -0
  52. data/_layouts/profile.html +123 -0
  53. data/_layouts/resume.html +238 -0
  54. data/_plugins/error_pages_generator.rb +71 -0
  55. data/_plugins/json_resume_generator.rb +79 -0
  56. data/_plugins/resume_pages_generator.rb +71 -0
  57. data/_plugins/resume_validator.rb +40 -0
  58. data/_sass/_all-pages.scss +335 -0
  59. data/_sass/_base.scss +130 -0
  60. data/_sass/_dark-mode.scss +387 -0
  61. data/_sass/_layout.scss +116 -0
  62. data/_sass/_mixins.scss +136 -0
  63. data/_sass/_normalize.scss +379 -0
  64. data/_sass/_profile-page.scss +60 -0
  65. data/_sass/_resume-ltr.scss +436 -0
  66. data/_sass/_resume-rtl.scss +91 -0
  67. data/_sass/_variables.scss +45 -0
  68. data/assets/css/cv-ltr.scss +31 -0
  69. data/assets/css/cv-rtl.scss +36 -0
  70. data/assets/css/main.scss +8 -0
  71. data/assets/css/profile.scss +9 -0
  72. data/assets/favicon/resume/about.txt +6 -0
  73. data/assets/favicon/resume/android-chrome-192x192.png +0 -0
  74. data/assets/favicon/resume/android-chrome-512x512.png +0 -0
  75. data/assets/favicon/resume/apple-touch-icon.png +0 -0
  76. data/assets/favicon/resume/favicon-16x16.png +0 -0
  77. data/assets/favicon/resume/favicon-32x32.png +0 -0
  78. data/assets/favicon/resume/favicon.ico +0 -0
  79. data/assets/favicon/resume/site.webmanifest +1 -0
  80. data/bin/validate-resume +89 -0
  81. data/bin/verify +27 -0
  82. data/docs/README.md +89 -0
  83. data/docs/explanation/accessibility-decisions.md +27 -0
  84. data/docs/explanation/architecture.md +44 -0
  85. data/docs/explanation/dark-mode-approach.md +25 -0
  86. data/docs/explanation/data-driven-model.md +25 -0
  87. data/docs/explanation/multilingual-and-rtl-design.md +40 -0
  88. data/docs/how-to/add-a-language.md +57 -0
  89. data/docs/how-to/add-a-section.md +35 -0
  90. data/docs/how-to/add-a-social-platform.md +32 -0
  91. data/docs/how-to/add-a-test.md +34 -0
  92. data/docs/how-to/create-a-custom-layout.md +64 -0
  93. data/docs/how-to/enable-dark-mode.md +43 -0
  94. data/docs/how-to/link-translations-with-hreflang.md +25 -0
  95. data/docs/how-to/migrate-v0.9-to-v1.0.md +46 -0
  96. data/docs/how-to/override-locale-strings.md +51 -0
  97. data/docs/how-to/override-sass-partials.md +26 -0
  98. data/docs/how-to/proof-built-html.md +22 -0
  99. data/docs/how-to/publish-json-resume.md +38 -0
  100. data/docs/how-to/show-language-proficiency-in-header.md +13 -0
  101. data/docs/how-to/switch-resume-versions.md +42 -0
  102. data/docs/how-to/troubleshoot-builds.md +39 -0
  103. data/docs/how-to/validate-in-ci.md +38 -0
  104. data/docs/how-to/verify-accessibility.md +28 -0
  105. data/docs/reference/accessibility-coverage.md +39 -0
  106. data/docs/reference/config.md +378 -0
  107. data/docs/reference/data-schemas.md +529 -0
  108. data/docs/reference/glossary.md +39 -0
  109. data/docs/reference/includes.md +197 -0
  110. data/docs/reference/json-resume-fields.md +148 -0
  111. data/docs/reference/layouts.md +144 -0
  112. data/docs/reference/locale-keys.md +76 -0
  113. data/docs/reference/repository-map.md +83 -0
  114. data/docs/reference/sass-tokens.md +279 -0
  115. data/docs/reference/testing-suites.md +62 -0
  116. data/docs/reference/validator-cli.md +203 -0
  117. data/docs/tutorials/getting-started.md +173 -0
  118. data/lib/jekyll-theme-resume/json_resume_exporter.rb +334 -0
  119. data/lib/jekyll-theme-resume/resume_validator.rb +853 -0
  120. data/lib/jekyll-theme-resume/schemas/LICENSE.md +21 -0
  121. data/lib/jekyll-theme-resume/schemas/json_resume_v1.0.0.json +500 -0
  122. data/lib/jekyll-theme-resume.rb +21 -0
  123. metadata +371 -0
@@ -0,0 +1,83 @@
1
+ # Repository Map
2
+
3
+ *Audience: theme developers*
4
+
5
+ Annotated tree of the theme's code directories. The `docs/` directory is not listed; see the [documentation index](../README.md).
6
+
7
+ ```text
8
+ jekyll-theme-resume/
9
+ ├── 403.html / 404.html / 500.html # Root HTTP error pages (layout: error)
10
+ │
11
+ ├── _layouts/
12
+ │ ├── default.html # Base HTML shell
13
+ │ ├── profile.html # Standalone landing page
14
+ │ ├── resume.html # Resume layout for every language (LTR and RTL)
15
+ │ └── error.html # HTTP error suite (404/403/500/503)
16
+ │
17
+ ├── _includes/
18
+ │ ├── resume-section.html # Section dispatcher (14 sections, every language)
19
+ │ ├── grouped-item-list.html # Shared Experience/Volunteering renderer
20
+ │ ├── date-formatter.html # Locale-driven date and "Present" formatting
21
+ │ ├── data-loader.html # Dot-path data resolution into resume_data
22
+ │ ├── shared-head.html # Meta, anti-FOUC script, favicons
23
+ │ ├── avatar.html # Profile picture
24
+ │ ├── safe-url.html # URL scheme allowlist (sets safe_url)
25
+ │ ├── dark-mode-toggle.html # Floating theme toggle
26
+ │ ├── language-switcher.html # Floating dropdown linking to every other configured language
27
+ │ ├── social-links.html # Social icons (email + 14 platforms)
28
+ │ ├── print-social-links.html # Print-only social links text list
29
+ │ ├── hreflang.html # Alternate-language SEO links
30
+ │ ├── analytics-head.html # GTM / GA4 head script
31
+ │ ├── analytics-body.html # GTM noscript body fallback
32
+ │ └── vendors/svg-icons/ # Bundled Lineicons SVGs (MIT; see ATTRIBUTION.md inside)
33
+ │
34
+ ├── _sass/
35
+ │ ├── _variables.scss # Widths, gutters, font stacks
36
+ │ ├── _dark-mode.scss # Color tokens, overrides, print reset
37
+ │ ├── _base.scss # Reset, .sr-only, base typography
38
+ │ ├── _layout.scss # Floating language-switcher styles
39
+ │ ├── _resume-ltr.scss # Main resume styles + LTR positioning
40
+ │ ├── _resume-rtl.scss # Language-neutral RTL overrides
41
+ │ ├── _profile-page.scss # Landing page styles
42
+ │ ├── _all-pages.scss # Shared markdown typography, icon links, footer, error-page styles
43
+ │ ├── _mixins.scss # Breakpoints and font mixins
44
+ │ └── _normalize.scss # Normalize.css v8.0.1
45
+ │
46
+ ├── assets/
47
+ │ ├── css/
48
+ │ │ ├── cv-ltr.scss # Resume entrypoint for LTR locales
49
+ │ │ ├── cv-rtl.scss # Resume entrypoint for RTL locales
50
+ │ │ ├── profile.scss # Profile page entrypoint
51
+ │ │ └── main.scss # Default/error pages entrypoint
52
+ │ └── favicon/resume/ # Favicon suite
53
+ │
54
+ ├── _data/
55
+ │ ├── locales/ # en, ar, es, fr, de, ur locale dictionaries
56
+ │ └── social_networks.yml # Shared platform list for social-links.html / print-social-links.html
57
+ │
58
+ ├── _plugins/
59
+ │ ├── error_pages_generator.rb # Synthesizes missing HTTP error pages
60
+ │ ├── json_resume_generator.rb # Publishes /<lang>/resume.json (and /resume.json for default_lang) via JsonResumeExporter
61
+ │ ├── resume_pages_generator.rb # Synthesizes missing CV/profile pages per language
62
+ │ └── resume_validator.rb # Build-time validation (on by default)
63
+ │
64
+ ├── lib/
65
+ │ ├── jekyll-theme-resume.rb # Gem entrypoint
66
+ │ └── jekyll-theme-resume/
67
+ │ ├── json_resume_exporter.rb # Maps resume data to a JSON Resume v1.0.0 document
68
+ │ ├── resume_validator.rb # Validator engine
69
+ │ ├── template_key_checker.rb # Template checker (repository-only)
70
+ │ └── schemas/
71
+ │ ├── json_resume_v1.0.0.json # Bundled JSON Resume schema used to validate exports
72
+ │ └── LICENSE.md # License for the bundled schema
73
+ │
74
+ ├── bin/
75
+ │ ├── validate-resume # Validator CLI (gem executable)
76
+ │ ├── check-data-keys # Template checker (repository-only)
77
+ │ └── release # Release script (not packaged)
78
+ │
79
+ ├── test/ # Minitest suites; see reference/testing-suites.md
80
+ └── Rakefile # validate, check_data_keys, test, rubocop, proof, default
81
+ ```
82
+
83
+ Test suites are described in [testing suites](testing-suites.md). Layouts, includes, and Sass are detailed in [layouts](layouts.md), [includes](includes.md), and [Sass tokens](sass-tokens.md).
@@ -0,0 +1,279 @@
1
+ # Sass reference (`_sass/`)
2
+
3
+ *Audience: theme developers*
4
+
5
+ Reference for the theme's styling system in [`_sass/`](../../_sass) and the entrypoint stylesheets in [`assets/css/`](../../assets/css): Dart Sass module architecture, partial inventory, and the dark mode tokens.
6
+
7
+ ---
8
+
9
+ ## Entrypoints & Compilation Architecture
10
+
11
+ ### Stylesheet Entrypoints
12
+
13
+ Jekyll compiles files in [`assets/css/`](../../assets/css) that start with YAML front matter into final static CSS assets:
14
+
15
+ | Source SCSS | Compiled Output CSS | Consuming Layouts |
16
+ |---|---|---|
17
+ | [`assets/css/cv-ltr.scss`](../../assets/css/cv-ltr.scss) | `assets/css/cv-ltr.css` | [`_layouts/resume.html`](../../_layouts/resume.html) for every locale with `direction: ltr` |
18
+ | [`assets/css/cv-rtl.scss`](../../assets/css/cv-rtl.scss) | `assets/css/cv-rtl.css` | [`_layouts/resume.html`](../../_layouts/resume.html) for every locale with `direction: rtl` |
19
+ | [`assets/css/profile.scss`](../../assets/css/profile.scss) | `assets/css/profile.css` | [`_layouts/profile.html`](../../_layouts/profile.html) |
20
+ | [`assets/css/main.scss`](../../assets/css/main.scss) | `assets/css/main.css` | [`_layouts/default.html`](../../_layouts/default.html), [`_layouts/error.html`](../../_layouts/error.html) |
21
+
22
+ ### Modern Dart Sass `@use` Architecture
23
+
24
+ The theme uses `@use` exclusively. The reasons are in [Why `@use` instead of `@import`](../explanation/architecture.md#why-use-instead-of-import).
25
+
26
+ ### Overriding Partials in a Consuming Site
27
+
28
+ Steps: [Override Sass partials](../how-to/override-sass-partials.md).
29
+
30
+ ---
31
+
32
+ ## SCSS Partial Inventory
33
+
34
+ ### 1. `_variables.scss`
35
+
36
+ - **File:** [`_sass/_variables.scss`](../../_sass/_variables.scss)
37
+ - **Role:** Font stacks and default sizes. Only `$white` and `$text_color` are read (by `_base.scss`, as fallbacks for `--bg-color` and `--text-color`); the other variables are currently unused.
38
+ - **Variables:**
39
+ - `$white`, `$text_color`: color fallbacks. They are not declared with `!default`, so they cannot be configured with `@use ... with`.
40
+ - `$container-width: 980px !default;`, `$grid-gutter: 10px !default;`, `$body-font`, `$mono-font`, `$body-font-size: 13px !default;`: declared but not used by any partial or entrypoint.
41
+
42
+ ---
43
+
44
+ ### 2. `_mixins.scss`
45
+
46
+ - **File:** [`_sass/_mixins.scss`](../../_sass/_mixins.scss)
47
+ - **Role:** Breakpoint helpers, typography mixins, and border accents.
48
+ - **Key Mixins:**
49
+ - `@mixin media_mobile` (`max-width: 600px`)
50
+ - `@mixin media_larger_than_mobile` (`min-width: 600px`)
51
+ - `@mixin sans`, `@mixin serif`, `@mixin section_border`
52
+
53
+ ---
54
+
55
+ ### 3. `_normalize.scss`
56
+
57
+ - **File:** [`_sass/_normalize.scss`](../../_sass/_normalize.scss)
58
+ - **Role:** Normalize.css v8.0.1 browser baseline reset.
59
+
60
+ ---
61
+
62
+ ### 4. `_base.scss`
63
+
64
+ - **File:** [`_sass/_base.scss`](../../_sass/_base.scss)
65
+ - **Role:** HTML and body defaults, box-sizing, and screen-reader accessibility classes.
66
+ - **Key Features:**
67
+ - Universal `box-sizing: border-box`.
68
+ - `.sr-only` utility class for WCAG screen-reader announcements.
69
+ - Selection background and text styling.
70
+
71
+ ---
72
+
73
+ ### 5. `_layout.scss`
74
+
75
+ - **File:** [`_sass/_layout.scss`](../../_sass/_layout.scss)
76
+ - **Role:** Floating language switcher component (`.language-switcher`, fixed top-left, hidden in print). The former grid classes (`.container`, `.columns`, `.one-third`, and so on) were removed.
77
+
78
+ ---
79
+
80
+ ### 6. `_resume-ltr.scss`
81
+
82
+ - **File:** [`_sass/_resume-ltr.scss`](../../_sass/_resume-ltr.scss)
83
+ - **Role:** The main resume stylesheet: every shared rule plus LTR positioning. Both entrypoints load it; `cv-rtl.scss` then layers `_resume-rtl.scss` on top.
84
+ - **Typography:** resume text reads `var(--font-locale, <default stack>)` and `var(--line-height-locale, <default>)`, so each locale's font and line height apply without per-language rules.
85
+ - **Components Styled:**
86
+ - Header: Avatar (`.avatar`), candidate name, contact info row, and social links bar.
87
+ - Contact CTA button (`.contact-button`) and "not looking" modifier.
88
+ - Section headers (`.section-header`) and item cards (`.resume-item`).
89
+ - Two-column responsive Languages table.
90
+ - Print-specific media overrides (`@media print`).
91
+
92
+ ---
93
+
94
+ ### 7. `_resume-rtl.scss`
95
+
96
+ - **File:** [`_sass/_resume-rtl.scss`](../../_sass/_resume-rtl.scss)
97
+ - **Role:** Language-neutral RTL overrides scoped under `html[dir="rtl"]`, loaded last by `cv-rtl.scss`. Serves Arabic, Urdu, and any future RTL locale.
98
+ - **Key Overrides:**
99
+ - Flips horizontal floats, text alignments, borders, and margins.
100
+ - Repositions timeline bullets and contact icons for RTL reading order.
101
+ - Resets `letter-spacing` to `normal` on headings.
102
+ - Sets no `font-family` or `line-height`; those come from the locale CSS variables.
103
+
104
+ ---
105
+
106
+ <a id="8-_profile-pagescss"></a>
107
+ <a id="8-_profile-page-scss"></a>
108
+ ### 8. `_profile-page.scss`
109
+
110
+ - **File:** [`_sass/_profile-page.scss`](../../_sass/_profile-page.scss)
111
+ - **Role:** Styles for the dedicated portfolio landing page layout ([`_layouts/profile.html`](../../_layouts/profile.html)) and entrypoint [`assets/css/profile.scss`](../../assets/css/profile.scss).
112
+ - **Architecture:** Provides clean, unconstrained vertical centering, avatar, bio typography, and CV action button without duplicating universal footer or SVG icon styles (which are loaded from [`_all-pages.scss`](../../_sass/_all-pages.scss)).
113
+
114
+ ---
115
+
116
+ ### 9. `_all-pages.scss`
117
+
118
+ - **File:** [`_sass/_all-pages.scss`](../../_sass/_all-pages.scss)
119
+ - **Role:** Universal styles shared across all layouts.
120
+ - **Key Features:**
121
+ - Shared icon-link sizing, hover animations, and `.page-footer` spacing. Shared rules live here and `.sr-only` lives in `_base.scss`.
122
+ - Complete dark-mode-aware typography rules for markdown content in `.main-content` (headings, paragraphs, blockquotes, tables, lists, and code blocks).
123
+
124
+ ---
125
+
126
+ ### 10. `_dark-mode.scss`
127
+
128
+ - **File:** [`_sass/_dark-mode.scss`](../../_sass/_dark-mode.scss)
129
+ - **Role:** Single source of truth for all color tokens, theme overrides, and the floating toggle button.
130
+
131
+ ---
132
+
133
+ ## The Dark Mode Token System
134
+
135
+ ### Design Tokens Table
136
+
137
+ Shared color styles use CSS custom properties defined on `:root` in [`_sass/_dark-mode.scss`](../../_sass/_dark-mode.scss):
138
+
139
+ | CSS Custom Property | Light Mode Value | Dark Mode Value | Semantic Role |
140
+ |---|---|---|---|
141
+ | **Background & Typography** | | | |
142
+ | `--bg-color` | `#ffffff` | `#121212` | Main page and viewport background |
143
+ | `--text-color` | `#333` | `#e0e0e0` | Primary reading and heading typography |
144
+ | `--text-muted` | `#999` | `#888888` | Secondary copy, timestamps, and details |
145
+ | `--text-light` | `#646464` | `#aaaaaa` | Tertiary descriptive text |
146
+ | `--border-color` | `#c7c7c7` | `#333333` | Section dividers and card borders |
147
+ | `--card-bg` | `#efefef` | `#1e1e1e` | Button backgrounds and code blocks |
148
+ | **Links & Navigation** | | | |
149
+ | `--link-color` | `#333` | `#e0e0e0` | Interactive hyperlinks |
150
+ | `--link-hover` | `#9c9c9c` | `#ffffff` | Hyperlink hover color |
151
+ | `--link-hover-color` | `var(--link-hover)` | `var(--link-hover)` | Hyperlink hover alias |
152
+ | **Accent & Brand** | | | |
153
+ | `--accent-color` | `#3064a9` | `#6ba4e8` | Primary accents and focus outlines |
154
+ | `--accent-contrast-text` | `#ffffff` | `#121212` | Text and focus outline against the accent background |
155
+ | `--accent-hover` | `#307EA9` | `#8cbcf3` | Accent hover state |
156
+ | `--accent-hover-color` | `var(--accent-hover)` | `var(--accent-hover)` | Accent hover alias |
157
+ | `--social-hover-color` | `var(--accent-hover)` | `var(--accent-hover)` | Social icons hover color |
158
+ | `--about-color` | `var(--text-light)` | `var(--text-light)` | Executive summary / about text color |
159
+ | **Footer** | | | |
160
+ | `--footer-text-color` | `var(--text-muted)` | `var(--text-muted)` | Footer copyright typography |
161
+ | `--footer-link-color` | `var(--link-color)` | `var(--link-color)` | Footer hyperlink color |
162
+ | **Icons & Graphics** | | | |
163
+ | `--icon-fill` | `#333` | `#e0e0e0` | Social and contact SVG icon fill |
164
+ | `--icon-fill-muted` | `#555555` | `#888888` | Muted secondary icon fill |
165
+ | `--icon-fill-dark` | `#000` | `#ffffff` | Dark/prominent icon fill |
166
+ | `--icon-hover-fill` | `var(--icon-fill-dark)` | `var(--icon-fill-dark)` | Icon hover fill |
167
+ | `--header-icon-fill` | `var(--icon-fill-dark)` | `var(--icon-fill-dark)` | Header contact icon fill |
168
+ | **Buttons (.contact-button, .cv-button)** | | | |
169
+ | `--button-bg` | `#efefef` | `#2a2a2a` | Action button background |
170
+ | `--button-text` | `#333` | `#e0e0e0` | Action button label color |
171
+ | `--button-hover-bg` | `#333` | `#444444` | Action button hover background |
172
+ | `--button-hover-text` | `#fff` | `#ffffff` | Action button hover label color |
173
+ | **Text Selection** | | | |
174
+ | `--selection-bg` | `rgba(51, 51, 51, .8)` | `rgba(107, 164, 232, .5)` | Highlighted text background |
175
+ | `--selection-color` | `#fff` | `#ffffff` | Highlighted text color |
176
+ | **Dark Mode Toggle Component** | | | |
177
+ | `--toggle-btn-bg` | `transparent` | `transparent` | Toggle button background |
178
+ | `--toggle-btn-border` | `var(--border-color)` | `var(--border-color)` | Toggle button border |
179
+ | `--toggle-btn-color` | `var(--text-color)` | `var(--text-color)` | Toggle button icon color |
180
+ | `--toggle-btn-hover-bg` | `var(--button-bg)` | `var(--button-bg)` | Toggle button hover background |
181
+ | `--toggle-btn-focus-ring`| `var(--accent-color)`| `var(--accent-color)`| Toggle button focus ring outline |
182
+
183
+ ### Two-Tier Activation Mechanism
184
+
185
+ Why activation has two tiers: [Dark mode approach](../explanation/dark-mode-approach.md#two-tiers-of-activation).
186
+
187
+ 1. **Automatic Detection:**
188
+ ```scss
189
+ @media (prefers-color-scheme: dark) {
190
+ :root:not([data-color-scheme="light"]):not([data-theme="light"]) {
191
+ --bg-color: #121212;
192
+ --text-color: #e0e0e0;
193
+ // ...
194
+ }
195
+ }
196
+ ```
197
+ 2. **Explicit User Pin:**
198
+ ```scss
199
+ :root[data-color-scheme="dark"],
200
+ :root[data-theme="dark"] {
201
+ --bg-color: #121212;
202
+ --text-color: #e0e0e0;
203
+ // ...
204
+ }
205
+ ```
206
+
207
+ ### Print Media Resets
208
+
209
+ When printing to physical paper or PDF, [`_sass/_dark-mode.scss`](../../_sass/_dark-mode.scss) enforces a strict print reset:
210
+
211
+ ```scss
212
+ @media print {
213
+ :root,
214
+ :root[data-color-scheme="dark"],
215
+ :root[data-color-scheme="light"],
216
+ :root[data-theme="dark"],
217
+ :root[data-theme="light"],
218
+ [data-color-scheme="dark"],
219
+ [data-theme="dark"],
220
+ html.dark,
221
+ html.light {
222
+ color-scheme: light !important;
223
+ --bg-color: #ffffff !important;
224
+ --text-color: #000000 !important;
225
+ --border-color: #c7c7c7 !important;
226
+ --card-bg: #fff !important;
227
+ /* ...every other color token (text, link, accent, icon, button, selection) is reset the same way */
228
+ }
229
+ html,
230
+ body {
231
+ background-color: #fff !important;
232
+ color: #000 !important;
233
+ -webkit-print-color-adjust: exact;
234
+ print-color-adjust: exact;
235
+ }
236
+ .dark-mode-toggle,
237
+ #dark-mode-toggle {
238
+ display: none !important;
239
+ }
240
+ }
241
+ ```
242
+
243
+ The block resets every color token to black, white, or grey values, whichever scheme the page is pinned to, forces `html` and `body` to `#fff` and `#000`, and hides the toggle.
244
+
245
+ ---
246
+
247
+ ## WCAG 2.2 Accessibility & High-Contrast Standards
248
+
249
+ - **Contrast:** see [Accessibility decisions](../explanation/accessibility-decisions.md) for the pair-based contrast rule; [accessibility-coverage.md](accessibility-coverage.md) lists current limitations and verification steps.
250
+ - **Focus Rings:** Interactive elements feature high-contrast visible focus outlines:
251
+ ```scss
252
+ :focus-visible {
253
+ outline: 2px solid var(--accent-color, #3064a9);
254
+ outline-offset: 2px;
255
+ }
256
+ ```
257
+ - **Screen-Reader Utility:**
258
+ ```scss
259
+ .sr-only {
260
+ position: absolute;
261
+ width: 1px;
262
+ height: 1px;
263
+ padding: 0;
264
+ margin: -1px;
265
+ overflow: hidden;
266
+ clip: rect(0, 0, 0, 0);
267
+ white-space: nowrap;
268
+ border-width: 0;
269
+ text-decoration: none !important;
270
+ }
271
+ ```
272
+
273
+ ---
274
+
275
+ ## Locale Typography & RTL Mechanics
276
+
277
+ - **Per-locale typography:** `_layouts/resume.html` emits `--font-locale` (from the locale's `font_family`, when non-empty) and `--line-height-locale` (from `line_height`) in an inline `:root` style, and `_sass/_resume-ltr.scss` reads both with the theme defaults as fallbacks. Shipped font and line-height values: [locale-keys.md](locale-keys.md#shipped-locales). To change a language's font, see [Override locale strings](../how-to/override-locale-strings.md#change-a-languages-font).
278
+ - **Direction selects the entrypoint:** the layout links `cv-<direction>.css` (see the entrypoints table).
279
+ - **RTL scope:** overrides activate via `html[dir="rtl"]`; rationale in [Multilingual and RTL design](../explanation/multilingual-and-rtl-design.md#typography--rtl).
@@ -0,0 +1,62 @@
1
+ # Testing suites
2
+
3
+ *Audience: theme developers*
4
+
5
+ How the theme is tested, what each suite proves. Validator rules themselves are in [validator-cli.md](validator-cli.md); agent verification rules are in [AGENTS.md](../../AGENTS.md#rule-5-build--packaging-verification).
6
+
7
+ ## Test tasks
8
+
9
+ To run the suites or add a test, see [Add a test](../how-to/add-a-test.md).
10
+
11
+ The `test` task runs every `test/test_*.rb` file, so a new suite needs no Rakefile edit. Two error-page tests run the page's real JavaScript in Node and are skipped when `node` is not installed.
12
+
13
+ `bundle exec rake` (the default task) runs `validate`, `check_data_keys`, `rubocop`, and `test`. The default task runs `validate` with its default data directory (`_data` if `_data/en` exists, else `demo/_data`); pass another one with `rake "validate[path/to/_data]"`. There is no config-path argument; see [validator-cli.md](validator-cli.md#4-rake-task-rake-validate). HTML proofing runs separately after a build.
14
+
15
+ ## What each suite covers
16
+
17
+ Every test goes through a public interface; see [Tests use public interfaces only](../explanation/architecture.md#tests-use-public-interfaces-only).
18
+
19
+ | Suite | Interface under test | Covers |
20
+ |---|---|---|
21
+ | [`test_rendered_site.rb`](../../test/test_rendered_site.rb) | Generated HTML of a fixture site built from the theme's real `_layouts`, `_includes`, `_sass`, `_data`, `assets` | Each of the sections (fields, separators, inactive entries, grouping, RTL `dir="ltr"` isolation); date formats; header, contact bar, avatar, microdata; social and print links; hreflang; profile, default and error layouts (including the URL-language script and `baseurl`); config switches such as `enable_live`, `lang_header`, `baseurl`, analytics, dark mode |
22
+ | [`test_language_switcher.rb`](../../test/test_language_switcher.rb) | Generated HTML, six languages | Switcher links, labels, direction, page/site opt-out, error layout |
23
+ | [`test_resume_validator.rb`](../../test/test_resume_validator.rb) | `ResumeValidator#validate`, `bin/validate-resume`, build plugin | Every schema rule per section, dates, "Present" values, URLs, alias keys, locale merge and parity, config resolution, strict mode, CLI flags |
24
+ | [`test_template_key_checker.rb`](../../test/test_template_key_checker.rb) | `TemplateKeyChecker#check`, `bin/check-data-keys` | Liquid binding resolution, include boundaries, advisory exit codes |
25
+ | [`test_error_pages_generator.rb`](../../test/test_error_pages_generator.rb) | Site build | Generated 404/403/500 pages, site overrides, generator registration |
26
+ | [`test_resume_pages_generator.rb`](../../test/test_resume_pages_generator.rb) | Site build | Generated CV/profile pages, collisions, per-language and global toggles |
27
+ | [`test_json_resume_exporter.rb`](../../test/test_json_resume_exporter.rb) | `JsonResumeExporter.export`, site build | JSON Resume mapping, privacy, visibility, schema validation, routes; see [json-resume-fields.md](json-resume-fields.md) |
28
+ | [`test_packaging.rb`](../../test/test_packaging.rb) | Gemspec, shipped `_data` | Gem file list, six-locale key parity, unused locale keys, social icons and labels, demo data parity |
29
+ | [`test_doc_links.rb`](../../test/test_doc_links.rb) | The tracked Markdown files | Every relative link and `#anchor` in the docs resolves; links in code blocks and external URLs are skipped |
30
+
31
+ ## The rendered-site fixture
32
+
33
+ `RenderedSiteTest` writes fixture data for `en` (LTR) and `ar` (RTL) into a temporary site. Each section has an entry for every rendering rule, an entry with optional fields missing, and an `active: false` entry whose text starts with `hidden-`. Entries are tagged `EN`/`AR` so a test can tell which language's data rendered.
34
+
35
+ Config variants reuse the same fixture. Pass overrides and the site is built once per distinct override set:
36
+
37
+ ```ruby
38
+ cv("en", "enable_live" => true) # _site/en/cv/ built with enable_live on
39
+ html("404.html", "baseurl" => "/cv") # any output path
40
+ section("Experience") # the <section> whose heading matches, default config
41
+ ```
42
+
43
+ ## Continuous integration
44
+
45
+ This repository runs the validator and the template key checker in two workflows:
46
+
47
+ - [`.github/workflows/lint.yml`](../../.github/workflows/lint.yml): `./bin/validate-resume demo/_data --fail-on-warnings`, `rake validate[demo/_data]`, `./bin/check-data-keys demo/_data`, `rake check_data_keys[demo/_data]`, a gemspec executable check, and RuboCop.
48
+ - [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml): gem packaging, a strict Jekyll build, built-HTML proofing, RuboCop, the resume validator and template key checker, and the unit tests across Ruby 3.3, 3.4, and 4.0.
49
+
50
+ ## Built-HTML proofing semantics
51
+
52
+ - External URLs are not fetched, so results are offline and deterministic.
53
+ - Absolute URLs built from `site.url` (hreflang, canonical) are mapped onto local files so they are checked too.
54
+ - When `_site/index.html` does not exist, `/` and every configured `languages.<lang>.url` are exempted as link targets. A site with a homepage gets no exemption. Use the demo build to check the complete generated site.
55
+
56
+ ## JSON Resume export coverage
57
+
58
+ Run the normal demo build and `bundle exec rake`. Export coverage includes visibility, live contacts, privacy, locale characters, schema validation, collisions, literal Liquid content, and discovery-link cleanup on rebuild. Generated JSON can be downloaded directly or imported into JSON Resume tooling; compatibility with every third-party renderer is not guaranteed by schema validity.
59
+
60
+ ## Liquid pitfalls the tests guard
61
+
62
+ See [Liquid pitfalls](../explanation/architecture.md#liquid-pitfalls).
@@ -0,0 +1,203 @@
1
+ # Validator and build checks
2
+
3
+ *Audience: site owners and theme developers*
4
+
5
+ The repository has two verification engines; only the resume-data validator ships in the gem. `lib/jekyll-theme-resume/resume_validator.rb` checks resume YAML data — reached three ways: the `validate-resume` CLI, the `rake validate` task, and a Jekyll generator that runs during every consuming site's build. `lib/jekyll-theme-resume/template_key_checker.rb` checks the theme's own `_layouts`/`_includes` Liquid templates for references to data keys that don't exist — reached via the `check-data-keys` CLI and the `rake check_data_keys` task ([section 11](#11-template-key-checker-check-data-keys)); unlike the resume validator, it runs only in this repository's own development workflow and CI, never on a consuming site's build, and it is not packaged in the gem. This page describes what each checks and how their entry points behave.
6
+
7
+ ---
8
+
9
+ ## 1. What Gets Checked
10
+
11
+ Findings are **errors** or **warnings**. Errors make the CLI exit 1; warnings do too only with `--fail-on-warnings`.
12
+
13
+ | Check | Severity |
14
+ |---|---|
15
+ | YAML syntax in every data, locale, and config file | Error |
16
+ | A configured language has no locale file (neither theme nor site) | Error |
17
+ | A configured language has no data folder | Error |
18
+ | A `languages.<lang>` entry has no `data_path` | Error |
19
+ | An explicit `--config` file does not exist | Error |
20
+ | Required fields per section, date formats, inverted date ranges, URLs that do not parse or do not start with `http://` or `https://` | Error (see [catalog](#6-validation-rules-catalog-by-section)) |
21
+ | A section file exists in one language folder and not another (file parity) | Warning |
22
+ | A language's effective locale is missing keys the reference locale has (locale parity) | Warning |
23
+ | Missing or non-boolean `active` flag, brief or missing header intro, skill `level` outside 1 to 5 | Warning |
24
+
25
+ Entries with `active: false` skip schema checks after the `active` flag check.
26
+
27
+ ---
28
+
29
+ ## 2. How Languages and Locales Are Resolved
30
+
31
+ **Config.** The validator reads the site's Jekyll config to learn which languages exist. It uses the first file found: the `--config` path, then `<data_dir>/_config.yml`, `<data_dir>/_config.sample.yml`, `<data_dir>/../_config.yml`, `<data_dir>/../_config.sample.yml`. The Jekyll plugin passes the already-loaded site config instead.
32
+
33
+ **Languages.** The set of languages to validate is, in order of precedence:
34
+
35
+ 1. `--languages` on the CLI, if given.
36
+ 2. Every key under `languages:` in the config. Each language's folder is `<data_dir>/<data_path>`, with dot paths split into folders (`2025-06.v1` resolves to `<data_dir>/2025-06/v1`).
37
+ 3. With no `languages:` in the config (or no config), a directory scan of `<data_dir>` for folders named like language codes (`en`, `pt-BR`, `zh_CN`) that contain YAML files. Folders named `locales`, `sample`, `archive`, `assets`, and similar are skipped.
38
+
39
+ `--all-locales` adds the directory scan on top of 1 or 2.
40
+
41
+ **Locales.** Each language's effective locale is the theme gem's `_data/locales/<lang>.yml` with the site's `<data_dir>/locales/<lang>.yml` deep-merged over it: nested hashes merge key by key, arrays (`months`, `present_values`) are replaced whole. This mirrors how Jekyll layers theme and site data (see [`multilingual-and-rtl-design.md`](../explanation/multilingual-and-rtl-design.md#overriding-theme-locales)). Locale parity is checked on the merged result, so a one-line site override warns on nothing, while a site-only locale for a language the theme does not ship must be complete.
42
+
43
+ **Reference language.** Locale parity compares every language against one reference: `--primary` (default `en`) if it has a locale, else `default_lang`, else the first language that has one. File parity uses `--primary` if it has a data folder, else the first language that does.
44
+
45
+ ---
46
+
47
+ ## 3. CLI Validator (`validate-resume`)
48
+
49
+ The gem installs `validate-resume` as an executable. In a consuming site run it through Bundler; in this repository run `./bin/validate-resume`.
50
+
51
+ ```bash
52
+ bundle exec validate-resume # auto-detects the data directory
53
+ bundle exec validate-resume _data # explicit data directory
54
+ bundle exec validate-resume _data -c _config.yml # explicit config
55
+ ./bin/validate-resume demo/_data # this repository's six-language demo
56
+ ```
57
+
58
+ With no `DATA_DIR`, the CLI uses the first of `_data` and `demo/_data` that contains at least one language folder, else `_data`. A positional `DATA_DIR` wins over `-d`.
59
+
60
+ | Flag | Long flag | Description | Default |
61
+ |---|---|---|---|
62
+ | `-d DIR` | `--dir DIR` | Data directory | `_data` or `demo/_data` |
63
+ | `-c FILE` | `--config FILE` | Jekyll config that declares `languages:` | First config found next to the data directory ([section 2](#2-how-languages-and-locales-are-resolved)) |
64
+ | `-l LANGS` | `--languages LANGS` | Comma-separated languages to validate; overrides the config | The config's `languages:` |
65
+ | `-a` | `--all-locales` | Also validate every language folder found by directory scan | off |
66
+ | `-p LOCALE` | `--primary LOCALE` | Reference language for parity checks | `en` |
67
+ | `-w` | `--fail-on-warnings` | Exit 1 on warnings as well as errors | off |
68
+ | `-v` | `--verbose` | Accepted for compatibility; currently has no effect | off |
69
+ | `-q` | `--quiet` | Print only when there are findings | off |
70
+ | `-h` | `--help` | Show usage | |
71
+
72
+ Exit codes (from [`bin/validate-resume`](../../bin/validate-resume), which exits with the value `validate` returns; see [section 12](#12-ruby-api-resumevalidator)):
73
+
74
+ | Code | When |
75
+ |---|---|
76
+ | `0` | No errors, and no warnings under `-w`; also after `-h` prints usage |
77
+ | `1` | Any error (including a data directory that does not exist), or any warning under `-w` |
78
+
79
+ An unrecognized option raises an uncaught `OptionParser` exception, so Ruby exits with status `1` before validation starts.
80
+
81
+ ---
82
+
83
+ ## 4. Rake Task (`rake validate`)
84
+
85
+ ```bash
86
+ bundle exec rake validate # _data if _data/en exists, else demo/_data
87
+ bundle exec rake "validate[path/to/_data]"
88
+ ```
89
+
90
+ The task takes no config argument; it finds the config next to the data directory as described in [section 2](#2-how-languages-and-locales-are-resolved). For what the default `bundle exec rake` task runs, see [testing-suites.md](testing-suites.md#test-tasks).
91
+
92
+ ---
93
+
94
+ ## 5. Jekyll Build-Time Validation
95
+
96
+ When the gem is loaded as a plugin (through `group :jekyll_plugins` in the site's `Gemfile` or the theme name in `_config.yml`’s `plugins:` list), `_plugins/resume_validator.rb` validates the site's `data_dir` on every `jekyll build` and `jekyll serve`. It is **on by default**; findings are logged and the build continues.
97
+
98
+ To opt out or gate the build on findings, see [Validate resume data in CI](../how-to/validate-in-ci.md#configure-build-time-validation).
99
+
100
+ | Key | Default | Effect |
101
+ |---|---|---|
102
+ | `validate_resume` | on (unset) | Only the literal value `false` disables validation. |
103
+ | `validate_resume_strict` | `false` | `true` raises `Jekyll::Errors::FatalException` when there are errors. |
104
+ | `validate_resume_fail_on_warnings` | `false` | With `validate_resume_strict: true`, warnings also abort the build. Has no effect without strict mode. |
105
+
106
+ If the configured `data_dir` does not exist, the plugin logs a warning and skips validation.
107
+
108
+ ---
109
+
110
+ ## 6. Validation Rules Catalog by Section
111
+
112
+ The rules are identical for every language. Required fields must use the canonical key the templates render. Names in parentheses are aliases the templates never read: an entry that sets only an alias still fails, and the error names the alias it found (for example `Missing required field 'company': found 'organization', but the theme only renders 'company'`). Field names are listed in [data-schemas.md](data-schemas.md).
113
+
114
+ | Section file | Required fields | Checked when present |
115
+ |---|---|---|
116
+ | `header.yml` | Must be a Hash | `intro` (or `about`): warning if missing or under 20 characters |
117
+ | `experience.yml` | `company` (`organization`), `position` (`role`) | `startdate`, `enddate` (date or "present"), date order, `url`, empty `durations[].duration` warns; warning if none of `startdate`, `enddate`, or `durations` is set |
118
+ | `education.yml` | `uni` (`institution`, `school`), `degree`, and nonblank `year` unless `startdate` is supplied | `startdate`, `enddate` (date or "present"), date order, `url` |
119
+ | `certifications.yml` | `name` (`title`) | `issue_date`, `expiration`, `expiration >= issue_date`, `credential_url` |
120
+ | `courses.yml` | `name` (`title`, `course`) | `startdate`, `enddate`, date order, `credential_url` |
121
+ | `volunteering.yml` | `company` (`organization`), `position` (`role`) | `startdate`, `enddate` (date or "present"), date order, `url` |
122
+ | `projects.yml` | `project` (`title`, `name`) | `url`, `startdate`, `enddate` (date or "present"), date order |
123
+ | `skills.yml` | `skill` (`category`, `name`) | `level` is an integer 1 to 5 (warning) |
124
+ | `recognitions.yml` | `award` (`title`, `recognition`) | `date` (ISO) |
125
+ | `associations.yml` | `organization` (`company`, `name`) | `url` |
126
+ | `languages.yml` | `language` (`name`) | |
127
+ | `links.yml` | `description` (`name`, `title`), `url` | `url` |
128
+ | `publications.yml` | `name` (`title`) | `release_date` (ISO), `url` (http/https) |
129
+ | `references.yml` | `name`, `reference` (`quote`, `text`) | none |
130
+ | `interests.yml` | none; missing `description` is a warning (naming `interest`/`name` when one is set) | No `active` flag check |
131
+
132
+ Every file except `header.yml` must be a list of Hashes. Every list entry except in `interests.yml` should carry `active: true` or `active: false`.
133
+
134
+ Dates must be ISO: `YYYY-MM-DD`, `YYYY-MM`, or `YYYY`. Ranges compare at the precision given, with partial end dates extended to the end of their period, so `2024-05-15` to `2024-05` is valid and `2025-01` to `2024-05` is an error.
135
+
136
+ URLs must parse and must start with `http://` or `https://`; either failure is an error.
137
+
138
+ ---
139
+
140
+ ## 7. "Present" Dates
141
+
142
+ The words the validator accepts for an ongoing `enddate` are listed in [Present values](locale-keys.md#present-values).
143
+
144
+ ---
145
+
146
+ ## 8. Troubleshooting
147
+
148
+ Findings and their fixes are in [Troubleshoot builds](../how-to/troubleshoot-builds.md).
149
+
150
+ ---
151
+
152
+ ## 9. Continuous Integration
153
+
154
+ This repository's workflows are listed in [testing-suites.md](testing-suites.md#continuous-integration). A consuming-site workflow is in [Validate resume data in CI](../how-to/validate-in-ci.md#run-the-validator-in-github-actions).
155
+
156
+ ---
157
+
158
+ ## 10. Built-HTML Proofing & Static Analysis
159
+
160
+ Commands are in [Validate resume data in CI](../how-to/proof-built-html.md); proofing semantics are in [testing-suites.md](testing-suites.md#built-html-proofing-semantics).
161
+
162
+ ---
163
+
164
+ ## 11. Template Key Checker (`check-data-keys`)
165
+
166
+ Why the checker exists and why it never defaults to `--fail-on-warnings`: [Resume Data Validator](../explanation/architecture.md#two-verification-engines).
167
+
168
+ ```bash
169
+ ./bin/check-data-keys demo/_data # this repository's six-language demo (the default target)
170
+ ./bin/check-data-keys demo/_data --fail-on-warnings # opt into strict mode yourself, once you trust your data's coverage
171
+ bundle exec rake check_data_keys # _data if _data/en exists, else demo/_data; never fails the task
172
+ bundle exec rake "check_data_keys[path/to/_data]"
173
+ ```
174
+
175
+ | Flag | Long flag | Description | Default |
176
+ |---|---|---|---|
177
+ | `-d DIR` | `--dir DIR` | Data directory whose sample data defines the known keys | `_data` or `demo/_data` |
178
+ | `-c FILE` | `--config FILE` | Jekyll config that declares `languages:` | `_config.yml` / `_config.sample.yml` next to the data directory |
179
+ | `-w` | `--fail-on-warnings` | Exit 1 on warnings | off |
180
+ | `-v` | `--verbose` | Accepted for compatibility; currently has no effect | off |
181
+ | `-q` | `--quiet` | Print only when there are findings | off |
182
+ | `-h` | `--help` | Show usage | |
183
+
184
+ With no `DATA_DIR`, `check-data-keys` uses the first of `_data` and `demo/_data` that contains at least one language folder, else `demo/_data`. A positional `DATA_DIR` wins over `-d`.
185
+
186
+ Scans only `_layouts/*.html` and `_includes/**/*.html` — this repository's own shipped templates, never a consuming site's `_pages/` or `_layouts/`/`_includes` overrides. Runs in `bundle exec rake` (the default task) and in both CI workflows ([testing-suites.md](testing-suites.md#continuous-integration)), always without `--fail-on-warnings`.
187
+
188
+ ---
189
+
190
+ ## 12. Ruby API (`ResumeValidator`)
191
+
192
+ `JekyllThemeResume::ResumeValidator` in [`lib/jekyll-theme-resume/resume_validator.rb`](../../lib/jekyll-theme-resume/resume_validator.rb) backs the CLI, the Rake task, and the Jekyll generator. Public interface:
193
+
194
+ | Signature | Behavior |
195
+ |---|---|
196
+ | `new(data_dir = "_data", config_path: nil, config: nil, primary_locale: "en")` | Stores the data directory (trailing `/` removed), an explicit config path, an already-parsed site config Hash (the Jekyll plugin passes `site.config`; when `nil` the config is looked up on disk, `config_path` first), and the reference language (`nil` becomes `"en"`). |
197
+ | `validate(languages: nil, all_locales: false, primary_locale: nil, verbose: false, quiet: false, fail_on_warnings: false)` | Clears previous findings, runs every check on the resolved languages, prints the report (skipped under `quiet:` when there are no findings), and returns `1` when there are errors or, with `fail_on_warnings: true`, warnings; otherwise `0`. A missing data directory is an error and returns `1` at once. A non-nil `primary_locale:` replaces the one given to `new`. |
198
+ | `discover_languages` | Returns the subfolders of the data directory that are named like language codes, are not in the skip list, and contain `.yml`/`.yaml` files, with `primary_locale` first (when it is among them) and the rest sorted; `[]` if the data directory does not exist. |
199
+ | `locale_for(lang)` | Returns the effective locale Hash for `lang` (stripped and downcased): the theme's `_data/locales/<lang>.yml` with the site's `<data_dir>/locales/<lang>.yml` deep-merged over it, a site-only locale as-is, or `nil` when neither exists; cached per `validate` run. |
200
+ | `present_aliases_for(lang = nil)` | Returns the unique accepted "present" words for `lang`: the effective locale's `present_values` plus its `ui.present`, falling back to the `default_lang` (else reference-language) locale when `lang` has none; a `nil` or blank `lang` returns the words of every known locale. |
201
+ | `present_date?(date_val, lang: nil)` | Returns `false` for `nil`, `Date`, or `Time` values, `true` for a blank string, and otherwise whether the value matches one of `present_aliases_for(lang)` case-insensitively. |
202
+
203
+ Read-only attributes: `data_dir`, `errors`, `warnings`, `primary_locale`, and `info` (currently always empty). Each finding is a Hash `{ context:, message: }`.