Add multilingual taxonomies

This commit is contained in:
Vincent Prouillet 2019-01-07 21:03:34 +01:00
parent cae9223ebd
commit 538866487b
16 changed files with 404 additions and 207 deletions

387
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,8 @@ pub struct Taxonomy {
pub paginate_path: Option<String>,
/// Whether to generate a RSS feed only for each taxonomy term, defaults to false
pub rss: bool,
/// The language for that taxonomy, only used in multilingual sites
pub lang: Option<String>,
}
impl Taxonomy {
@ -64,7 +66,7 @@ impl Taxonomy {
impl Default for Taxonomy {
fn default() -> Taxonomy {
Taxonomy { name: String::new(), paginate_by: None, paginate_path: None, rss: false }
Taxonomy { name: String::new(), paginate_by: None, paginate_path: None, rss: false, lang: None }
}
}

View file

@ -48,7 +48,7 @@ pub struct TaxonomyItem {
}
impl TaxonomyItem {
pub fn new(name: &str, path: &str, config: &Config, keys: Vec<Key>, library: &Library) -> Self {
pub fn new(name: &str, taxonomy: &TaxonomyConfig, config: &Config, keys: Vec<Key>, library: &Library) -> Self {
// Taxonomy are almost always used for blogs so we filter by dates
// and it's not like we can sort things across sections by anything other
// than dates
@ -64,7 +64,11 @@ impl TaxonomyItem {
.collect();
let (mut pages, ignored_pages) = sort_pages_by_date(data);
let slug = slugify(name);
let permalink = config.make_permalink(&format!("/{}/{}", path, slug));
let permalink = if let Some(ref lang) = taxonomy.lang {
config.make_permalink(&format!("/{}/{}/{}", lang, taxonomy.name, slug))
} else {
config.make_permalink(&format!("/{}/{}", taxonomy.name, slug))
};
// We still append pages without dates at the end
pages.extend(ignored_pages);
@ -108,7 +112,7 @@ impl Taxonomy {
) -> Taxonomy {
let mut sorted_items = vec![];
for (name, pages) in items {
sorted_items.push(TaxonomyItem::new(&name, &kind.name, config, pages, library));
sorted_items.push(TaxonomyItem::new(&name, &kind, config, pages, library));
}
sorted_items.sort_by(|a, b| a.name.cmp(&b.name));
@ -186,6 +190,14 @@ pub fn find_taxonomies(config: &Config, library: &Library) -> Result<Vec<Taxonom
for (name, val) in &page.meta.taxonomies {
if taxonomies_def.contains_key(name) {
if taxonomies_def[name].lang != page.lang {
bail!(
"Page `{}` has taxonomy `{}` which is not available in that language",
page.file.path.display(),
name
);
}
all_taxonomies.entry(name).or_insert_with(HashMap::new);
for v in val {
@ -220,7 +232,7 @@ mod tests {
use super::*;
use std::collections::HashMap;
use config::{Config, Taxonomy as TaxonomyConfig};
use config::{Config, Taxonomy as TaxonomyConfig, Language};
use content::Page;
use library::Library;
@ -326,4 +338,112 @@ mod tests {
"Page `` has taxonomy `tags` which is not defined in config.toml"
);
}
#[test]
fn can_make_taxonomies_in_multiple_languages() {
let mut config = Config::default();
config.languages.push(Language {rss: false, code: "fr".to_string()});
let mut library = Library::new(2, 0, true);
config.taxonomies = vec![
TaxonomyConfig { name: "categories".to_string(), ..TaxonomyConfig::default() },
TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() },
TaxonomyConfig { name: "auteurs".to_string(), lang: Some("fr".to_string()), ..TaxonomyConfig::default() },
];
let mut page1 = Page::default();
let mut taxo_page1 = HashMap::new();
taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]);
taxo_page1.insert("categories".to_string(), vec!["Programming tutorials".to_string()]);
page1.meta.taxonomies = taxo_page1;
library.insert_page(page1);
let mut page2 = Page::default();
let mut taxo_page2 = HashMap::new();
taxo_page2.insert("tags".to_string(), vec!["rust".to_string()]);
taxo_page2.insert("categories".to_string(), vec!["Other".to_string()]);
page2.meta.taxonomies = taxo_page2;
library.insert_page(page2);
let mut page3 = Page::default();
page3.lang = Some("fr".to_string());
let mut taxo_page3 = HashMap::new();
taxo_page3.insert("auteurs".to_string(), vec!["Vincent Prouillet".to_string()]);
page3.meta.taxonomies = taxo_page3;
library.insert_page(page3);
let taxonomies = find_taxonomies(&config, &library).unwrap();
let (tags, categories, authors) = {
let mut t = None;
let mut c = None;
let mut a = None;
for x in taxonomies {
match x.kind.name.as_ref() {
"tags" => t = Some(x),
"categories" => c = Some(x),
"auteurs" => a = Some(x),
_ => unreachable!(),
}
}
(t.unwrap(), c.unwrap(), a.unwrap())
};
assert_eq!(tags.items.len(), 2);
assert_eq!(categories.items.len(), 2);
assert_eq!(authors.items.len(), 1);
assert_eq!(tags.items[0].name, "db");
assert_eq!(tags.items[0].slug, "db");
assert_eq!(tags.items[0].permalink, "http://a-website.com/tags/db/");
assert_eq!(tags.items[0].pages.len(), 1);
assert_eq!(tags.items[1].name, "rust");
assert_eq!(tags.items[1].slug, "rust");
assert_eq!(tags.items[1].permalink, "http://a-website.com/tags/rust/");
assert_eq!(tags.items[1].pages.len(), 2);
assert_eq!(authors.items[0].name, "Vincent Prouillet");
assert_eq!(authors.items[0].slug, "vincent-prouillet");
assert_eq!(authors.items[0].permalink, "http://a-website.com/fr/auteurs/vincent-prouillet/");
assert_eq!(authors.items[0].pages.len(), 1);
assert_eq!(categories.items[0].name, "Other");
assert_eq!(categories.items[0].slug, "other");
assert_eq!(categories.items[0].permalink, "http://a-website.com/categories/other/");
assert_eq!(categories.items[0].pages.len(), 1);
assert_eq!(categories.items[1].name, "Programming tutorials");
assert_eq!(categories.items[1].slug, "programming-tutorials");
assert_eq!(
categories.items[1].permalink,
"http://a-website.com/categories/programming-tutorials/"
);
assert_eq!(categories.items[1].pages.len(), 1);
}
#[test]
fn errors_on_taxonomy_of_different_language() {
let mut config = Config::default();
config.languages.push(Language {rss: false, code: "fr".to_string()});
let mut library = Library::new(2, 0, false);
config.taxonomies =
vec![TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() }];
let mut page1 = Page::default();
page1.lang = Some("fr".to_string());
let mut taxo_page1 = HashMap::new();
taxo_page1.insert("tags".to_string(), vec!["rust".to_string(), "db".to_string()]);
page1.meta.taxonomies = taxo_page1;
library.insert_page(page1);
let taxonomies = find_taxonomies(&config, &library);
assert!(taxonomies.is_err());
let err = taxonomies.unwrap_err();
// no path as this is created by Default
assert_eq!(
err.description(),
"Page `` has taxonomy `tags` which is not available in that language"
);
}
}

View file

@ -723,7 +723,13 @@ impl Site {
}
ensure_directory_exists(&self.output_path)?;
let output_path = self.output_path.join(&taxonomy.kind.name);
let output_path = if let Some(ref lang) = taxonomy.kind.lang {
let mid_path = self.output_path.join(lang);
create_directory(&mid_path)?;
mid_path.join(&taxonomy.kind.name)
} else {
self.output_path.join(&taxonomy.kind.name)
};
let list_output = taxonomy.render_all_terms(&self.tera, &self.config, &self.library)?;
create_directory(&output_path)?;
create_file(&output_path.join("index.html"), &self.inject_livereload(list_output))?;

View file

@ -479,6 +479,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
paginate_by: Some(2),
paginate_path: None,
rss: true,
lang: None,
});
site.load().unwrap();

View file

@ -125,4 +125,18 @@ fn can_build_multilingual_site() {
assert!(file_contains!(public, "fr/rss.xml", "https://example.com/fr/blog/something-else/"));
// Italian doesn't have RSS enabled
assert!(!file_exists!(public, "it/rss.xml"));
// Taxonomies are per-language
assert!(file_exists!(public, "authors/index.html"));
assert!(file_contains!(public, "authors/index.html", "Queen"));
assert!(!file_contains!(public, "authors/index.html", "Vincent"));
assert!(!file_exists!(public, "auteurs/index.html"));
assert!(file_exists!(public, "authors/queen-elizabeth/rss.xml"));
assert!(!file_exists!(public, "fr/authors/index.html"));
assert!(file_exists!(public, "fr/auteurs/index.html"));
assert!(!file_contains!(public, "fr/auteurs/index.html", "Queen"));
assert!(file_contains!(public, "fr/auteurs/index.html", "Vincent"));
assert!(!file_exists!(public, "fr/auteurs/vincent-prouillet/rss.xml"));
}

View file

@ -297,7 +297,7 @@ mod tests {
fn can_get_taxonomy() {
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
let library = Library::new(0, 0, false);
let tag = TaxonomyItem::new("Programming", "tags", &Config::default(), vec![], &library);
let tag = TaxonomyItem::new("Programming", &taxo_config, &Config::default(), vec![], &library);
let tags = Taxonomy { kind: taxo_config, items: vec![tag] };
let taxonomies = vec![tags.clone()];
@ -336,7 +336,7 @@ mod tests {
fn can_get_taxonomy_url() {
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
let library = Library::new(0, 0, false);
let tag = TaxonomyItem::new("Programming", "tags", &Config::default(), vec![], &library);
let tag = TaxonomyItem::new("Programming", &taxo_config, &Config::default(), vec![], &library);
let tags = Taxonomy { kind: taxo_config, items: vec![tag] };
let taxonomies = vec![tags.clone()];

View file

@ -16,6 +16,9 @@ languages = [
]
```
If you want to use per-language taxonomies, ensure you set the `lang` field in their
configuration.
## Content
Once the languages are added in, you can start to translate your content. Zola
uses the filename to detect the language:

View file

@ -7,13 +7,14 @@ Zola has built-in support for taxonomies.
The first step is to define the taxonomies in your [config.toml](./documentation/getting-started/configuration.md).
A taxonomy has 4 variables:
A taxonomy has 5 variables:
- `name`: a required string that will be used in the URLs, usually the plural version (i.e. tags, categories etc)
- `paginate_by`: if this is set to a number, each term page will be paginated by this much.
- `paginate_path`: if set, will be the path used by paginated page and the page number will be appended after it.
For example the default would be page/1
- `rss`: if set to `true`, a RSS feed will be generated for each individual term.
- `lang`: only set this if you are making a multilingual site and want to indicate which language this taxonomy is for
Once this is done, you can then set taxonomies in your content and Zola will pick
them up:

View file

@ -13,6 +13,11 @@ build_search_index = false
generate_rss = true
taxonomies = [
{name = "authors", rss = true},
{name = "auteurs", lang = "fr"},
]
languages = [
{code = "fr", rss = true},
{code = "it", rss = false},

View file

@ -1,6 +1,9 @@
+++
title = "Quelque chose"
date = 2018-10-09
[taxonomies]
auteurs = ["Vincent Prouillet"]
+++
Un article

View file

@ -1,6 +1,9 @@
+++
title = "Something"
date = 2018-10-09
[taxonomies]
authors = ["Queen Elizabeth"]
+++
A blog post

View file

@ -0,0 +1,3 @@
{% for author in terms %}
{{ author.name }} {{ author.slug }} {{ author.pages | length }}
{% endfor %}

View file

@ -0,0 +1,21 @@
{% if not paginator %}
Tag: {{ term.name }}
{% for page in term.pages %}
<article>
<h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3>
</article>
{% endfor %}
{% else %}
Tag: {{ term.name }}
{% for page in paginator.pages %}
{{page.title|safe}}
{% endfor %}
Num pagers: {{ paginator.number_pagers }}
Page size: {{ paginator.paginate_by }}
Current index: {{ paginator.current_index }}
First: {{ paginator.first | safe }}
Last: {{ paginator.last | safe }}
{% if paginator.previous %}has_prev{% endif%}
{% if paginator.next %}has_next{% endif%}
{% endif %}

View file

@ -0,0 +1,3 @@
{% for term in terms %}
{{ term.name }} {{ term.slug }} {{ term.pages | length }}
{% endfor %}

View file

@ -0,0 +1,21 @@
{% if not paginator %}
Tag: {{ term.name }}
{% for page in term.pages %}
<article>
<h3 class="post__title"><a href="{{ page.permalink | safe }}">{{ page.title | safe }}</a></h3>
</article>
{% endfor %}
{% else %}
Tag: {{ term.name }}
{% for page in paginator.pages %}
{{page.title|safe}}
{% endfor %}
Num pagers: {{ paginator.number_pagers }}
Page size: {{ paginator.paginate_by }}
Current index: {{ paginator.current_index }}
First: {{ paginator.first | safe }}
Last: {{ paginator.last | safe }}
{% if paginator.previous %}has_prev{% endif%}
{% if paginator.next %}has_next{% endif%}
{% endif %}