diff --git a/.gitignore b/.gitignore index 36614264fc..e64ea82741 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ guide/book .vscode tests/dummy_book/book/ +tests/localized_book/book/ # Ignore Jetbrains specific files. .idea/ diff --git a/src/book/book.rs b/src/book/book.rs index da2a0a3cfc..325383a0d0 100644 --- a/src/book/book.rs +++ b/src/book/book.rs @@ -5,27 +5,37 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use super::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem}; -use crate::config::BuildConfig; +use crate::build_opts::BuildOpts; +use crate::config::Config; use crate::errors::*; /// Load a book into memory from its `src/` directory. -pub fn load_book>(src_dir: P, cfg: &BuildConfig) -> Result { - let src_dir = src_dir.as_ref(); - let summary_md = src_dir.join("SUMMARY.md"); +pub fn load_book>( + root_dir: P, + cfg: &Config, + build_opts: &BuildOpts, +) -> Result { + let localized_src_dir = root_dir.as_ref().join( + cfg.get_localized_src_path(build_opts.language_ident.as_ref()) + .unwrap(), + ); + let fallback_src_dir = root_dir.as_ref().join(cfg.get_fallback_src_path()); + + let summary_md = localized_src_dir.join("SUMMARY.md"); let mut summary_content = String::new(); File::open(&summary_md) - .with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", src_dir))? + .with_context(|| format!("Couldn't open SUMMARY.md in {:?} directory", localized_src_dir))? .read_to_string(&mut summary_content)?; let summary = parse_summary(&summary_content) .with_context(|| format!("Summary parsing failed for file={:?}", summary_md))?; if cfg.create_missing { - create_missing(src_dir, &summary).with_context(|| "Unable to create missing chapters")?; + create_missing(localized_src_dir, &summary).with_context(|| "Unable to create missing chapters")?; } - load_book_from_disk(&summary, src_dir) + load_book_from_disk(&summary, localized_src_dir, fallback_src_dir, cfg) } fn create_missing(src_dir: &Path, summary: &Summary) -> Result<()> { @@ -208,9 +218,13 @@ impl Chapter { /// /// You need to pass in the book's source directory because all the links in /// `SUMMARY.md` give the chapter locations relative to it. -pub(crate) fn load_book_from_disk>(summary: &Summary, src_dir: P) -> Result { +pub(crate) fn load_book_from_disk>( + summary: &Summary, + localized_src_dir: P, + fallback_src_dir: P, + cfg: &Config, +) -> Result { debug!("Loading the book from disk"); - let src_dir = src_dir.as_ref(); let prefix = summary.prefix_chapters.iter(); let numbered = summary.numbered_chapters.iter(); @@ -221,7 +235,13 @@ pub(crate) fn load_book_from_disk>(summary: &Summary, src_dir: P) let mut chapters = Vec::new(); for summary_item in summary_items { - let chapter = load_summary_item(summary_item, src_dir, Vec::new())?; + let chapter = load_summary_item( + summary_item, + localized_src_dir.as_ref(), + fallback_src_dir.as_ref(), + Vec::new(), + cfg, + )?; chapters.push(chapter); } @@ -233,13 +253,16 @@ pub(crate) fn load_book_from_disk>(summary: &Summary, src_dir: P) fn load_summary_item + Clone>( item: &SummaryItem, - src_dir: P, + localized_src_dir: P, + fallback_src_dir: P, parent_names: Vec, + cfg: &Config, ) -> Result { match item { SummaryItem::Separator => Ok(BookItem::Separator), SummaryItem::Link(ref link) => { - load_chapter(link, src_dir, parent_names).map(BookItem::Chapter) + load_chapter(link, localized_src_dir, fallback_src_dir, parent_names, cfg) + .map(BookItem::Chapter) } SummaryItem::PartTitle(title) => Ok(BookItem::PartTitle(title.clone())), } @@ -247,20 +270,34 @@ fn load_summary_item + Clone>( fn load_chapter>( link: &Link, - src_dir: P, + localized_src_dir: P, + fallback_src_dir: P, parent_names: Vec, + cfg: &Config, ) -> Result { - let src_dir = src_dir.as_ref(); + let src_dir_localized = localized_src_dir.as_ref(); + let src_dir_fallback = fallback_src_dir.as_ref(); let mut ch = if let Some(ref link_location) = link.location { debug!("Loading {} ({})", link.name, link_location.display()); - let location = if link_location.is_absolute() { + let mut src_dir = src_dir_localized; + let mut location = if link_location.is_absolute() { link_location.clone() } else { src_dir.join(link_location) }; + if !location.exists() && !link_location.is_absolute() { + src_dir = src_dir_fallback; + location = src_dir.join(link_location); + debug!("Falling back to {}", location.display()); + } + if !location.exists() && cfg.build.create_missing { + create_missing(&location, &link) + .with_context(|| "Unable to create missing chapters")?; + } + let mut f = File::open(&location) .with_context(|| format!("Chapter file not found, {}", link_location.display()))?; @@ -290,7 +327,15 @@ fn load_chapter>( let sub_items = link .nested_items .iter() - .map(|i| load_summary_item(i, src_dir, sub_item_parents.clone())) + .map(|i| { + load_summary_item( + i, + src_dir_localized, + src_dir_fallback, + sub_item_parents.clone(), + cfg, + ) + }) .collect::>>()?; ch.sub_items = sub_items; @@ -347,7 +392,7 @@ mod tests { this is some dummy text. And here is some \ - more text. +more text. "; /// Create a dummy `Link` in a temporary directory. @@ -389,6 +434,7 @@ And here is some \ #[test] fn load_a_single_chapter_from_disk() { let (link, temp_dir) = dummy_link(); + let cfg = Config::default(); let should_be = Chapter::new( "Chapter 1", DUMMY_SRC.to_string(), @@ -396,7 +442,7 @@ And here is some \ Vec::new(), ); - let got = load_chapter(&link, temp_dir.path(), Vec::new()).unwrap(); + let got = load_chapter(&link, temp_dir.path(), temp_dir.path(), Vec::new(), &cfg).unwrap(); assert_eq!(got, should_be); } @@ -427,7 +473,7 @@ And here is some \ fn cant_load_a_nonexistent_chapter() { let link = Link::new("Chapter 1", "/foo/bar/baz.md"); - let got = load_chapter(&link, "", Vec::new()); + let got = load_chapter(&link, "", "", Vec::new(), &Config::default()); assert!(got.is_err()); } @@ -444,6 +490,7 @@ And here is some \ parent_names: vec![String::from("Chapter 1")], sub_items: Vec::new(), }; + let cfg = Config::default(); let should_be = BookItem::Chapter(Chapter { name: String::from("Chapter 1"), content: String::from(DUMMY_SRC), @@ -458,7 +505,14 @@ And here is some \ ], }); - let got = load_summary_item(&SummaryItem::Link(root), temp.path(), Vec::new()).unwrap(); + let got = load_summary_item( + &SummaryItem::Link(root), + temp.path(), + temp.path(), + Vec::new(), + &cfg, + ) + .unwrap(); assert_eq!(got, should_be); } @@ -469,6 +523,7 @@ And here is some \ numbered_chapters: vec![SummaryItem::Link(link)], ..Default::default() }; + let cfg = Config::default(); let should_be = Book { sections: vec![BookItem::Chapter(Chapter { name: String::from("Chapter 1"), @@ -480,7 +535,7 @@ And here is some \ ..Default::default() }; - let got = load_book_from_disk(&summary, temp.path()).unwrap(); + let got = load_book_from_disk(&summary, temp.path(), temp.path(), &cfg).unwrap(); assert_eq!(got, should_be); } @@ -611,8 +666,9 @@ And here is some \ ..Default::default() }; + let cfg = Config::default(); - let got = load_book_from_disk(&summary, temp.path()); + let got = load_book_from_disk(&summary, temp.path(), temp.path(), &cfg); assert!(got.is_err()); } @@ -630,8 +686,61 @@ And here is some \ })], ..Default::default() }; + let cfg = Config::default(); + + let got = load_book_from_disk(&summary, temp.path(), temp.path(), &cfg); + assert!(got.is_err()); + } + + #[test] + fn can_load_a_nonexistent_chapter_with_fallback() { + let (_, temp_localized) = dummy_link(); + let chapter_path = temp_localized.path().join("chapter_1.md"); + fs::remove_file(&chapter_path).unwrap(); + + let (_, temp_fallback) = dummy_link(); + + let link_relative = Link::new("Chapter 1", "chapter_1.md"); + + let summary = Summary { + numbered_chapters: vec![SummaryItem::Link(link_relative)], + ..Default::default() + }; + let mut cfg = Config::default(); + cfg.build.create_missing = false; + let should_be = Book { + sections: vec![BookItem::Chapter(Chapter { + name: String::from("Chapter 1"), + content: String::from(DUMMY_SRC), + path: Some(PathBuf::from("chapter_1.md")), + ..Default::default() + })], + ..Default::default() + }; + + let got = load_book_from_disk(&summary, temp_localized.path(), temp_fallback.path(), &cfg) + .unwrap(); + + assert_eq!(got, should_be); + } + + #[test] + fn cannot_load_a_nonexistent_absolute_link_with_fallback() { + let (link_absolute, temp_localized) = dummy_link(); + let chapter_path = temp_localized.path().join("chapter_1.md"); + fs::remove_file(&chapter_path).unwrap(); + + let (_, temp_fallback) = dummy_link(); + + let summary = Summary { + numbered_chapters: vec![SummaryItem::Link(link_absolute)], + ..Default::default() + }; + let mut cfg = Config::default(); + cfg.build.create_missing = false; + + let got = load_book_from_disk(&summary, temp_localized.path(), temp_fallback.path(), &cfg); - let got = load_book_from_disk(&summary, temp.path()); assert!(got.is_err()); } } diff --git a/src/book/mod.rs b/src/book/mod.rs index e1750e14b4..308aa267c1 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -28,8 +28,8 @@ use crate::preprocess::{ use crate::renderer::{CmdRenderer, HtmlHandlebars, MarkdownRenderer, RenderContext, Renderer}; use crate::utils; -use crate::config::{Config, RustEdition}; use crate::build_opts::BuildOpts; +use crate::config::{Config, RustEdition}; /// The object used to manage and build a book. pub struct MDBook { @@ -57,7 +57,10 @@ impl MDBook { /// Load a book from its root directory on disk, passing in options from the /// frontend. - pub fn load_with_build_opts>(book_root: P, build_opts: BuildOpts) -> Result { + pub fn load_with_build_opts>( + book_root: P, + build_opts: BuildOpts, + ) -> Result { let book_root = book_root.into(); let config_location = book_root.join("book.toml"); @@ -90,11 +93,14 @@ impl MDBook { } /// Load a book from its root directory using a custom config. - pub fn load_with_config>(book_root: P, config: Config, build_opts: BuildOpts) -> Result { + pub fn load_with_config>( + book_root: P, + config: Config, + build_opts: BuildOpts, + ) -> Result { let root = book_root.into(); - let src_dir = root.join(&config.book.src); - let book = book::load_book(&src_dir, &config.build)?; + let book = book::load_book(&root, &config, &build_opts)?; let renderers = determine_renderers(&config); let preprocessors = determine_preprocessors(&config)?; @@ -118,8 +124,14 @@ impl MDBook { ) -> Result { let root = book_root.into(); - let src_dir = root.join(&config.book.src); - let book = book::load_book_from_disk(&summary, &src_dir)?; + let localized_src_dir = root.join( + config + .get_localized_src_path(build_opts.language_ident.as_ref()) + .unwrap(), + ); + let fallback_src_dir = root.join(config.get_fallback_src_path()); + let book = + book::load_book_from_disk(&summary, localized_src_dir, fallback_src_dir, &config)?; let renderers = determine_renderers(&config); let preprocessors = determine_preprocessors(&config)?; @@ -256,8 +268,12 @@ impl MDBook { let temp_dir = TempFileBuilder::new().prefix("mdbook-").tempdir()?; // FIXME: Is "test" the proper renderer name to use here? - let preprocess_context = - PreprocessorContext::new(self.root.clone(), self.build_opts.clone(), self.config.clone(), "test".to_string()); + let preprocess_context = PreprocessorContext::new( + self.root.clone(), + self.build_opts.clone(), + self.config.clone(), + "test".to_string(), + ); let book = LinkPreprocessor::new().run(&preprocess_context, self.book.clone())?; // Index Preprocessor is disabled so that chapter paths continue to point to the diff --git a/src/cmd/build.rs b/src/cmd/build.rs index edc3be44ac..90e8a88a3c 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -17,6 +17,11 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { (Defaults to the Current Directory when omitted)'", ) .arg_from_usage("-o, --open 'Opens the compiled book in a web browser'") + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Build command implementation diff --git a/src/cmd/clean.rs b/src/cmd/clean.rs index b58f937ee8..ae736c7977 100644 --- a/src/cmd/clean.rs +++ b/src/cmd/clean.rs @@ -1,4 +1,4 @@ -use crate::get_book_dir; +use crate::{get_book_dir, get_build_opts}; use anyhow::Context; use clap::{App, ArgMatches, SubCommand}; use mdbook::MDBook; @@ -18,12 +18,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { "[dir] 'Root directory for the book{n}\ (Defaults to the Current Directory when omitted)'", ) + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Clean command implementation pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> { let book_dir = get_book_dir(args); - let book = MDBook::load(&book_dir)?; + let build_opts = get_build_opts(args); + let book = MDBook::load_with_build_opts(&book_dir, build_opts)?; let dir_to_remove = match args.value_of("dest-dir") { Some(dest_dir) => dest_dir.into(), diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index c5394f8aae..1ea3214d23 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -1,6 +1,6 @@ #[cfg(feature = "watch")] use super::watch; -use crate::{get_book_dir, open}; +use crate::{get_book_dir, get_build_opts, open}; use clap::{App, Arg, ArgMatches, SubCommand}; use futures_util::sink::SinkExt; use futures_util::StreamExt; @@ -49,12 +49,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { .help("Port to use for HTTP connections"), ) .arg_from_usage("-o, --open 'Opens the book server in a web browser'") + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Serve command implementation pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); - let mut book = MDBook::load(&book_dir)?; + let build_opts = get_build_opts(args); + let mut book = MDBook::load_with_build_opts(&book_dir, build_opts)?; let port = args.value_of("port").unwrap(); let hostname = args.value_of("hostname").unwrap(); diff --git a/src/cmd/test.rs b/src/cmd/test.rs index c52ebfa0cc..0a7c7f57f0 100644 --- a/src/cmd/test.rs +++ b/src/cmd/test.rs @@ -25,6 +25,9 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { .multiple(true) .empty_values(false) .help("A comma-separated list of directories to add to {n}the crate search path when building tests")) + .arg_from_usage("-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'") } // test command implementation diff --git a/src/cmd/watch.rs b/src/cmd/watch.rs index b27516b064..5a724b579b 100644 --- a/src/cmd/watch.rs +++ b/src/cmd/watch.rs @@ -1,4 +1,4 @@ -use crate::{get_book_dir, open}; +use crate::{get_book_dir, get_build_opts, open}; use clap::{App, ArgMatches, SubCommand}; use mdbook::errors::Result; use mdbook::utils; @@ -23,12 +23,18 @@ pub fn make_subcommand<'a, 'b>() -> App<'a, 'b> { (Defaults to the Current Directory when omitted)'", ) .arg_from_usage("-o, --open 'Open the compiled book in a web browser'") + .arg_from_usage( + "-l, --language=[language] 'Language to render the compiled book in.{n}\ + Only valid if the [languages] table in the config is not empty.{n}\ + If omitted, defaults to the language with `default` set to true.'", + ) } // Watch command implementation pub fn execute(args: &ArgMatches) -> Result<()> { let book_dir = get_book_dir(args); - let mut book = MDBook::load(&book_dir)?; + let build_opts = get_build_opts(args); + let mut book = MDBook::load_with_build_opts(&book_dir, build_opts)?; let update_config = |book: &mut MDBook| { if let Some(dest_dir) = args.value_of("dest-dir") { diff --git a/src/config.rs b/src/config.rs index b2c88eb59a..c13fab3b0f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -49,6 +49,7 @@ #![deny(missing_docs)] +use anyhow::anyhow; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashMap; use std::env; @@ -253,27 +254,66 @@ impl Config { } /// Get the source directory of a localized book corresponding to language ident `index`. - pub fn get_localized_src_path>(&self, index: Option) -> Option { + pub fn get_localized_src_path>(&self, index: Option) -> Result { match self.language.default_language() { + // Languages have been specified, assume directory structure with + // language subfolders. Some(default) => match index { // Make sure that the language we passed was actually // declared in the config, and return `None` if not. - Some(lang_ident) => self.language.0.get(lang_ident.as_ref()).map(|_| { + Some(lang_ident) => match self.language.0.get(lang_ident.as_ref()) { + Some(_) => { + let mut buf = PathBuf::new(); + buf.push(self.book.src.clone()); + buf.push(lang_ident.as_ref()); + Ok(buf) + } + None => Err(anyhow!( + "Expected [language.{}] to be declared in book.toml", + lang_ident.as_ref() + )), + }, + // Use the default specified in book.toml. + None => { let mut buf = PathBuf::new(); buf.push(self.book.src.clone()); - buf.push(lang_ident.as_ref()); - buf - }), - // Use the default specified in book.toml. - None => Some(PathBuf::from(default)) - } + buf.push(default); + Ok(buf) + } + }, // No default language was configured in book.toml. Preserve // backwards compatibility by just returning `src`. None => match index { - Some(_) => None, - None => Some(self.book.src.clone()), + // We passed in a language from the frontend, but the config + // offers no languages. + Some(lang_ident) => Err(anyhow!( + "No [language] table in book.toml, expected [language.{}] to be declared", + lang_ident.as_ref() + )), + // Default to previous non-localized behavior. + None => Ok(self.book.src.clone()), + }, + } + } + + /// Get the fallback source directory of a book. For example, if chapters + /// are missing in a localization, the links will gracefully degrade to the + /// files that exist in this directory. + pub fn get_fallback_src_path(&self) -> PathBuf { + match self.language.default_language() { + // Languages have been specified, assume directory structure with + // language subfolders. + Some(default) => { + let mut buf = PathBuf::new(); + buf.push(self.book.src.clone()); + buf.push(default); + buf } + + // No default language was configured in book.toml. Preserve + // backwards compatibility by just returning `src`. + None => self.book.src.clone(), } } @@ -373,14 +413,11 @@ impl<'de> Deserialize<'de> for Config { .unwrap_or_default(); if !language.0.is_empty() { - let default_languages = language.0 - .iter() - .filter(|(_, lang)| lang.default) - .count(); + let default_languages = language.0.iter().filter(|(_, lang)| lang.default).count(); if default_languages != 1 { return Err(D::Error::custom( - "If languages are specified, exactly one must be set as 'default'" + "If languages are specified, exactly one must be set as 'default'", )); } } @@ -752,9 +789,10 @@ pub struct Language { impl LanguageConfig { /// Returns the default language specified in the config. pub fn default_language(&self) -> Option<&String> { - self.0.iter() - .find(|(_, lang)| lang.default) - .map(|(lang_ident, _)| lang_ident) + self.0 + .iter() + .find(|(_, lang)| lang.default) + .map(|(lang_ident, _)| lang_ident) } } @@ -874,8 +912,20 @@ mod tests { ..Default::default() }; let mut language_should_be = LanguageConfig::default(); - language_should_be.0.insert(String::from("en"), Language { name: String::from("English"), default: true }); - language_should_be.0.insert(String::from("fr"), Language { name: String::from("Français"), default: false }); + language_should_be.0.insert( + String::from("en"), + Language { + name: String::from("English"), + default: true, + }, + ); + language_should_be.0.insert( + String::from("fr"), + Language { + name: String::from("Français"), + default: false, + }, + ); let got = Config::from_str(src).unwrap(); diff --git a/src/main.rs b/src/main.rs index f0c9bd3c6f..94c37831dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ use chrono::Local; use clap::{App, AppSettings, Arg, ArgMatches, Shell, SubCommand}; use env_logger::Builder; use log::LevelFilter; -use mdbook::utils; use mdbook::build_opts::BuildOpts; +use mdbook::utils; use std::env; use std::ffi::OsStr; use std::io::Write; @@ -136,7 +136,7 @@ fn get_build_opts(args: &ArgMatches) -> BuildOpts { let language = args.value_of("language"); BuildOpts { - language_ident: language.map(String::from) + language_ident: language.map(String::from), } } diff --git a/src/preprocess/cmd.rs b/src/preprocess/cmd.rs index dbcda528fb..b84af3f665 100644 --- a/src/preprocess/cmd.rs +++ b/src/preprocess/cmd.rs @@ -178,8 +178,8 @@ impl Preprocessor for CmdPreprocessor { #[cfg(test)] mod tests { use super::*; - use crate::MDBook; use crate::build_opts::BuildOpts; + use crate::MDBook; use std::path::Path; fn guide() -> MDBook { diff --git a/src/preprocess/mod.rs b/src/preprocess/mod.rs index 7da381de69..0fdeaa9245 100644 --- a/src/preprocess/mod.rs +++ b/src/preprocess/mod.rs @@ -9,8 +9,8 @@ mod index; mod links; use crate::book::Book; -use crate::config::Config; use crate::build_opts::BuildOpts; +use crate::config::Config; use crate::errors::*; use std::cell::RefCell; @@ -39,7 +39,12 @@ pub struct PreprocessorContext { impl PreprocessorContext { /// Create a new `PreprocessorContext`. - pub(crate) fn new(root: PathBuf, build_opts: BuildOpts, config: Config, renderer: String) -> Self { + pub(crate) fn new( + root: PathBuf, + build_opts: BuildOpts, + config: Config, + renderer: String, + ) -> Self { PreprocessorContext { root, build_opts, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5f41dbae7e..78cca280de 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -25,8 +25,8 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use crate::book::Book; -use crate::config::Config; use crate::build_opts::BuildOpts; +use crate::config::Config; use crate::errors::*; use toml::Value; @@ -75,7 +75,13 @@ pub struct RenderContext { impl RenderContext { /// Create a new `RenderContext`. - pub fn new(root: P, book: Book, build_opts: BuildOpts, config: Config, destination: Q) -> RenderContext + pub fn new( + root: P, + book: Book, + build_opts: BuildOpts, + config: Config, + destination: Q, + ) -> RenderContext where P: Into, Q: Into, diff --git a/tests/localized_book/book.toml b/tests/localized_book/book.toml new file mode 100644 index 0000000000..c513aade36 --- /dev/null +++ b/tests/localized_book/book.toml @@ -0,0 +1,32 @@ +[book] +title = "Localized Book" +description = "Testing mdBook localization features" +authors = ["Ruin0x11"] +language = "en" + +[rust] +edition = "2018" + +[output.html] +mathjax-support = true +site-url = "/mdBook/" + +[output.html.playground] +editable = true +line-numbers = true + +[output.html.search] +limit-results = 20 +use-boolean-and = true +boost-title = 2 +boost-hierarchy = 2 +boost-paragraph = 1 +expand = true +heading-split-level = 2 + +[language.en] +name = "English" +default = true + +[language.ja] +name = "日本語" diff --git a/tests/localized_book/src/en/README.md b/tests/localized_book/src/en/README.md new file mode 100644 index 0000000000..c43c8b584c --- /dev/null +++ b/tests/localized_book/src/en/README.md @@ -0,0 +1,3 @@ +# Localized Book + +This is a test of the book localization features. diff --git a/tests/localized_book/src/en/SUMMARY.md b/tests/localized_book/src/en/SUMMARY.md new file mode 100644 index 0000000000..904cfbf978 --- /dev/null +++ b/tests/localized_book/src/en/SUMMARY.md @@ -0,0 +1,7 @@ +# Summary + +- [README](README.md) +- [Chapter 1](chapter/README.md) + - [Section 1](chapter/1.md) + - [Section 2](chapter/2.md) +- [Untranslated Chapter](untranslated.md) diff --git a/tests/localized_book/src/en/chapter/1.md b/tests/localized_book/src/en/chapter/1.md new file mode 100644 index 0000000000..a0e9a83157 --- /dev/null +++ b/tests/localized_book/src/en/chapter/1.md @@ -0,0 +1,2 @@ +# First section. + diff --git a/tests/localized_book/src/en/chapter/2.md b/tests/localized_book/src/en/chapter/2.md new file mode 100644 index 0000000000..17378866a7 --- /dev/null +++ b/tests/localized_book/src/en/chapter/2.md @@ -0,0 +1,2 @@ +# Second section. + diff --git a/tests/localized_book/src/en/chapter/README.md b/tests/localized_book/src/en/chapter/README.md new file mode 100644 index 0000000000..0809d65017 --- /dev/null +++ b/tests/localized_book/src/en/chapter/README.md @@ -0,0 +1 @@ +# First chapter page. diff --git a/tests/localized_book/src/en/untranslated.md b/tests/localized_book/src/en/untranslated.md new file mode 100644 index 0000000000..08d515ca0d --- /dev/null +++ b/tests/localized_book/src/en/untranslated.md @@ -0,0 +1,3 @@ +# Untranslated chapter. + +This chapter is not available in any translation. If things work correctly, you should see this page written in the fallback language (English) if the other translations list it on their summary page. diff --git a/tests/localized_book/src/ja/README.md b/tests/localized_book/src/ja/README.md new file mode 100644 index 0000000000..77229d775d --- /dev/null +++ b/tests/localized_book/src/ja/README.md @@ -0,0 +1,3 @@ +# 本の翻訳 + +これは本の翻訳のテストです。 diff --git a/tests/localized_book/src/ja/SUMMARY.md b/tests/localized_book/src/ja/SUMMARY.md new file mode 100644 index 0000000000..79938511d3 --- /dev/null +++ b/tests/localized_book/src/ja/SUMMARY.md @@ -0,0 +1,8 @@ +# 目次 + +- [README](README.md) +- [第一章](chapter/README.md) + - [第一節](chapter/1.md) + - [第二節](chapter/2.md) + - [第三節](chapter/3.md) +- [Untranslated Chapter](untranslated.md) diff --git a/tests/localized_book/src/ja/chapter/1.md b/tests/localized_book/src/ja/chapter/1.md new file mode 100644 index 0000000000..eae183f142 --- /dev/null +++ b/tests/localized_book/src/ja/chapter/1.md @@ -0,0 +1 @@ +# 第一節。 diff --git a/tests/localized_book/src/ja/chapter/2.md b/tests/localized_book/src/ja/chapter/2.md new file mode 100644 index 0000000000..f23ac37be9 --- /dev/null +++ b/tests/localized_book/src/ja/chapter/2.md @@ -0,0 +1 @@ +# 第二節。 diff --git a/tests/localized_book/src/ja/chapter/3.md b/tests/localized_book/src/ja/chapter/3.md new file mode 100644 index 0000000000..923550afaa --- /dev/null +++ b/tests/localized_book/src/ja/chapter/3.md @@ -0,0 +1,5 @@ +# 第三節。 + +実は、このページは英語バージョンに存在しません。 + +This page doesn't exist in the English translation. It is unique to this translation only. diff --git a/tests/localized_book/src/ja/chapter/README.md b/tests/localized_book/src/ja/chapter/README.md new file mode 100644 index 0000000000..00e762ff0d --- /dev/null +++ b/tests/localized_book/src/ja/chapter/README.md @@ -0,0 +1 @@ +# 第一章のページ。 diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index ebae94e7be..4a49cbbe17 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -6,8 +6,8 @@ mod dummy_book; use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook}; use anyhow::Context; -use mdbook::config::Config; use mdbook::build_opts::BuildOpts; +use mdbook::config::Config; use mdbook::errors::*; use mdbook::utils::fs::write_file; use mdbook::MDBook;