Browse Source

Add multilingual taxonomies

index-subcmd
Vincent Prouillet 5 years ago
parent
commit
538866487b
16 changed files with 404 additions and 207 deletions
  1. +189
    -198
      Cargo.lock
  2. +3
    -1
      components/config/src/config.rs
  3. +124
    -4
      components/library/src/taxonomies/mod.rs
  4. +7
    -1
      components/site/src/lib.rs
  5. +1
    -0
      components/site/tests/site.rs
  6. +14
    -0
      components/site/tests/site_i18n.rs
  7. +2
    -2
      components/templates/src/global_fns/mod.rs
  8. +3
    -0
      docs/content/documentation/content/multilingual.md
  9. +2
    -1
      docs/content/documentation/content/taxonomies.md
  10. +5
    -0
      test_site_i18n/config.toml
  11. +3
    -0
      test_site_i18n/content/blog/something.fr.md
  12. +3
    -0
      test_site_i18n/content/blog/something.md
  13. +3
    -0
      test_site_i18n/templates/auteurs/list.html
  14. +21
    -0
      test_site_i18n/templates/auteurs/single.html
  15. +3
    -0
      test_site_i18n/templates/authors/list.html
  16. +21
    -0
      test_site_i18n/templates/authors/single.html

+ 189
- 198
Cargo.lock
File diff suppressed because it is too large
View File


+ 3
- 1
components/config/src/config.rs 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 }
}
}



+ 124
- 4
components/library/src/taxonomies/mod.rs 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"
);
}
}

+ 7
- 1
components/site/src/lib.rs 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))?;


+ 1
- 0
components/site/tests/site.rs 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();



+ 14
- 0
components/site/tests/site_i18n.rs 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"));

}

+ 2
- 2
components/templates/src/global_fns/mod.rs 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()];


+ 3
- 0
docs/content/documentation/content/multilingual.md 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:


+ 2
- 1
docs/content/documentation/content/taxonomies.md 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:


+ 5
- 0
test_site_i18n/config.toml 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},


+ 3
- 0
test_site_i18n/content/blog/something.fr.md View File

@@ -1,6 +1,9 @@
+++
title = "Quelque chose"
date = 2018-10-09

[taxonomies]
auteurs = ["Vincent Prouillet"]
+++

Un article

+ 3
- 0
test_site_i18n/content/blog/something.md View File

@@ -1,6 +1,9 @@
+++
title = "Something"
date = 2018-10-09

[taxonomies]
authors = ["Queen Elizabeth"]
+++

A blog post

+ 3
- 0
test_site_i18n/templates/auteurs/list.html View File

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

+ 21
- 0
test_site_i18n/templates/auteurs/single.html 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 %}

+ 3
- 0
test_site_i18n/templates/authors/list.html View File

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

+ 21
- 0
test_site_i18n/templates/authors/single.html 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 %}

Loading…
Cancel
Save