multilang begin
[martlubbers.net.git] / readme.markdown
1 # Multilingual Experiment in Jekyll
2
3 The [basic *GitHub Pages* site](https://ranbureand.github.io/multilingual-experiment/) hosted in this repository and this README illustrate my approach to create a multilingual site in *[Jekyll](https://jekyllrb.com/ "Jekyll")*.
4
5 ## Table of Contents
6
7 + [Preface](#preface)
8 + [Foreword](#foreword)
9 + [Directory Structure](#directory-structure)
10 + [Pages](#pages)
11 + [Exceptions](#exceptions)
12 + [Posts](#posts)
13 + [Configuration](#configuration)
14 + [Front Matter](#front-matter)
15 + [Pages](#pages-1)
16 + [Posts](#posts-1)
17 + [Data Files](#data-files)
18 + [Snippets](#snippets)
19 + [Includes](#includes)
20 + [header.html](#headerhtml)
21 + [navigation.html](#navigationhtml)
22 + [language-switch.html](#language-switchhtml)
23 + [if page.layout == 'page'](#if-pagelayout--page)
24 + [elsif page.layout == 'post'](#elsif-pagelayout--post)
25 + [else](#else)
26 + [Fallback Page](#fallback-page)
27 + [title.html](#titlehtml)
28 + [localizations.html](#localizationshtml)
29 + [Multilingual Sitemaps](#multilingual-sitemaps)
30 + [Sitemap Index File](#sitemap-index-file)
31 + [Sitemap Files](#sitemap-files)
32 + [RSS Feed](#rss-feed)
33 + [404 Page Not Found](#404-page-not-found)
34 + [Resources](#resources)
35 + [Afterword](#afterword)
36
37 ## Preface
38
39 When I found myself coding a multilingual site in [Jekyll](https://jekyllrb.com/ "Jekyll"), I stumbled on [a lot of useful resources](#resources) while surfing the Web, but I struggled not a little while trying to digest and replicate their approaches because of the lack of a concrete, working example to look at.
40
41 At first, I tried to replicate their approaches directly in the site I was working on, but this quickly backfired because it proved to be too big of a bite to chew for a designer who codes.
42
43 Not giving up, I then opted for creating a basic site from scratch, so that I could just focus on experimenting with multiple languages in Jekyll without any extra complexity in the picture.
44
45 That very same basic site is hosted in this repository, which I gladly share with the world as an example project, hoping to be of a help for anybody who is into coding a multilingual site using Jekyll.
46
47 ## Foreword
48
49 A few words before starting. Sites built with the approach illustrated in this README:
50
51 + can support [as many languages as needed](#directory-structure)
52 + can serve pages or posts that do not necessarily need to be translated in all the supported languages
53 + have a [language switch](#language-switchhtml) that can either direct web surfers to view the current page or post in the selected language, if available, or can direct them to an alternative [fallback page](#fallback-page)
54 + do not need you to install custom plugins
55 + leverage the basics of Jekyll and thus should be relatively future-proof (last famous words)
56 + can be published as *GitHub Pages* sites
57
58 Specifically, [the basic site](https://ranbureand.github.io/multilingual-experiment/) hosted in this repository and used as an example:
59
60 + is visually quite crude, since the focus is on illustrating a structural (not visual) approach to building multilingual sites
61 + supports English and Italian as example languages
62
63 ## Directory Structure
64
65 The directory structure of this basic site looks like this:
66
67 ```
68 .
69 ├── _data
70 │ └── snippets.yml
71 ├── _includes
72 │ ├── head.html
73 │ ├── header.html
74 │ ├── localizations.html
75 │ └── references.html
76 ├── _layouts
77 │ ├── base.html
78 │ ├── index.html
79 │ ├── page.html
80 │ └── post.html
81 ├── _posts
82 │ ├── en
83 │ │ ├── YYYY-MM-DD-title.markdown
84 │ │ ├── …
85 │ │ └── YYYY-MM-DD-title.markdown
86 │ └── it
87 │ ├── YYYY-MM-DD-titolo.markdown
88 │ ├── …
89 │ └── YYYY-MM-DD-titolo.markdown
90 ├── 404.html
91 ├── config.yml
92 ├── en
93 │ ├── drafts.html
94 │ ├── feed.xml
95 │ ├── postface.html
96 │ ├── preface.html
97 │ ├── sitemap.xml
98 │ └── stories.html
99 ├── index.html
100 ├── it
101 │ ├── bozze.html
102 │ ├── feed.xml
103 │ ├── prefazione.html
104 │ ├── sitemap.xml
105 │ └── storie.html
106 └── sitemap.xml
107 ```
108
109 ### Pages
110
111 We organize the pages into as many subdirectory as the languages that we plan to support, and name them using [ISO language codes](https://www.w3schools.com/tags/ref_language_codes.asp "HTML Language Code Reference in W3Schools"). This basic site has two subdirectories, one named `en` for grouping the English pages, and one named `it` for grouping the Italian pages.
112
113 ```
114 ├── …
115 ├── en
116 │ ├── drafts.html
117 │ ├── feed.xml
118 │ ├── postface.html
119 │ ├── preface.html
120 │ ├── sitemap.xml
121 │ └── stories.html
122 ├── …
123 ├── it
124 │ ├── bozze.html
125 │ ├── feed.xml
126 │ ├── prefazione.html
127 │ ├── sitemap.xml
128 │ └── storie.html
129 ├── …
130 ```
131
132 After Jekyll has built the site, we can reach, for example, the English page `stories.html` and the Italian page `storie.html` at the URLs `www.site.ext/en/stories.html` and `www.site.ext/it/storie.html`, respectively.
133
134 #### Exceptions
135
136 But, of course, there are exceptions. We place the pages `404.html`, `index.html`, and `sitemap.html` in the root directory of the site. Why?
137
138 `404.html` and `index.html` are *unique* pages because Jekyll builds and serves automatically one and only one of them at a time.
139
140 `sitemap.xml` instead is none other than a [Sitemap index](https://www.sitemaps.org/protocol.html#index "Sitemaps XML Format, Sitemap index") which points to the other localized sitemaps in the respective language subfolders (read the section [Multilingual Sitemap](#multilingual-sitemap) for more details).
141
142 ### Posts
143
144 We organize the posts following a similar logic. This basic site has two subdirectories in the folder named `_posts`, one named `en` for grouping the English posts, and one named `it` for grouping the Italian posts.
145
146 ```
147 ├── …
148 ├── _posts
149 │ ├── en
150 │ │ ├── YYYY-MM-DD-title.markdown
151 │ │ ├── …
152 │ │ └── YYYY-MM-DD-title.markdown
153 │ └── it
154 │ ├── YYYY-MM-DD-titolo.markdown
155 │ ├── …
156 │ └── YYYY-MM-DD-titolo.markdown
157 ├── …
158 ```
159
160 #### Configuration
161
162 We then add the following configuration options in the `_config.yml` file placed in the site’s root directory:
163
164 ``` yaml
165 defaults:
166 -
167 scope:
168 path: '_posts/en'
169 type: 'posts'
170 values:
171 permalink: 'en/story/:title'
172 language: en
173 -
174 scope:
175 path: '_posts/it'
176 type: 'posts'
177 values:
178 permalink: 'it/storia/:title'
179 language: it
180 ```
181
182 By setting global permalinks for posts, we can reach, for example, the English post named `2021-01-01-hello-world.markdown` and the Italian post named `2021-01-01-ciao-mondo.markdown` at the URLs `www.site.ext/en/hello-world.html` and `www.site.ext/it/ciao-mondo.html`, respectively.
183
184 ## Front Matter
185
186 ### Pages
187
188 Here is how the front matter of a page looks like:
189
190 ``` yaml
191 ---
192 layout: page
193
194 title: Stories
195 description: Stories.
196
197 language: en
198 language_reference: stories
199
200 published: true
201 ---
202 ```
203
204 But for the usual variables, we set two new ones, `language` to define the language of the page, and `language_reference` to relate different translations of the same page. The logic is based on the principle articulated in Sylvain Durand’s *[Making Jekyll Multilingual](https://sylvaindurand.org/making-jekyll-multilingual/#principle "Making Jekyll
205 Multilingual")*.
206
207 For example, here is the front matter of the English page *Stories*:
208
209 ``` yaml
210 ---
211 layout: page
212
213 title: Stories
214 description: Stories.
215
216 language: en
217 language_reference: stories
218
219 published: true
220 ---
221 ```
222
223 and here is the front matter of its Italian counterpart:
224
225 ``` yaml
226 ---
227 layout: page
228
229 title: Storie
230 description: Storie.
231
232 language: it
233 language_reference: stories
234
235 published: true
236 ---
237 ```
238
239 Both pages have the variable `language_reference` set to `stories` so that they can be easily related.
240
241 We can use `language` to retrieve only the pages that have the same language, and `language_reference` to retrieve only the pages that return the same content translated in different languages.
242
243 ### Posts
244
245 Here is how the front matter of a post looks like:
246
247 ``` yaml
248 ---
249 layout: post
250
251 title: Hello World
252 description: Hello world.
253 date: 2021-01-01 00:00:00
254
255 language: en
256 language_reference: world
257
258 published: true
259 ---
260 ```
261
262 Again, but for the usual variables, we set two new ones, `language` to define the language of the post, and `language_reference` to relate different translations of the same post.
263
264 For example, here is the front matter of the English post *Hello World*:
265
266 ``` yaml
267 ---
268 layout: post
269
270 title: Hello World
271 description: Hello world.
272 date: 2021-01-01 00:00:00
273
274 language: en
275 language_reference: world
276
277 published: true
278 ---
279 ```
280
281 and here is the front matter of its Italian counterpart:
282
283 ``` yaml
284 ---
285 layout: post
286
287 title: Ciao Mondo
288 description: Ciao Mondo.
289 date: 2021-01-01 00:00:00
290
291 language: it
292 language_reference: world
293
294 published: true
295 ---
296 ```
297
298 Both posts have the variable `language_reference` set to `world` so that they can be easily related.
299
300 Again, we can use `language` to retrieve only the posts that have the same language, and `language_reference` to retrieve only the posts that return the same content translated in different languages.
301
302 ## Data Files
303
304 ### Snippets
305
306 We create a YAML [Data File](https://jekyllrb.com/docs/datafiles/ "Data Files") named `snippets.yml` to store the different translations of the user interface copy as additional data in the `_data` subdirectory.
307
308 We then create a new variable named `snippets` in the `base.html` layout to shorten the code that we need to write to access the data contained in the `snippets.yml` file:
309
310 ``` liquid
311 {%- assign snippets = site.data.snippets %}
312 ```
313
314 Since the `base.html` layout works as the base for all the other layouts, if we place the variable `snippets` there, we can then call it from any page.
315
316 Through this variable, we can write just `snippets.name_of_the_data_item` when accessing a data item rather than the full, longer `site.data.snippets.name_of_the_data_item`.
317
318 For example, the piece of code that generates *Back to the Top* link at the bottom of the page:
319
320 ``` liquid
321 <a href="#{{ snippets.top[page.language] | slugify: 'latin' }}">{{ snippets.back[page.language] }}</a>
322 ```
323
324 uses the following variable:
325
326 ``` liquid
327 {{ snippets.back[page.language] }}
328 ```
329
330 to retrieve the name of the link in the current selected language from the following lines in the `snippets.yml` data file:
331
332 ``` yaml
333 back:
334 en: Back to the Top
335 it: Torna in Cima
336
337 top:
338 en: Top
339 it: Cima
340 ```
341
342 ## Includes
343
344 The purpose of most of the includes in this basic site is building the navigation.
345
346 ### header.html
347
348 The include `header.html` generates the header in the HTML page. It, in turn, has three more includes:
349
350 + `title.html`
351 + `navigation.html`
352 + `language-switch.html`
353
354 ``` liquid
355 <header>
356 {% include site-title.html %}
357 <nav>
358 {% include navigation.html %}
359
360 {% include language-switch.html %}
361 </nav>
362 </header>
363 ```
364
365 #### navigation.html
366
367 The include `navigation.html` generates an unordered list containing all the published pages having the same `language` variable as the current page.
368
369 ``` liquid
370 <ul>
371 {%- assign navigation_pages = site.pages
372 | where: 'layout', 'page'
373 | where: 'language', page.language
374 | where: 'published', true
375 | sort: 'order' %}
376 {%- for navigation_page in navigation_pages %}
377 <li{%- if navigation_page.title == page.title %} class="current"{%- endif %}>
378 <a href="{{ site.baseurl }}{{ navigation_page.url }}">{{ navigation_page.title }}</a>
379 </li>
380 {%- endfor %}
381 </ul>
382 ```
383
384 In the code above, we create a new variable named `navigation_pages` which returns a list of the pages that, [in their front matter](#pages-1), have:
385
386 + the `layout` variable set to `page`
387 + the `language` variable set to the language of the current page (`page.language`)
388 + the `published` variable set to `true`
389
390 and we order the list according to the `order` variable. We then loop trough the array of pages and generate the list items of the unordered list.
391
392 Whenever the title of the current page in the array (`navigation_page.title`) matches the title of the current page (`page.title`), we add a class named `current` to the corresponding `<li/>` tag.
393
394 #### language-switch.html
395
396 The include `language-switch.html` generates an unordered list containing all the languages supported in the site. You can use the list to switch to one of the other language translations of the current page/post, if available.
397
398 ``` liquid
399 <ul>
400 {%- for language in snippets.languages %}
401
402 {%- if page.layout == 'page' %}
403 {%- assign navigation_pages = site.pages
404 | where: 'language_reference', page.language_reference
405 | where: 'language', language[1].slug %}
406 {%- if navigation_pages.size == 1 %}
407 {%- for navigation_page in navigation_pages %}
408 {%- assign url = site.baseurl | append: navigation_page.url %}
409 {%- endfor %}
410 {%- else %}
411 {%- assign navigation_pages = site.pages
412 | where: 'language_reference', site.fallback_page
413 | where: 'language', language[1].slug %}
414 {%- for navigation_page in navigation_pages %}
415 {%- assign url = site.baseurl | append: navigation_page.url %}
416 {%- endfor %}
417 {%- endif %}
418
419 {%- elsif page.layout == 'post' %}
420 {%- assign navigation_posts = site.posts
421 | where: 'language_reference', page.language_reference
422 | where: 'language', language[1].slug %}
423 {%- if navigation_posts.size == 1 %}
424 {%- for navigation_post in navigation_posts %}
425 {%- assign url = site.baseurl | append: navigation_post.url %}
426 {%- endfor %}
427 {%- else %}
428 {%- assign navigation_pages = site.pages
429 | where: 'language_reference', site.fallback_page
430 | where: 'language', language[1].slug %}
431 {%- for navigation_page in navigation_pages %}
432 {%- assign url = site.baseurl | append: navigation_page.url %}
433 {%- endfor %}
434 {%- endif %}
435
436 {%- else %}
437 {%- assign navigation_pages = site.pages
438 | where: 'language_reference', site.fallback_page
439 | where: 'language', language[1].slug %}
440 {%- for navigation_page in navigation_pages %}
441 {%- assign url = site.baseurl | append: navigation_page.url %}
442 {%- endfor %}
443
444 {%- endif %}
445 <li{%- if language[1].slug == page.language %} class="current"{%- endif %}>
446 <a href="{{ url }}">{{ language[1].value }}</a>
447 </li>
448 {%- endfor %}
449 </ul>
450 ```
451
452 In the code above, we loop through the languages defined in the `snippets.html` file (read the section [Snippets](#snippets) for more details).
453
454 ``` yaml
455 languages:
456 en:
457 value: English
458 slug: en
459 it:
460 value: Italian
461 slug: it
462 ```
463
464 The *for* loop contains three different code blocks that are run only if specific conditions are met. If we were to look only at its high-level structure:
465
466 ``` liquid
467 <ul>
468 {%- for language in snippets.languages %}
469
470 {%- if page.layout == 'page' %}
471 <!-- first code block -->
472
473 {%- elsif page.layout == 'post' %}
474 <!-- second code block -->
475
476 {%- else %}
477 <!-- third code block -->
478
479 {%- endif %}
480 <li {%- if language[1].slug == page.language %} class="current"{%- endif %}>
481 <a href="{{ url }}">{{ language[1].value }}</a>
482 </li>
483 {%- endfor %}
484 </ul>
485 ```
486
487 We run the first block of code only if the `layout` variable of the current page is set to `page`, else, if it is set to `post`, we run the second block of code, else, if it is set to anything else (or to nothing at all), we run the third block of code.
488
489 After at least one of the code blocks has been run, we generate the list items of the unordered list.
490
491 Whenever the slug of the current language item of the array `snippets.languages` (`language[1].slug`) matches the language of the current page (`page.language`), we add a class named `current` to the corresponding `<li/>` tag.
492
493 ##### if page.layout == 'page'
494
495 ``` liquid
496 {%- if page.layout == 'page' %}
497 {%- assign navigation_pages = site.pages
498 | where: 'language_reference', page.language_reference
499 | where: 'language', language[1].slug %}
500 {%- if navigation_pages.size == 1 %}
501 {%- for navigation_page in navigation_pages %}
502 {%- assign url = site.baseurl | append: navigation_page.url %}
503 {%- endfor %}
504 {%- else %}
505 {%- assign navigation_pages = site.pages
506 | where: 'language_reference', site.fallback_page
507 | where: 'language', language[1].slug %}
508 {%- for navigation_page in navigation_pages %}
509 {%- assign url = site.baseurl | append: navigation_page.url %}
510 {%- endfor %}
511 {%- endif %}
512 ```
513
514 What does the first block of code do?
515
516 ``` liquid
517 {%- assign navigation_pages = site.pages
518 | where: 'language_reference', page.language_reference
519 | where: 'language', language[1].slug %}
520 ```
521
522 We create a new variable named `navigation_pages` which returns a list of the pages that, [in their front matter](#pages-1), have:
523
524 + the `language_reference` variable equal to the current page’s `language_reference` variable (`page.language_reference`)
525 + the `language` variable equal to the slug of the current language item (`language[1].slug`) in the array `snippets.languages`
526
527 If we set the front matter of the pages correctly, the size of the array `navigation_pages` should be:
528
529 + either equal to one if the current page <u>has</u> a corresponding page translated in the current language item of the array `snippets.languages`
530 + or equal to zero if the current page <u>does not have</u> a corresponding page translated in the current language item of the array `snippets.languages`
531
532 ``` liquid
533 {%- if navigation_pages.size == 1 %}
534 {%- for navigation_page in navigation_pages %}
535 {%- assign url = site.baseurl | append: navigation_page.url %}
536 {%- endfor %}
537 ```
538
539 If the size of the array `navigation_pages` is equal to one, we loop through the array `navigation_pages` and create a new variable named `url` by combining the `site.baseurl` (defined in the `_config.yml` file) and the url of the one page (`navigation_page.url`) contained in the array `navigation_pages`.
540
541 ``` liquid
542 {%- else %}
543 {%- assign navigation_pages = site.pages
544 | where: 'language_reference', site.fallback_page
545 | where: 'language', language[1].slug %}
546 {%- for navigation_page in navigation_pages %}
547 {%- assign url = site.baseurl | append: navigation_page.url %}
548 {%- endfor %}
549 {%- endif %}
550 ```
551
552 If instead, the size of the array `navigation_pages` is equal to zero (or more than one, which is trouble), we do not have a corresponding page in the current language item of the array `snippets.languages` to switch to.
553
554 Thus, we provide a fallback page (`site.fallback_page`) so that web surfers who interact with the language switch and press on a language that does not support the current page are at least redirected to a meaningful page in the language they selected.
555
556 We set the `fallback_page` in the `_config.yml` file placed in the site’s root directory:
557
558 ``` yaml
559 fallback_page: 'stories'
560 ```
561
562 The fallback pages of this basic site are those whose `language_reference` variable is set to `stories`.
563
564 Why `stories`? Because the pages whose `language_reference` variable is set to `stories` work as *home* pages, since they:
565
566 + return a list of all the published posts (they have exactly the same structure as the `index.html` page)
567 + have a translated counterpart in all the languages supported on the site
568
569 ##### elsif page.layout == 'post'
570
571 ``` liquid
572 {%- elsif page.layout == 'post' %}
573 {%- assign navigation_posts = site.posts
574 | where: 'language_reference', page.language_reference
575 | where: 'language', language[1].slug %}
576 {%- if navigation_posts.size == 1 %}
577 {%- for navigation_post in navigation_posts %}
578 {%- assign url = site.baseurl | append: navigation_post.url %}
579 {%- endfor %}
580 {%- else %}
581 {%- assign navigation_pages = site.pages
582 | where: 'language_reference', site.fallback_page
583 | where: 'language', language[1].slug %}
584 {%- for navigation_page in navigation_pages %}
585 {%- assign url = site.baseurl | append: navigation_page.url %}
586 {%- endfor %}
587 {%- endif %}
588 ```
589
590 The second block of code behaves akin to the first, with the only difference that we manipulate an array of posts (`navigation_posts`) rather than one of pages (`navigation_pages`).
591
592 ##### else
593
594 ``` liquid
595 {%- else %}
596 {%- assign navigation_pages = site.pages
597 | where: 'language_reference', site.fallback_page
598 | where: 'language', language[1].slug %}
599 {%- for navigation_page in navigation_pages %}
600 {%- assign url = site.baseurl | append: navigation_page.url %}
601 {%- endfor %}
602 ```
603
604 The third block of code runs in the remote eventuality in which both the first and second blocks of code are not run, so that we make sure, again, to serve a fallback page to our web surfers.
605
606 ##### Fallback Page
607
608 How can we be sure that the fallback page truly works?
609
610 In this basic site, not all the pages and posts are translated into all the supported languages—on purpose.
611
612 ###### Pages
613
614 | English | Italian |
615 | - | - |
616 | preface.html | prefazione.html |
617 | stories.html | storie.html |
618 | postface.html| — |
619
620 If you go to [the English page *Postface*](https://ranbureand.github.io/multilingual-experiment/en/postface.html) and press on *Italian* in the language switch, you can see that you are indeed redirected to the Italian page *Storie*.
621
622 ###### Posts
623
624 | English | Italian |
625 | - | - |
626 | hello-world.markdown | ciao-mondo.markdown |
627 | hello-mars.markdown | ciao-marte.markdown |
628 | — | ciao-giove.markdown |
629
630 Similarly, if you go to [the Italian post *Ciao Giove*](https://ranbureand.github.io/multilingual-experiment/it/storia/ciao-giove) and press on *English* in the language switch, you can see that you are indeed redirected to the English page *Stories*.
631
632 #### title.html
633
634 The include `title.html` generates the title of this basic site.
635
636 ``` liquid
637 {%- if page.language == site.default_language %}
638 {%- assign url = site.baseurl | append: '/'%}
639 {%- else %}
640 {%- assign navigation_pages = site.pages
641 | where: 'language_reference', site.fallback_page
642 | where: 'language', page.language %}
643 {%- for navigation_page in navigation_pages %}
644 {%- assign url = site.baseurl | append: navigation_page.url %}
645 {%- endfor %}
646 {%- endif %}
647 <h1>
648 <a href="{{ url }}" {%- if page.url == '/' %} class="current"{%- endif %}>{{ site.title }}</a>
649 </h1>
650 ```
651
652 Again, we have two different code blocks that are run only if specific conditions are met.
653
654 We run the first code block when the language of the current page (`page.language`) is equal to the default language (`site.default_language`) defined in the `_config.yml` file. Through it we create a new variable named `url` by combining the `site.baseurl` (defined in the `_config.yml` file) and `/`, that is, the domain name of the site. Web surfers who browse the site in the default language are directed to the main page when they press on the title.
655
656 Else, we run the second code block to provide the usual fallback page already discussed above (read the section [language-switch.html](#language-switchhtml) for more details). Web surfers who browse the site in a language different than the default one are directed to the fallback page in their current language when they press on the title.
657
658 ### localizations.html
659
660 The include `localizations.html` adds `<link rel="alternate" … />` tags in the `<head/>` tag of a page [to tell search engines](https://developers.google.com/search/docs/advanced/crawling/localized-versions "Tell Google about localized versions of your page") if there are multiple versions of the page for different languages or regions.
661
662 ``` liquid
663 {%- if page.layout == 'page' %}
664 {%- assign localized_pages = site.pages
665 | where: 'language_reference', page.language_reference
666 | sort: 'language' %}
667 {%- for localized_page in localized_pages %}
668 <link rel="alternate" hreflang="{{ localized_page.language }}" href="{{ site.baseurl }}{{ localized_page.url }}" />
669 {%- endfor %}
670
671 {%- elsif page.layout == 'post' %}
672 {%- assign localized_posts = site.posts
673 | where: 'language_reference', page.language_reference
674 | sort: 'language' %}
675 {%- for localized_post in localized_posts %}
676 <link rel="alternate" hreflang="{{ localized_post.language }}" href="{{ site.baseurl }}{{ localized_post.url }}" />
677 {%- endfor %}
678
679 {%- elsif page.layout == 'index' %}
680 {%- assign localized_pages = site.pages
681 | where: 'language_reference', site.fallback_page
682 | sort: 'language' %}
683 {%- for localized_page in localized_pages %}
684 <link rel="alternate" hreflang="{{ localized_page.language }}" href="{{ site.baseurl }}{{ localized_page.url }}" />
685 {%- endfor %}
686 {%- endif %}
687 ```
688
689 Again, we have three different code blocks that are run only if specific conditions are met (read the section [language-switch.html](#language-switchhtml) for more details).
690
691 ## Multilingual Sitemaps
692
693 To serve a multilingual sitemap, we need to create a [Sitemap index](https://www.sitemaps.org/protocol.html#index "Sitemaps XML Format, Sitemap index") file and list a Sitemap file for each language we support.
694
695 ### Sitemap Index File
696
697 We place the page named `sitemap.html` in the root directory of the site. It points to the other localized sitemaps in the respective language subfolders.
698
699 ``` liquid
700 ---
701 layout: none
702
703 sitemap:
704 excluded: true
705 ---
706
707 <?xml version="1.0" encoding="UTF-8"?>
708 <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
709
710 {%- assign pages = site.pages | where: 'language_reference', 'sitemap' %}
711
712 {%- for page in pages %}
713 <sitemap>
714 <loc>{{ site.absoluteurl }}{{ page.url | remove: 'index.html' }}</loc>
715
716 {%- if page.sitemap.lastmod %}
717 {%- assign lastmod = page.sitemap.lastmod | date: '%Y-%m-%d' %}
718 {%- elsif page.date %}
719 {%- assign lastmod = page.date | date_to_xmlschema %}
720 {%- else %}
721 {%- assign lastmod = site.time | date_to_xmlschema %}
722 {%- endif %}
723 <lastmod>{{ lastmod }}</lastmod>
724 </sitemap>
725 {%- endfor %}
726
727 </sitemapindex>
728 ```
729
730 By setting the following variables in the front matter of the Sitemap index file:
731
732 ``` yaml
733 sitemap:
734 excluded: true
735 ```
736
737 we make sure to exclude it from the list of pages returned in the other Sitemap files.
738
739 ### Sitemap Files
740
741 ``` liquid
742 ---
743 layout: none
744
745 title: English Sitemap
746
747 language: en
748 language_reference: sitemap
749
750 sitemap:
751 excluded: true
752 ---
753
754 <?xml version="1.0" encoding="UTF-8"?>
755 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
756
757 {%- assign posts = site.posts | sort: 'date' | where: 'language', page.language | where: 'published', true %}
758
759 {%- for post in posts reversed %}
760 {%- unless post.sitemap.excluded == true %}
761 <url>
762 <loc>{{ site.absoluteurl }}{{ post.url }}</loc>
763
764 {%- if post.sitemap.lastmod %}
765 {%- assign lastmod = post.sitemap.lastmod | date: '%Y-%m-%d' %}
766 {%- elsif post.date %}
767 {%- assign lastmod = post.date | date_to_xmlschema %}
768 {%- else %}
769 {%- assign lastmod = site.time | date_to_xmlschema %}
770 {%- endif %}
771 <lastmod>{{ lastmod }}</lastmod>
772
773 {%- if post.sitemap.changefreq %}
774 {%- assign changefreq = post.sitemap.changefreq %}
775 {%- else %}
776 {%- assign changefreq = 'monthly' %}
777 {%- endif %}
778 <changefreq>{{ changefreq }}</changefreq>
779
780 {%- if post.sitemap.priority %}
781 {%- assign priority = post.sitemap.priority %}
782 {%- else %}
783 {%- assign priority = 0.5 %}
784 {%- endif %}
785 <priority>{{ priority }}</priority>
786 </url>
787 {%- endunless %}
788 {%- endfor %}
789
790 {%- assign pages = site.pages | where: 'language', page.language %}
791
792 {%- for page in pages %}
793 {%- unless page.sitemap.excluded == true %}
794 <url>
795 <loc>{{ site.absoluteurl }}{{ page.url | remove: 'index.html' }}</loc>
796
797 {%- if post.sitemap.lastmod %}
798 {%- assign lastmod = page.sitemap.lastmod | date: '%Y-%m-%d' %}
799 {%- elsif post.date %}
800 {%- assign lastmod = page.date | date_to_xmlschema %}
801 {%- else %}
802 {%- assign lastmod = site.time | date_to_xmlschema %}
803 {%- endif %}
804 <lastmod>{{ lastmod }}</lastmod>
805
806 {%- if page.sitemap.changefreq %}
807 {%- assign changefreq = page.sitemap.changefreq %}
808 {%- else %}
809 {%- assign changefreq = 'monthly' %}
810 {%- endif %}
811 <changefreq>{{ changefreq }}</changefreq>
812
813 {%- if page.sitemap.priority %}
814 {%- assign priority = page.sitemap.priority %}
815 {%- else %}
816 {%- assign priority = 0.3 %}
817 {%- endif %}
818 <priority>{{ priority }}</priority>
819 </url>
820 {%- endunless %}
821 {%- endfor %}
822
823 </urlset>
824 ```
825
826 ``` yaml
827 ---
828
829
830 sitemap:
831 lastmod: true
832 changefreq: 'monthly'
833 priority: ''
834 ---
835 ```
836
837 ### RSS Feed
838
839 *Coming soon…*
840
841 ### 404 Page Not Found
842
843 *Coming soon…*
844
845 ## Resources
846
847 + [Making Jekyll multilingual](https://sylvaindurand.org/making-jekyll-multilingual/ "Making Jekyll multilingual")
848 + [Making a multilingual website with Jekyll collections](https://www.kooslooijesteijn.net/blog/multilingual-website-with-jekyll-collections "Making a multilingual website with Jekyll collections")
849
850 ## Afterword
851
852 If you feel like adding something to the subject and/or you have spotted something worth fixing, please feel free to either [drop me a line](andreaburan.com/ "Andrea Buran’s Sitefolio") or [create an issue on GitHub](https://github.com/ranbureand/multilingual-experiment/issues): thoughts, critiques, suggestions are all more than welcomed.
853
854 Thank you!
855