diff --git a/components/rendering/src/context.rs b/components/rendering/src/context.rs index aebdb431..44b8e74e 100644 --- a/components/rendering/src/context.rs +++ b/components/rendering/src/context.rs @@ -14,6 +14,7 @@ pub struct RenderContext<'a> { pub current_page_permalink: &'a str, pub permalinks: Cow<'a, HashMap>, pub insert_anchor: InsertAnchor, + pub lang: &'a str, } impl<'a> RenderContext<'a> { @@ -34,10 +35,13 @@ impl<'a> RenderContext<'a> { permalinks: Cow::Borrowed(permalinks), insert_anchor, config, + lang, } } // In use in the markdown filter + // NOTE: This RenderContext is not i18n-aware, see MarkdownFilter::filter for details + // If this function is ever used outside of MarkdownFilter, take this into consideration pub fn from_config(config: &'a Config) -> RenderContext<'a> { Self { tera: Cow::Owned(Tera::default()), @@ -46,6 +50,7 @@ impl<'a> RenderContext<'a> { permalinks: Cow::Owned(HashMap::new()), insert_anchor: InsertAnchor::None, config, + lang: &config.default_language, } } } diff --git a/components/rendering/src/markdown.rs b/components/rendering/src/markdown.rs index dd2dfdd2..c0078e75 100644 --- a/components/rendering/src/markdown.rs +++ b/components/rendering/src/markdown.rs @@ -342,6 +342,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> Result, ) -> Result { let mut tera_context = Context::new(); + + // nth and lang are inserted before passed arguments, so that they can be overriden explicitly + tera_context.insert("nth", &invocation_count); + tera_context.insert("lang", &context.lang); + for (key, value) in args.iter() { tera_context.insert(key, value); } @@ -114,7 +119,7 @@ fn render_shortcode( // Trimming right to avoid most shortcodes with bodies ending up with a HTML new line tera_context.insert("body", b.trim_end()); } - tera_context.insert("nth", &invocation_count); + tera_context.extend(context.tera_context.clone()); let mut template_name = format!("shortcodes/{}.md", name); diff --git a/components/rendering/tests/markdown.rs b/components/rendering/tests/markdown.rs index 2818598f..e955e0dc 100644 --- a/components/rendering/tests/markdown.rs +++ b/components/rendering/tests/markdown.rs @@ -181,6 +181,47 @@ fn can_render_shortcode_with_markdown_char_in_args_value() { } } +#[test] +fn can_render_html_shortcode_with_lang() { + let permalinks_ctx = HashMap::new(); + let config = Config::default_for_test(); + let mut tera = Tera::default(); + tera.extend(&ZOLA_TERA).unwrap(); + tera.add_raw_template("shortcodes/i18nshortcode.html", "{{ lang }}").unwrap(); + let context = RenderContext::new( + &tera, + &config, + &config.default_language, + "", + &permalinks_ctx, + InsertAnchor::None, + ); + + let res = render_content("a{{ i18nshortcode() }}a", &context).unwrap(); + assert_eq!(res.body, "

aena

\n"); +} + +#[test] +fn can_render_md_shortcode_with_lang() { + let permalinks_ctx = HashMap::new(); + let config = Config::default_for_test(); + let mut tera = Tera::default(); + tera.extend(&ZOLA_TERA).unwrap(); + tera.add_raw_template("shortcodes/i18nshortcode.md", "![Book cover in {{ lang }}](cover.{{ lang }}.png)").unwrap(); + let context = RenderContext::new( + &tera, + &config, + &config.default_language, + "", + &permalinks_ctx, + InsertAnchor::None, + ); + + let res = render_content("{{ i18nshortcode() }}", &context).unwrap(); + assert_eq!(res.body, "

\"Book

\n"); +} + + #[test] fn can_render_body_shortcode_with_markdown_char_in_name() { let permalinks_ctx = HashMap::new(); @@ -689,6 +730,28 @@ fn can_insert_anchor_with_other_special_chars() { ); } +#[test] +fn can_insert_anchor_with_lang() { + let mut tera = Tera::default(); + tera.extend(&ZOLA_TERA).unwrap(); + tera.add_raw_template("anchor-link.html", "({{ lang }})").unwrap(); + let permalinks_ctx = HashMap::new(); + let config = Config::default_for_test(); + let context = RenderContext::new( + &tera, + &config, + &config.default_language, + "", + &permalinks_ctx, + InsertAnchor::Right, + ); + let res = render_content("# Hello", &context).unwrap(); + assert_eq!( + res.body, + "

Hello(en)

\n" + ); +} + #[test] fn can_make_toc() { let permalinks_ctx = HashMap::new(); diff --git a/components/templates/src/filters.rs b/components/templates/src/filters.rs index 806feffe..a1c65734 100644 --- a/components/templates/src/filters.rs +++ b/components/templates/src/filters.rs @@ -33,6 +33,10 @@ impl MarkdownFilter { impl TeraFilter for MarkdownFilter { fn filter(&self, value: &Value, args: &HashMap) -> TeraResult { + // NOTE: RenderContext below is not aware of the current language + // However, it should not be a problem because the surrounding tera + // template has language context, and will most likely call a piece of + // markdown respecting language preferences. let mut context = RenderContext::from_config(&self.config); context.permalinks = Cow::Borrowed(&self.permalinks); context.tera = Cow::Borrowed(&self.tera); @@ -123,6 +127,22 @@ mod tests { assert_eq!(result.unwrap(), to_value(&"

Hey

\n").unwrap()); } + #[test] + fn markdown_filter_override_lang() { + // We're checking that we can use a workaround to explicitly provide `lang` in markdown filter from tera, + // because otherwise markdown filter shortcodes are not aware of the current language + // NOTE: This should also work for `nth` although i don't see a reason to do that + let args = HashMap::new(); + let config = Config::default(); + let permalinks = HashMap::new(); + let mut tera = super::load_tera(&PathBuf::new(), &config).map_err(tera::Error::msg).unwrap(); + tera.add_raw_template("shortcodes/explicitlang.html", "a{{ lang }}a").unwrap(); + let filter = MarkdownFilter { config, permalinks, tera }; + let result = filter.filter(&to_value(&"{{ explicitlang(lang='jp') }}").unwrap(), &args); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), to_value(&"ajpa").unwrap()); + } + #[test] fn markdown_filter_inline() { let mut args = HashMap::new(); diff --git a/docs/content/documentation/content/linking.md b/docs/content/documentation/content/linking.md index 3497fe0a..ecc232a9 100644 --- a/docs/content/documentation/content/linking.md +++ b/docs/content/documentation/content/linking.md @@ -39,11 +39,12 @@ This option is set at the section level: the `insert_anchor_links` variable on t The default template is very basic and will need CSS tweaks in your project to look decent. If you want to change the anchor template, it can be easily overwritten by -creating an `anchor-link.html` file in the `templates` directory. +creating an `anchor-link.html` file in the `templates` directory. [Here](https://github.com/getzola/zola/blob/master/components/templates/src/builtins/anchor-link.html) you can find the default template. The anchor link template has the following variables: - `id`: the heading's id after applying the rules defined by `slugify.anchors` +- `lang`: the current language, unless called from the `markdown` template filter, in which case it will always be `en` - `level`: the heading level (between 1 and 6) ## Internal links diff --git a/docs/content/documentation/content/shortcodes.md b/docs/content/documentation/content/shortcodes.md index defa896f..139d205c 100644 --- a/docs/content/documentation/content/shortcodes.md +++ b/docs/content/documentation/content/shortcodes.md @@ -134,10 +134,19 @@ If you want to have some content that looks like a shortcode but not have Zola t you will need to escape it by using `{%/*` and `*/%}` instead of `{%` and `%}`. You won't need to escape anything else until the closing tag. +## Shortcode context + +Every shortcode can access some variables, beyond what you explicitly passed as parameter. These variables are explained in the following subsections: + +- invocation count (`nth`) +- current language (`lang`), unless called from the `markdown` template filter (in which case it will always be the same value as `default_language` in configuration, or `en` when it is unset) + +When one of these variables conflict with a variable passed as argument, the argument value will be used. + ### Invocation Count Every shortcode context is passed in a variable named `nth` that tracks how many times a particular shortcode has -been invoked in a Markdown file. Given a shortcode `true_statement.html` template: +been invoked in the current Markdown file. Given a shortcode `true_statement.html` template: ```jinja2

{{ value }} is equal to {{ nth }}.

@@ -152,6 +161,18 @@ It could be used in our Markdown as follows: This is useful when implementing custom markup for features such as sidenotes or end notes. +### Current language + +**NOTE:** When calling a shortcode from within the `markdown` template filter, the `lang` variable will always be `en`. If you feel like you need that, please consider using template macros instead. If you really need that, you can rewrite your Markdown content to pass `lang` as argument to the shortcode. + +Every shortcode can access the current language in the `lang` variable in the context. This is useful for presenting/filtering information in a shortcode depending in a per-language manner. For example, to display a per-language book cover for the current page in a shortcode called `bookcover.md`: + +```jinja2 +![Book cover in {{ lang }}](cover.{{ lang }}.png) +``` + +You can then use it in your Markdown like so: `{{/* bookcover() */}}` + ## Built-in shortcodes Zola comes with a few built-in shortcodes. If you want to override a default shortcode template, diff --git a/docs/content/documentation/templates/overview.md b/docs/content/documentation/templates/overview.md index 1cfc83a2..5cfeeac2 100644 --- a/docs/content/documentation/templates/overview.md +++ b/docs/content/documentation/templates/overview.md @@ -64,7 +64,10 @@ Zola adds a few filters in addition to [those](https://tera.netlify.com/docs/#fi in Tera. ### markdown -Converts the given variable to HTML using Markdown. Please note that shortcodes evaluated by this filter cannot access the current rendering context. `config` will be available, but accessing `section` or `page` (among others) from a shortcode called within the `markdown` filter will prevent your site from building. See [this discussion](https://github.com/getzola/zola/pull/1358). +Converts the given variable to HTML using Markdown. There are a few differences compared to page/section Markdown rendering: + +- shortcodes evaluated by this filter cannot access the current rendering context: `config` will be available, but accessing `section` or `page` (among others) from a shortcode called within the `markdown` filter will prevent your site from building (see [this discussion](https://github.com/getzola/zola/pull/1358)) +- `lang` in shortcodes will always be equal to the site's `default_lang` (or `en` otherwise) ; it should not be a problem, but if it is in most cases, but if you need to use language-aware shortcodes in this filter, please refer to the [Shortcode context](@/documentation/content/shortcodes.md#shortcode-context) section of the docs. By default, the filter will wrap all text in a paragraph. To disable this behaviour, you can pass `true` to the inline argument: