Add multilingual taxonomies
This commit is contained in:
parent
cae9223ebd
commit
538866487b
387
Cargo.lock
generated
387
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -42,6 +42,8 @@ pub struct Taxonomy {
|
||||||
pub paginate_path: Option<String>,
|
pub paginate_path: Option<String>,
|
||||||
/// Whether to generate a RSS feed only for each taxonomy term, defaults to false
|
/// Whether to generate a RSS feed only for each taxonomy term, defaults to false
|
||||||
pub rss: bool,
|
pub rss: bool,
|
||||||
|
/// The language for that taxonomy, only used in multilingual sites
|
||||||
|
pub lang: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Taxonomy {
|
impl Taxonomy {
|
||||||
|
@ -64,7 +66,7 @@ impl Taxonomy {
|
||||||
|
|
||||||
impl Default for Taxonomy {
|
impl Default for Taxonomy {
|
||||||
fn default() -> 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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ pub struct TaxonomyItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
// 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
|
// and it's not like we can sort things across sections by anything other
|
||||||
// than dates
|
// than dates
|
||||||
|
@ -64,7 +64,11 @@ impl TaxonomyItem {
|
||||||
.collect();
|
.collect();
|
||||||
let (mut pages, ignored_pages) = sort_pages_by_date(data);
|
let (mut pages, ignored_pages) = sort_pages_by_date(data);
|
||||||
let slug = slugify(name);
|
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
|
// We still append pages without dates at the end
|
||||||
pages.extend(ignored_pages);
|
pages.extend(ignored_pages);
|
||||||
|
@ -108,7 +112,7 @@ impl Taxonomy {
|
||||||
) -> Taxonomy {
|
) -> Taxonomy {
|
||||||
let mut sorted_items = vec![];
|
let mut sorted_items = vec![];
|
||||||
for (name, pages) in items {
|
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));
|
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 {
|
for (name, val) in &page.meta.taxonomies {
|
||||||
if taxonomies_def.contains_key(name) {
|
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);
|
all_taxonomies.entry(name).or_insert_with(HashMap::new);
|
||||||
|
|
||||||
for v in val {
|
for v in val {
|
||||||
|
@ -220,7 +232,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use config::{Config, Taxonomy as TaxonomyConfig};
|
use config::{Config, Taxonomy as TaxonomyConfig, Language};
|
||||||
use content::Page;
|
use content::Page;
|
||||||
use library::Library;
|
use library::Library;
|
||||||
|
|
||||||
|
@ -326,4 +338,112 @@ mod tests {
|
||||||
"Page `` has taxonomy `tags` which is not defined in config.toml"
|
"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"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -723,7 +723,13 @@ impl Site {
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_directory_exists(&self.output_path)?;
|
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)?;
|
let list_output = taxonomy.render_all_terms(&self.tera, &self.config, &self.library)?;
|
||||||
create_directory(&output_path)?;
|
create_directory(&output_path)?;
|
||||||
create_file(&output_path.join("index.html"), &self.inject_livereload(list_output))?;
|
create_file(&output_path.join("index.html"), &self.inject_livereload(list_output))?;
|
||||||
|
|
|
@ -479,6 +479,7 @@ fn can_build_site_with_pagination_for_taxonomy() {
|
||||||
paginate_by: Some(2),
|
paginate_by: Some(2),
|
||||||
paginate_path: None,
|
paginate_path: None,
|
||||||
rss: true,
|
rss: true,
|
||||||
|
lang: None,
|
||||||
});
|
});
|
||||||
site.load().unwrap();
|
site.load().unwrap();
|
||||||
|
|
||||||
|
|
|
@ -125,4 +125,18 @@ fn can_build_multilingual_site() {
|
||||||
assert!(file_contains!(public, "fr/rss.xml", "https://example.com/fr/blog/something-else/"));
|
assert!(file_contains!(public, "fr/rss.xml", "https://example.com/fr/blog/something-else/"));
|
||||||
// Italian doesn't have RSS enabled
|
// Italian doesn't have RSS enabled
|
||||||
assert!(!file_exists!(public, "it/rss.xml"));
|
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"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,7 +297,7 @@ mod tests {
|
||||||
fn can_get_taxonomy() {
|
fn can_get_taxonomy() {
|
||||||
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
|
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
|
||||||
let library = Library::new(0, 0, false);
|
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 tags = Taxonomy { kind: taxo_config, items: vec![tag] };
|
||||||
|
|
||||||
let taxonomies = vec![tags.clone()];
|
let taxonomies = vec![tags.clone()];
|
||||||
|
@ -336,7 +336,7 @@ mod tests {
|
||||||
fn can_get_taxonomy_url() {
|
fn can_get_taxonomy_url() {
|
||||||
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
|
let taxo_config = TaxonomyConfig { name: "tags".to_string(), ..TaxonomyConfig::default() };
|
||||||
let library = Library::new(0, 0, false);
|
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 tags = Taxonomy { kind: taxo_config, items: vec![tag] };
|
||||||
|
|
||||||
let taxonomies = vec![tags.clone()];
|
let taxonomies = vec![tags.clone()];
|
||||||
|
|
|
@ -16,6 +16,9 @@ languages = [
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you want to use per-language taxonomies, ensure you set the `lang` field in their
|
||||||
|
configuration.
|
||||||
|
|
||||||
## Content
|
## Content
|
||||||
Once the languages are added in, you can start to translate your content. Zola
|
Once the languages are added in, you can start to translate your content. Zola
|
||||||
uses the filename to detect the language:
|
uses the filename to detect the language:
|
||||||
|
|
|
@ -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).
|
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)
|
- `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_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.
|
- `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
|
For example the default would be page/1
|
||||||
- `rss`: if set to `true`, a RSS feed will be generated for each individual term.
|
- `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
|
Once this is done, you can then set taxonomies in your content and Zola will pick
|
||||||
them up:
|
them up:
|
||||||
|
|
|
@ -13,6 +13,11 @@ build_search_index = false
|
||||||
|
|
||||||
generate_rss = true
|
generate_rss = true
|
||||||
|
|
||||||
|
taxonomies = [
|
||||||
|
{name = "authors", rss = true},
|
||||||
|
{name = "auteurs", lang = "fr"},
|
||||||
|
]
|
||||||
|
|
||||||
languages = [
|
languages = [
|
||||||
{code = "fr", rss = true},
|
{code = "fr", rss = true},
|
||||||
{code = "it", rss = false},
|
{code = "it", rss = false},
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
+++
|
+++
|
||||||
title = "Quelque chose"
|
title = "Quelque chose"
|
||||||
date = 2018-10-09
|
date = 2018-10-09
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
auteurs = ["Vincent Prouillet"]
|
||||||
+++
|
+++
|
||||||
|
|
||||||
Un article
|
Un article
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
+++
|
+++
|
||||||
title = "Something"
|
title = "Something"
|
||||||
date = 2018-10-09
|
date = 2018-10-09
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
authors = ["Queen Elizabeth"]
|
||||||
+++
|
+++
|
||||||
|
|
||||||
A blog post
|
A blog post
|
||||||
|
|
3
test_site_i18n/templates/auteurs/list.html
Normal file
3
test_site_i18n/templates/auteurs/list.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% for author in terms %}
|
||||||
|
{{ author.name }} {{ author.slug }} {{ author.pages | length }}
|
||||||
|
{% endfor %}
|
21
test_site_i18n/templates/auteurs/single.html
Normal file
21
test_site_i18n/templates/auteurs/single.html
Normal 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 %}
|
3
test_site_i18n/templates/authors/list.html
Normal file
3
test_site_i18n/templates/authors/list.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% for term in terms %}
|
||||||
|
{{ term.name }} {{ term.slug }} {{ term.pages | length }}
|
||||||
|
{% endfor %}
|
21
test_site_i18n/templates/authors/single.html
Normal file
21
test_site_i18n/templates/authors/single.html
Normal 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 %}
|
Loading…
Reference in a new issue