datalog-theme 0.9.2 → 0.10.1

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.
@@ -9,6 +9,9 @@ Parameters:
9
9
  - conda_command: Custom conda install command
10
10
  - cran_command: Custom R install command
11
11
  - git_url: GitHub clone URL
12
+
13
+ The tabs and the copy buttons are wired by the core bundle
14
+ (assets/js/core/install-tabs.js, assets/js/core/copy-buttons.js).
12
15
  {%- endcomment -%}
13
16
 
14
17
  {%- assign language = include.language | default: "python" -%}
@@ -17,178 +20,88 @@ Parameters:
17
20
  {%- assign conda_command = include.conda_command -%}
18
21
  {%- assign cran_command = include.cran_command -%}
19
22
  {%- assign git_url = include.git_url | default: page.github_url -%}
23
+ {%- comment -%}
24
+ The id is the package's, not "installation": a page whose Markdown has an
25
+ `## Installation` heading gets that id from kramdown, and two elements
26
+ shared it.
27
+ {%- endcomment -%}
28
+ {%- assign install_id = package_name | slugify | prepend: 'install-' -%}
29
+ {%- capture copied_label %}{% t 'package_install.copied' %}{% endcapture -%}
30
+ {%- capture copy_error_label %}{% t 'package_install.copy_error' %}{% endcapture -%}
20
31
 
21
- <div class="package-install" id="installation">
22
- <h2 class="package-install__title">Installation</h2>
32
+ <div class="package-install" id="{{ install_id }}">
33
+ <h2 class="package-install__title" id="{{ install_id }}-title">{% t 'package_install.title' %}</h2>
23
34
 
24
35
  {%- if language == "python" -%}
25
- <div class="package-install__tabs" data-install-tabs>
26
- <div class="package-install__tab-buttons">
27
- <button class="package-install__tab-btn package-install__tab-btn--active"
28
- data-tab="pip">
29
- pip
30
- </button>
31
- <button class="package-install__tab-btn" data-tab="conda">
32
- conda
33
- </button>
34
- <button class="package-install__tab-btn" data-tab="git">
35
- Git
36
- </button>
37
- </div>
38
-
39
- <div class="package-install__tab-content" data-tab-content="pip">
40
- <div class="package-install__code-block">
41
- <div class="package-install__code-header">
42
- <span class="package-install__code-label">Terminal</span>
43
- <button class="package-install__copy-btn" data-copy-btn>
44
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
45
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
46
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
47
- </svg>
48
- Copy
49
- </button>
50
- </div>
51
- <pre><code data-copy-text>{{ pip_command | default: "pip install " | append: package_name }}</code></pre>
52
- </div>
53
- </div>
54
-
55
- <div class="package-install__tab-content" data-tab-content="conda" hidden>
56
- <div class="package-install__code-block">
57
- <div class="package-install__code-header">
58
- <span class="package-install__code-label">Terminal</span>
59
- <button class="package-install__copy-btn" data-copy-btn>
60
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
61
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
62
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
63
- </svg>
64
- Copy
65
- </button>
66
- </div>
67
- <pre><code data-copy-text>{{ conda_command | default: "conda install " | append: package_name }}</code></pre>
68
- </div>
69
- </div>
70
-
71
- <div class="package-install__tab-content" data-tab-content="git" hidden>
72
- <div class="package-install__code-block">
73
- <div class="package-install__code-header">
74
- <span class="package-install__code-label">Terminal</span>
75
- <button class="package-install__copy-btn" data-copy-btn>
76
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
77
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
78
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
79
- </svg>
80
- Copy
81
- </button>
82
- </div>
83
- <pre><code data-copy-text>git clone {{ git_url }}
84
- cd {{ package_name }}
85
- pip install -e .</code></pre>
86
- </div>
87
- </div>
88
- </div>
89
-
36
+ {%- assign tabs = 'pip,conda,git' | split: ',' -%}
37
+ {%- assign tab_labels = 'pip,conda,Git' | split: ',' -%}
90
38
  {%- elsif language == "r" -%}
91
- <div class="package-install__tabs" data-install-tabs>
92
- <div class="package-install__tab-buttons">
93
- <button class="package-install__tab-btn package-install__tab-btn--active" data-tab="cran">
94
- CRAN
95
- </button>
96
- <button class="package-install__tab-btn" data-tab="github">
97
- GitHub
98
- </button>
99
- </div>
39
+ {%- assign tabs = 'cran,github' | split: ',' -%}
40
+ {%- assign tab_labels = 'CRAN,GitHub' | split: ',' -%}
41
+ {%- endif -%}
100
42
 
101
- <div class="package-install__tab-content" data-tab-content="cran">
102
- <div class="package-install__code-block">
103
- <div class="package-install__code-header">
104
- <span class="package-install__code-label">R Console</span>
105
- <button class="package-install__copy-btn" data-copy-btn>
106
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
107
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
108
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
109
- </svg>
110
- Copy
111
- </button>
112
- </div>
113
- <pre><code data-copy-text>{{ cran_command | default: "install.packages('" | append: package_name | append: "')" }}</code></pre>
114
- </div>
43
+ {%- if tabs -%}
44
+ <div class="package-install__tabs" data-install-tabs>
45
+ <div class="package-install__tab-buttons" role="tablist" aria-labelledby="{{ install_id }}-title">
46
+ {%- for tab in tabs -%}
47
+ <button type="button"
48
+ class="package-install__tab-btn{% if forloop.first %} package-install__tab-btn--active{% endif %}"
49
+ role="tab"
50
+ id="{{ install_id }}-tab-{{ tab }}"
51
+ aria-controls="{{ install_id }}-panel-{{ tab }}"
52
+ aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"
53
+ tabindex="{% if forloop.first %}0{% else %}-1{% endif %}"
54
+ data-tab="{{ tab }}">
55
+ {{ tab_labels[forloop.index0] }}
56
+ </button>
57
+ {%- endfor -%}
115
58
  </div>
116
59
 
117
- <div class="package-install__tab-content" data-tab-content="github" hidden>
118
- <div class="package-install__code-block">
119
- <div class="package-install__code-header">
120
- <span class="package-install__code-label">R Console</span>
121
- <button class="package-install__copy-btn" data-copy-btn>
122
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
123
- <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
124
- <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
125
- </svg>
126
- Copy
127
- </button>
60
+ {%- for tab in tabs -%}
61
+ {%- assign command = '' -%}
62
+ {%- assign console_key = 'package_install.terminal' -%}
63
+ {%- case tab -%}
64
+ {%- when 'pip' -%}
65
+ {%- capture command %}{{ pip_command | default: "pip install " | append: package_name }}{% endcapture -%}
66
+ {%- when 'conda' -%}
67
+ {%- capture command %}{{ conda_command | default: "conda install " | append: package_name }}{% endcapture -%}
68
+ {%- when 'git' -%}
69
+ {%- capture command -%}git clone {{ git_url }}
70
+ cd {{ package_name }}
71
+ pip install -e .{%- endcapture -%}
72
+ {%- when 'cran' -%}
73
+ {%- assign console_key = 'package_install.r_console' -%}
74
+ {%- capture command %}{{ cran_command | default: "install.packages('" | append: package_name | append: "')" }}{% endcapture -%}
75
+ {%- when 'github' -%}
76
+ {%- assign console_key = 'package_install.r_console' -%}
77
+ {%- capture command %}devtools::install_github("{{ git_url | remove: "https://github.com/" }}"){% endcapture -%}
78
+ {%- endcase -%}
79
+ <div class="package-install__tab-content"
80
+ role="tabpanel"
81
+ id="{{ install_id }}-panel-{{ tab }}"
82
+ aria-labelledby="{{ install_id }}-tab-{{ tab }}"
83
+ tabindex="0"
84
+ data-tab-content="{{ tab }}"{% unless forloop.first %} hidden{% endunless %}>
85
+ <div class="package-install__code-block">
86
+ <div class="package-install__code-header">
87
+ <span class="package-install__code-label">{{ console_key | t }}</span>
88
+ <button type="button"
89
+ class="package-install__copy-btn"
90
+ data-copy-target
91
+ data-target="{{ install_id }}-command-{{ tab }}"
92
+ data-copied-label="{{ copied_label | strip | escape }}"
93
+ data-error-label="{{ copy_error_label | strip | escape }}">
94
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
95
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
96
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
97
+ </svg>
98
+ {% t 'package_install.copy' %}
99
+ </button>
100
+ </div>
101
+ <pre><code id="{{ install_id }}-command-{{ tab }}">{{ command | strip }}</code></pre>
128
102
  </div>
129
- <pre><code data-copy-text>devtools::install_github("{{ git_url | remove: "https://github.com/" }}")</code></pre>
130
103
  </div>
131
- </div>
104
+ {%- endfor -%}
132
105
  </div>
133
106
  {%- endif -%}
134
107
  </div>
135
-
136
- <script>
137
- (function() {
138
- 'use strict';
139
-
140
- const initInstallTabs = () => {
141
- const containers = document.querySelectorAll('[data-install-tabs]');
142
-
143
- containers.forEach(container => {
144
- const tabBtns = container.querySelectorAll('[data-tab]');
145
- const tabContents = container.querySelectorAll('[data-tab-content]');
146
- const copyBtns = container.querySelectorAll('[data-copy-btn]');
147
-
148
- // Tab switching
149
- tabBtns.forEach(btn => {
150
- btn.addEventListener('click', () => {
151
- const tabName = btn.dataset.tab;
152
-
153
- // Update buttons
154
- tabBtns.forEach(b => b.classList.remove('package-install__tab-btn--active'));
155
- btn.classList.add('package-install__tab-btn--active');
156
-
157
- // Update content
158
- tabContents.forEach(content => {
159
- content.hidden = content.dataset.tabContent !== tabName;
160
- });
161
- });
162
- });
163
-
164
- // Copy functionality
165
- copyBtns.forEach(btn => {
166
- btn.addEventListener('click', async () => {
167
- const codeEl = btn.closest('.package-install__code-block').querySelector('[data-copy-text]');
168
- const text = codeEl.textContent.trim();
169
-
170
- try {
171
- await navigator.clipboard.writeText(text);
172
- const originalHTML = btn.innerHTML;
173
- btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg> Copied!';
174
- btn.classList.add('package-install__copy-btn--success');
175
-
176
- setTimeout(() => {
177
- btn.innerHTML = originalHTML;
178
- btn.classList.remove('package-install__copy-btn--success');
179
- }, 2000);
180
- } catch (err) {
181
- console.error('Failed to copy:', err);
182
- }
183
- });
184
- });
185
- });
186
- };
187
-
188
- if (document.readyState === 'loading') {
189
- document.addEventListener('DOMContentLoaded', initInstallTabs);
190
- } else {
191
- initInstallTabs();
192
- }
193
- })();
194
- </script>
@@ -12,15 +12,15 @@ Parameters:
12
12
  {%- assign share_description = include.description | default: page.excerpt | strip_html | truncate: 200 -%}
13
13
  {%- assign show_count = include.show_count | default: false -%}
14
14
 
15
- <div class="social-share" aria-label="Share this article">
16
- <h3 class="social-share__heading">Share this article</h3>
15
+ <div class="social-share" aria-label="{% t 'post.share.label' %}">
16
+ <h3 class="social-share__heading">{% t 'post.share.label' %}</h3>
17
17
  <div class="social-share__buttons">
18
18
  {%- comment -%} Twitter/X Share {%- endcomment -%}
19
19
  <a href="https://twitter.com/intent/tweet?url={{ share_url | uri_escape }}&text={{ share_title | uri_escape }}"
20
20
  class="social-share__button social-share__button--twitter"
21
21
  target="_blank"
22
22
  rel="noopener noreferrer"
23
- aria-label="Share on Twitter"
23
+ aria-label="{% t 'post.share.twitter' %}"
24
24
  data-share-platform="twitter">
25
25
  <svg class="social-share__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
26
26
  <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
@@ -33,7 +33,7 @@ Parameters:
33
33
  class="social-share__button social-share__button--linkedin"
34
34
  target="_blank"
35
35
  rel="noopener noreferrer"
36
- aria-label="Share on LinkedIn"
36
+ aria-label="{% t 'post.share.linkedin' %}"
37
37
  data-share-platform="linkedin">
38
38
  <svg class="social-share__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
39
39
  <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
@@ -46,7 +46,7 @@ Parameters:
46
46
  class="social-share__button social-share__button--facebook"
47
47
  target="_blank"
48
48
  rel="noopener noreferrer"
49
- aria-label="Share on Facebook"
49
+ aria-label="{% t 'post.share.facebook' %}"
50
50
  data-share-platform="facebook">
51
51
  <svg class="social-share__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
52
52
  <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
@@ -59,7 +59,7 @@ Parameters:
59
59
  class="social-share__button social-share__button--reddit"
60
60
  target="_blank"
61
61
  rel="noopener noreferrer"
62
- aria-label="Share on Reddit"
62
+ aria-label="{% t 'post.share.reddit' %}"
63
63
  data-share-platform="reddit">
64
64
  <svg class="social-share__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
65
65
  <path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/>
@@ -70,26 +70,26 @@ Parameters:
70
70
  {%- comment -%} Email Share {%- endcomment -%}
71
71
  <a href="mailto:?subject={{ share_title | uri_escape }}&body={{ share_description | uri_escape }}%0A%0A{{ share_url | uri_escape }}"
72
72
  class="social-share__button social-share__button--email"
73
- aria-label="Share via email"
73
+ aria-label="{% t 'post.share.email' %}"
74
74
  data-share-platform="email">
75
75
  <svg class="social-share__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
76
76
  <path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
77
77
  <polyline points="22,6 12,13 2,6"></polyline>
78
78
  </svg>
79
- <span class="social-share__label">Email</span>
79
+ <span class="social-share__label">{% t 'post.share.email_label' %}</span>
80
80
  </a>
81
81
 
82
82
  {%- comment -%} Copy Link {%- endcomment -%}
83
83
  <button type="button"
84
84
  class="social-share__button social-share__button--copy"
85
- aria-label="Copy link to clipboard"
85
+ aria-label="{% t 'post.share.copy_link' %}"
86
86
  data-share-platform="copy"
87
87
  data-copy-url="{{ share_url }}">
88
88
  <svg class="social-share__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
89
89
  <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
90
90
  <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
91
91
  </svg>
92
- <span class="social-share__label">Copy link</span>
92
+ <span class="social-share__label">{% t 'post.share.copy_link_label' %}</span>
93
93
  </button>
94
94
  </div>
95
95
  </div>
@@ -16,8 +16,11 @@ Renders a footer navigation column with optional item descriptions.
16
16
  <h4>{{ include.title | strip | escape }}</h4>
17
17
  <ul>
18
18
  {%- for item in column_items -%}
19
- {%- capture item_label -%}{% if item.label %}{{ item.label }}{% elsif item.label_key %}{% t item.label_key %}{% endif %}{%- endcapture -%}
20
- {%- capture item_description -%}{% if item.description %}{{ item.description }}{% elsif item.description_key %}{% t item.description_key %}{% endif %}{%- endcapture -%}
19
+ {%- comment -%} The key first, so a site in another language gets its own words; the literal when there is no key, or the key names no translation. {%- endcomment -%}
20
+ {%- capture item_label -%}{% if item.label_key %}{% t item.label_key %}{% endif %}{%- endcapture -%}
21
+ {%- if item_label == '' or item_label == item.label_key -%}{%- assign item_label = item.label | default: '' -%}{%- endif -%}
22
+ {%- capture item_description -%}{% if item.description_key %}{% t item.description_key %}{% endif %}{%- endcapture -%}
23
+ {%- if item_description == '' or item_description == item.description_key -%}{%- assign item_description = item.description | default: '' -%}{%- endif -%}
21
24
  <li>
22
25
  <a href="{{ item.url | relative_url }}">
23
26
  <span>{{ item_label | strip }}</span>
@@ -25,7 +25,7 @@
25
25
  {% assign footer_links = navigation_data.footer %}
26
26
  {% if footer_links.size > 0 %}
27
27
  <nav class="footer-navigation" aria-labelledby="footer-explore-heading">
28
- <h2 id="footer-explore-heading">Explore</h2>
28
+ <h2 id="footer-explore-heading">{% t 'footer.explore' %}</h2>
29
29
  <ul class="footer-link-list">
30
30
  {% for item in footer_links %}
31
31
  <li><a href="{{ item.url | relative_url }}">{{ item.title | escape }}</a></li>
@@ -37,7 +37,7 @@
37
37
  {% assign primary_social = social_data.primary %}
38
38
  {% if primary_social.size > 0 or site.author.email %}
39
39
  <section class="footer-connect">
40
- <h2>Connect</h2>
40
+ <h2>{% t 'footer.connect' %}</h2>
41
41
  {% if primary_social.size > 0 %}
42
42
  <ul class="footer-link-list">
43
43
  {% for profile in primary_social %}
@@ -54,8 +54,8 @@
54
54
  {% endif %}
55
55
 
56
56
  <section class="footer-subscribe">
57
- <h2>Stay updated</h2>
58
- <p>Follow via RSS or check back for new research and tutorials.</p>
57
+ <h2>{% t 'footer.stay_updated' %}</h2>
58
+ <p>{% t 'footer.rss_description' %}</p>
59
59
  {%- comment -%} jekyll-feed is a dependency of the theme, so /feed.xml exists even when _data/social.yml lists no feeds. {%- endcomment -%}
60
60
  {% assign rss_links = social_data.rss %}
61
61
  <ul class="footer-link-list">
@@ -64,7 +64,7 @@
64
64
  <li><a href="{{ feed.url | relative_url }}">{{ feed.label }}</a></li>
65
65
  {% endfor %}
66
66
  {% else %}
67
- <li><a href="{{ '/feed.xml' | relative_url }}">RSS feed</a></li>
67
+ <li><a href="{{ '/feed.xml' | relative_url }}">{% t 'footer.rss_feed' %}</a></li>
68
68
  {% endif %}
69
69
  </ul>
70
70
  {%- comment -%} The newsletter form, on a site with a backend that takes subscriptions {%- endcomment -%}
@@ -26,10 +26,19 @@ page language gets its own copy of any translated descriptions.
26
26
  {%- assign is_active = true -%}
27
27
  {%- endif -%}
28
28
  {%- assign nav_title = item.title | default: 'Untitled' | strip -%}
29
+ {%- if item.title_key -%}
30
+ {%- capture translated_title -%}{% t item.title_key %}{%- endcapture -%}
31
+ {%- unless translated_title == item.title_key -%}
32
+ {%- assign nav_title = translated_title | strip -%}
33
+ {%- endunless -%}
34
+ {%- endif -%}
29
35
  {%- assign nav_section = item.section | default: nav_title | replace: '_', ' ' | replace: '-', ' ' | strip -%}
30
36
  {%- assign nav_description = item.description | default: '' -%}
31
- {%- if nav_description == '' and item.description_key -%}
32
- {%- capture nav_description -%}{% t item.description_key %}{%- endcapture -%}
37
+ {%- if item.description_key -%}
38
+ {%- capture translated_description -%}{% t item.description_key %}{%- endcapture -%}
39
+ {%- unless translated_description == item.description_key -%}
40
+ {%- assign nav_description = translated_description | strip -%}
41
+ {%- endunless -%}
33
42
  {%- endif -%}
34
43
  {%- if nav_description == '' -%}
35
44
  {%- assign nav_description = nav_section | default: nav_title -%}
@@ -30,10 +30,10 @@ and is cached for each distinct set.
30
30
  <div class="header-primary">
31
31
  {% assign brand_symbol = site.brand.accent_symbol | default: '∀ data · Σ insights' | strip %}
32
32
  {% assign brand_home_name = site.title | default: site.name %}
33
- <a class="site-brand" href="{{ '/' | relative_url }}" aria-label="{{ brand_home_name }} home">
33
+ <a class="site-brand" href="{{ '/' | relative_url }}" aria-label="{% t 'header.brand_home' site: brand_home_name %}">
34
34
  <span class="site-logo" aria-hidden="true">
35
35
  <svg class="logo-mark" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" role="img" focusable="false">
36
- <title>DataLog logo</title>
36
+ <title>{% t 'header.logo_title' %}</title>
37
37
  <path d="M8 48c12-24 24-32 32-32 8 0 12 4 16 8" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" />
38
38
  <path d="M44 16c0 4-4 8-8 8s-8-4-8-8 4-8 8-8 8 4 8 8z" fill="currentColor" opacity="0.25" />
39
39
  </svg>
@@ -46,14 +46,14 @@ and is cached for each distinct set.
46
46
  <div class="header-secondary">
47
47
  {% if site.features.search %}
48
48
  <form class="site-search" role="search" action="{{ '/search/' | relative_url }}" method="get">
49
- <label class="visually-hidden" for="site-search-input">Search</label>
50
- <input id="site-search-input" name="q" type="search" placeholder="Search..." autocomplete="off">
51
- <button type="submit">Search</button>
49
+ <label class="visually-hidden" for="site-search-input">{% t 'header.search_label' %}</label>
50
+ <input id="site-search-input" name="q" type="search" placeholder="{% t 'header.search_placeholder' %}" autocomplete="off">
51
+ <button type="submit">{% t 'header.search_button' %}</button>
52
52
  </form>
53
53
  {% endif %}
54
54
  <div class="header-controls">
55
55
  {% if site.features.dark_mode_toggle %}
56
- <button class="header-control header-control--theme" type="button" data-toggle-dark-mode aria-pressed="false" aria-label="Toggle dark mode">
56
+ <button class="header-control header-control--theme" type="button" data-toggle-dark-mode aria-pressed="false" aria-label="{% t 'header.dark_mode_toggle' %}">
57
57
  <svg class="theme-icon theme-icon--light" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
58
58
  <circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
59
59
  </svg>
@@ -63,7 +63,7 @@ and is cached for each distinct set.
63
63
  </button>
64
64
  {% endif %}
65
65
  {% if nav_items.size > 0 %}
66
- <button class="header-control header-control--icon nav-toggle" type="button" aria-expanded="false" aria-controls="site-nav" aria-label="Toggle navigation">
66
+ <button class="header-control header-control--icon nav-toggle" type="button" aria-expanded="false" aria-controls="site-nav" aria-label="{% t 'header.navigation_toggle' %}">
67
67
  <span aria-hidden="true">☰</span>
68
68
  </button>
69
69
  {% endif %}
@@ -71,7 +71,7 @@ and is cached for each distinct set.
71
71
  </div>
72
72
  </div>
73
73
  {% if nav_items.size > 0 %}
74
- <nav id="site-nav" class="site-nav" role="navigation" aria-label="Primary navigation" data-open="false">
74
+ <nav id="site-nav" class="site-nav" role="navigation" aria-label="{% t 'navigation.primary_label' %}" data-open="false">
75
75
  {% include_cached header/navigation.html items=nav_items active=active_items lang=page.lang %}
76
76
  </nav>
77
77
  {% endif %}
@@ -103,7 +103,8 @@
103
103
  </script>
104
104
 
105
105
  {% if js_manifest %}
106
- <script id="datalog-js-manifest" type="application/json" nonce="{{ page.csp_nonce }}">{{ js_manifest | jsonify }}</script>
106
+ {%- comment -%} The loader reads this as JSON, so the baseurl goes in before it is written, not with `relative_url` at each use. {%- endcomment -%}
107
+ <script id="datalog-js-manifest" type="application/json" nonce="{{ page.csp_nonce }}">{{ js_manifest | relative_manifest | jsonify }}</script>
107
108
  {% endif %}
108
109
 
109
110
  {% assign loader_src = js_manifest.loader | default: '/assets/js/loader.js' %}
@@ -90,7 +90,7 @@ show_title: false # this layout renders the page's <h1>
90
90
  {% if page.nav_sections %}
91
91
  {% for section in page.nav_sections %}
92
92
  <div class="package-nav__section">
93
- <h3 class="package-nav__section-title">{{ section.title | escape }}</h3>
93
+ <h2 class="package-nav__section-title">{{ section.title | escape }}</h2>
94
94
  <ul class="package-nav__list">
95
95
  {% for item in section.items %}
96
96
  <li class="package-nav__item">
@@ -105,7 +105,7 @@ show_title: false # this layout renders the page's <h1>
105
105
  {% else %}
106
106
  {%- comment -%} Auto-generate from headings {%- endcomment -%}
107
107
  <div class="package-nav__section">
108
- <h3 class="package-nav__section-title">Contents</h3>
108
+ <h2 class="package-nav__section-title">Contents</h2>
109
109
  <ul class="package-nav__list">
110
110
  <li class="package-nav__item">
111
111
  <a href="#installation" class="package-nav__link">Installation</a>
@@ -129,8 +129,9 @@ show_title: false # this layout renders the page's <h1>
129
129
  {%- comment -%} Version Selector {%- endcomment -%}
130
130
  {% if page.versions %}
131
131
  <div class="package-nav__section package-nav__section--versions">
132
- <h3 class="package-nav__section-title">Versions</h3>
133
- <select class="package-nav__version-select" data-navigate-on-change>
132
+ <h2 class="package-nav__section-title" id="package-versions-heading">Versions</h2>
133
+ <select class="package-nav__version-select" data-navigate-on-change
134
+ aria-labelledby="package-versions-heading">
134
135
  {% for v in page.versions %}
135
136
  <option value="{{ v.url }}" {% if v.version == page.version %}selected{% endif %}>
136
137
  v{{ v.version }} {% if v.status %}({{ v.status }}){% endif %}
@@ -143,7 +144,7 @@ show_title: false # this layout renders the page's <h1>
143
144
  {%- comment -%} Related Packages {%- endcomment -%}
144
145
  {% if page.related_packages %}
145
146
  <div class="package-nav__section">
146
- <h3 class="package-nav__section-title">Related Packages</h3>
147
+ <h2 class="package-nav__section-title">Related Packages</h2>
147
148
  <ul class="package-nav__list">
148
149
  {% for pkg in page.related_packages %}
149
150
  <li class="package-nav__item">
@@ -157,7 +158,8 @@ show_title: false # this layout renders the page's <h1>
157
158
  </aside>
158
159
 
159
160
  {%- comment -%} Main Content {%- endcomment -%}
160
- <main class="package-docs__main">
161
+ {%- comment -%} A column, not a landmark: the default layout's <main> already wraps this. {%- endcomment -%}
162
+ <div class="package-docs__main">
161
163
  <article class="package-docs__content">
162
164
  {{ content }}
163
165
  </article>
@@ -188,7 +190,7 @@ show_title: false # this layout renders the page's <h1>
188
190
  </a>
189
191
  {% endif %}
190
192
  </nav>
191
- </main>
193
+ </div>
192
194
  </div>
193
195
  </div>
194
196
 
data/_plugins/i18n.rb CHANGED
@@ -117,6 +117,7 @@ class TranslateTag < Liquid::Tag
117
117
  # The key may be bare or quoted; a quoted key must not leak its closing
118
118
  # quote into the first option name, or interpolation silently fails.
119
119
  SYNTAX = /\A\s*(['"]?)(\w[\w.-]*)\1(.*)?\z/m
120
+ QUOTED = /\A\s*['"]/
120
121
 
121
122
  # `name: value` pairs, separated by commas or spaces. A quoted value may
122
123
  # contain commas: splitting the markup on every comma cut `name: "Doe, Jane"`
@@ -128,16 +129,29 @@ class TranslateTag < Liquid::Tag
128
129
  raise Liquid::SyntaxError, "Syntax Error in 't' - Valid syntax: t key [arg: value]" unless markup.strip =~ SYNTAX
129
130
 
130
131
  @key = Regexp.last_match(2)
132
+ @quoted = markup.match?(QUOTED)
131
133
  @markup = Regexp.last_match(3)
132
134
  end
133
135
 
134
136
  def render(context)
135
137
  options = parse_options(@markup, context)
136
- Datalog::I18n.translate(context, @key, options)
138
+ Datalog::I18n.translate(context, key_for(context), options)
137
139
  end
138
140
 
139
141
  private
140
142
 
143
+ # An unquoted key may be a key (`{% t header.brand_home %}`) or a variable
144
+ # holding one (`{% t item.title_key %}`, where _data/navigation.yml names the
145
+ # key for each entry). Both are written the same way, so the variable is
146
+ # tried first: a key that is not one resolves to nothing and is used as it
147
+ # was written. A quoted key is always a key.
148
+ def key_for(context)
149
+ return @key if @quoted
150
+
151
+ value = context[@key]
152
+ value.is_a?(String) && !value.empty? ? value : @key
153
+ end
154
+
141
155
  def parse_options(markup, context)
142
156
  return {} unless markup && !markup.strip.empty?
143
157
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ # `_data/js_manifest.json` records each bundle at a site-root path, so that
5
+ # every template can put it through `relative_url` and a site served from a
6
+ # subdirectory gets the right address. The loader is the one reader that
7
+ # cannot: `_includes/scripts.html` hands it the manifest as JSON, and no
8
+ # Liquid filter reaches inside that. Its copy is prefixed here instead.
9
+ #
10
+ # Without this a site with a `baseurl` imports /assets/js/dist/core.js, the
11
+ # import 404s, and `bootstrapFeatures` returns before it loads a single
12
+ # feature: no dark mode, no navigation, no search, no math (#328).
13
+ module JsManifestFilter
14
+ # The manifest with the site's baseurl in front of every path it holds.
15
+ # Nested, since the bundles are grouped under `features` and `entrypoints`.
16
+ def relative_manifest(manifest)
17
+ relativize_manifest_value(manifest)
18
+ end
19
+
20
+ private
21
+
22
+ def relativize_manifest_value(value)
23
+ case value
24
+ when Hash then value.transform_values { |entry| relativize_manifest_value(entry) }
25
+ when Array then value.map { |entry| relativize_manifest_value(entry) }
26
+ # `relative_url` leaves an absolute URL alone, so a manifest pointing at
27
+ # a CDN survives this untouched.
28
+ when String then relative_url(value)
29
+ else value
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ Liquid::Template.register_filter(Jekyll::JsManifestFilter)