Update mdBook manual to have information about translations

This commit is contained in:
Ruin0x11 2021-09-15 14:29:30 -07:00
parent 09a8b66e87
commit 5fed5e866d
10 changed files with 139 additions and 40 deletions

View File

@ -14,13 +14,19 @@ up for you:
```bash ```bash
book-test/ book-test/
├── book ├── book
├── book.toml
└── src └── src
├── chapter_1.md └── en
└── SUMMARY.md ├── chapter_1.md
└── SUMMARY.md
``` ```
- The `src` directory is where you write your book in markdown. It contains all - The `src` directory is where you write your book in Markdown. It contains all
the source files, configuration files, etc. the source files for each translation of your book. By default, a directory
for the English translation is created, `src/en`.
- The `book.toml` file holds configuration about how your book gets rendered.
See the [configuration](../format/config.md) section for more details.
- The `book` directory is where your book is rendered. All the output is ready - The `book` directory is where your book is rendered. All the output is ready
to be uploaded to a server to be seen by your audience. to be uploaded to a server to be seen by your audience.

View File

@ -56,7 +56,7 @@ be adapted for other preprocessors.
```rust ```rust
// nop-preprocessors.rs // nop-preprocessors.rs
{{#include ../../../examples/nop-preprocessor.rs}} {{#include ../../../../examples/nop-preprocessor.rs}}
``` ```
</details> </details>

View File

@ -4,9 +4,11 @@ This section details the configuration options available in the ***book.toml***:
- **[General]** configuration including the `book`, `rust`, `build` sections - **[General]** configuration including the `book`, `rust`, `build` sections
- **[Preprocessor]** configuration for default and custom book preprocessors - **[Preprocessor]** configuration for default and custom book preprocessors
- **[Renderer]** configuration for the HTML, Markdown and custom renderers - **[Renderer]** configuration for the HTML, Markdown and custom renderers
- **[Translations]** configuration for books written in more than one language
- **[Environment Variable]** configuration for overriding configuration options in your environment - **[Environment Variable]** configuration for overriding configuration options in your environment
[General]: general.md [General]: general.md
[Preprocessor]: preprocessors.md [Preprocessor]: preprocessors.md
[Renderer]: renderers.md [Renderer]: renderers.md
[Translations]: translations.md
[Environment Variable]: environment-variables.md [Environment Variable]: environment-variables.md

View File

@ -0,0 +1,86 @@
## Translations
It's possible to write your book in more than one language and bundle all of its
translations into a single output folder, with the ability for readers to switch
between each one in the rendered output. The available languages for your book
are defined in the `[language]` table:
```toml
[language.en]
name = "English"
[language.ja]
name = "日本語"
title = "本のサンプル"
description = "この本は実例です。"
authors = ["Ruin0x11"]
```
Each language must have a human-readable `name` defined. Also, if the
`[language]` table is defined, you must define `book.language` to be a key of
this table, which will indicate the language whose files will be used for
fallbacks if a page is missing in a translation.
The `title` and `description` fields, if defined, will override the ones set in
the `[book]` section. This way you can translate the book's title and
description. `authors` provides a list of this translation's authors.
After defining a new language like `[language.ja]`, add a new subdirectory
`src/ja` and create your `SUMMARY.md` and other files there.
> **Note:** Whether or not the `[language]` table is defined changes the format
> of the `src` directory that mdBook expects to see. If there is no `[language]`
> table, mdBook will treat the `src` directory as a single translation of the
> book, with `SUMMARY.md` at the root:
>
> ```
> ├── book.toml
> └── src
> ├── chapter
> │ ├── 1.md
> │ ├── 2.md
> │ └── README.md
> ├── README.md
> └── SUMMARY.md
> ```
>
> If the `[language]` table is defined, mdBook will instead expect to find
> subdirectories under `src` named after the keys in the table:
>
> ```
> ├── book.toml
> └── src
> ├── en
> │ ├── chapter
> │ │ ├── 1.md
> │ │ ├── 2.md
> │ │ └── README.md
> │ ├── README.md
> │ └── SUMMARY.md
> └── ja
> ├── chapter
> │ ├── 1.md
> │ ├── 2.md
> │ └── README.md
> ├── README.md
> └── SUMMARY.md
> ```
If the `[language]` table is used, you can pass the `-l <language id>` argument
to commands like `mdbook build` to build the book for only a single language. In
this example, `<language id>` can be `en` or `ja`.
Some extra notes on translations:
- In a translation's `SUMMARY.md` or inside Markdown files, you can link to
pages, images or other files that don't exist in the current translation, but
do exist in the default translation. This is so you can have a fallback in
case new pages get added in the default language that haven't been translated
yet.
- Each translation can have its own `SUMMARY.md` with differing content from
other translations. Even if the translation's summary goes out of sync with
the default language, the links will continue to work so long as the pages
exist in either translation.
- Each translation can have its own pages listed in `SUMMARY.md` that don't
exist in the default translation at all, in case extra information specific to
that language is needed.

View File

@ -20,5 +20,6 @@ shout-out to them!
- Vivek Akupatni ([apatniv](https://github.com/apatniv)) - Vivek Akupatni ([apatniv](https://github.com/apatniv))
- Eric Huss ([ehuss](https://github.com/ehuss)) - Eric Huss ([ehuss](https://github.com/ehuss))
- Josh Rotenberg ([joshrotenberg](https://github.com/joshrotenberg)) - Josh Rotenberg ([joshrotenberg](https://github.com/joshrotenberg))
- [Ruin0x11](https://github.com/Ruin0x11)
If you feel you're missing from this list, feel free to add yourself in a PR. If you feel you're missing from this list, feel free to add yourself in a PR.

View File

@ -1020,6 +1020,7 @@ mod tests {
assert_eq!(got.rust, rust_should_be); assert_eq!(got.rust, rust_should_be);
assert_eq!(got.html_config().unwrap(), html_should_be); assert_eq!(got.html_config().unwrap(), html_should_be);
assert_eq!(got.language, language_should_be); assert_eq!(got.language, language_should_be);
assert_eq!(got.default_language(), Some(String::from("ja")));
} }
#[test] #[test]
@ -1369,7 +1370,18 @@ mod tests {
#[test] #[test]
#[should_panic(expected = "Invalid configuration file")] #[should_panic(expected = "Invalid configuration file")]
fn validate_config_default_language_must_exist_in_languages_table() { fn book_language_without_languages_table() {
let src = r#"
[book]
language = "en"
"#;
Config::from_str(src).unwrap();
}
#[test]
#[should_panic(expected = "Invalid configuration file")]
fn default_language_must_exist_in_languages_table() {
let src = r#" let src = r#"
[language.ja] [language.ja]
name = "日本語" name = "日本語"

View File

@ -10,7 +10,7 @@ use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Write; use std::fmt::Write;
use std::path::PathBuf; use std::path::{Path, PathBuf};
pub use self::string::{ pub use self::string::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines, take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
@ -99,9 +99,9 @@ pub fn id_from_content(content: &str) -> String {
normalize_id(trimmed) normalize_id(trimmed)
} }
fn rewrite_if_missing( fn rewrite_if_missing<P: AsRef<Path>>(
fixed_link: &mut String, fixed_link: &mut String,
path_to_dest: &PathBuf, path_to_dest: P,
dest: &str, dest: &str,
src_dir: &PathBuf, src_dir: &PathBuf,
language: &str, language: &str,
@ -114,7 +114,7 @@ fn rewrite_if_missing(
// if the file exists. // if the file exists.
let mut path_on_disk = src_dir.clone(); let mut path_on_disk = src_dir.clone();
path_on_disk.push(language); path_on_disk.push(language);
path_on_disk.push(path_to_dest); path_on_disk.push(path_to_dest.as_ref());
path_on_disk.push(dest); path_on_disk.push(dest);
debug!("Checking if {} exists", path_on_disk.display()); debug!("Checking if {} exists", path_on_disk.display());
@ -122,7 +122,7 @@ fn rewrite_if_missing(
// Now see if the file exists in the fallback language directory (like "src/en"). // Now see if the file exists in the fallback language directory (like "src/en").
let mut fallback_path = src_dir.clone(); let mut fallback_path = src_dir.clone();
fallback_path.push(fallback_language); fallback_path.push(fallback_language);
fallback_path.push(path_to_dest); fallback_path.push(path_to_dest.as_ref());
fallback_path.push(dest); fallback_path.push(dest);
debug!( debug!(
@ -133,7 +133,7 @@ fn rewrite_if_missing(
// We can fall back to this link. Get enough parent directories to // We can fall back to this link. Get enough parent directories to
// reach the root source directory, append the fallback language // reach the root source directory, append the fallback language
// directory to it, the prepend the whole thing to the link. // directory to it, the prepend the whole thing to the link.
let mut relative_path = PathBuf::from(path_to_dest); let mut relative_path = PathBuf::from(path_to_dest.as_ref());
relative_path.push(dest); relative_path.push(dest);
let mut path_to_fallback_src = fs::path_to_root(&relative_path); let mut path_to_fallback_src = fs::path_to_root(&relative_path);
@ -158,7 +158,6 @@ fn fix<'a>(dest: CowStr<'a>, ctx: Option<&RenderMarkdownContext>) -> CowStr<'a>
if base.ends_with(".md") { if base.ends_with(".md") {
base.replace_range(base.len() - 3.., ".html"); base.replace_range(base.len() - 3.., ".html");
} }
info!("{:?} {:?}", base, dest);
return format!("{}{}", base, dest).into(); return format!("{}{}", base, dest).into();
} else { } else {
return dest; return dest;
@ -173,6 +172,8 @@ fn fix<'a>(dest: CowStr<'a>, ctx: Option<&RenderMarkdownContext>) -> CowStr<'a>
let mut fixed_link = String::new(); let mut fixed_link = String::new();
if let Some(ctx) = ctx { if let Some(ctx) = ctx {
let base = ctx.path.parent().expect("path can't be empty");
// If the book is multilingual, check if the file actually // If the book is multilingual, check if the file actually
// exists, and if not rewrite the link to the fallback // exists, and if not rewrite the link to the fallback
// language's page. // language's page.
@ -180,7 +181,7 @@ fn fix<'a>(dest: CowStr<'a>, ctx: Option<&RenderMarkdownContext>) -> CowStr<'a>
if let Some(fallback_language) = &ctx.fallback_language { if let Some(fallback_language) = &ctx.fallback_language {
rewrite_if_missing( rewrite_if_missing(
&mut fixed_link, &mut fixed_link,
&ctx.path, &base,
&dest, &dest,
&ctx.src_dir, &ctx.src_dir,
&language, &language,
@ -190,13 +191,7 @@ fn fix<'a>(dest: CowStr<'a>, ctx: Option<&RenderMarkdownContext>) -> CowStr<'a>
} }
if ctx.prepend_parent { if ctx.prepend_parent {
let base = ctx let base = base.to_str().expect("utf-8 paths only");
.path
.parent()
.expect("path can't be empty")
.to_str()
.expect("utf-8 paths only");
if !base.is_empty() { if !base.is_empty() {
write!(fixed_link, "{}/", base).unwrap(); write!(fixed_link, "{}/", base).unwrap();
} }
@ -562,27 +557,22 @@ more text with spaces
); );
}; };
test("../b/summary.md", "a", true, "../b/summary.html"); test("../b/summary.md", "a.md", true, "../b/summary.html");
test("../b/summary.md", "a", false, "../../en/../b/summary.html"); test(
test("../c/summary.md", "a/b", true, "../c/summary.html"); "../b/summary.md",
"a.md",
false,
"../../en/../b/summary.html",
);
test("../c/summary.md", "a/b.md", true, "../c/summary.html");
test( test(
"../c/summary.md", "../c/summary.md",
"a/b", "a/b.md",
false, false,
"../../../en/../c/summary.html", "../../../en/../c/summary.html",
); );
test( test("#translations", "config.md", true, "#translations");
"#translations", test("#translations", "config.md", false, "#translations");
"config.md",
true,
"#translations",
);
test(
"#translations",
"config.md",
false,
"#translations",
);
} }
} }

View File

@ -1,3 +1,5 @@
# Localized Book # Localized Book
This is a test of the book localization features. This is a test of the book localization features.
Select a language from the dropdown to see a translation of the current page.

View File

@ -5,5 +5,5 @@
- [第一節](chapter/1.md) - [第一節](chapter/1.md)
- [第二節](chapter/2.md) - [第二節](chapter/2.md)
- [Untranslated Page](untranslated-page.md) - [Untranslated Page](untranslated-page.md)
- [日本語専用のページ](translation-local-page.md)
- [内部リンクの入れ替え](inline-link-fallbacks.md) - [内部リンクの入れ替え](inline-link-fallbacks.md)
- [日本語専用のページ](translation-local-page.md)

View File

@ -8,7 +8,7 @@ If inline link substitution works, then an image should appear below, sourced fr
Here is an [inline link](translation-local-page.md) to an existing page in this translation. Here is an [inline link](translation-local-page.md) to an existing page in this translation.
Here is an [inline link](missing-summary-chapter.md) to a page missing from this translation's SUMMARY.md. It should have been modified to point to the page in the English version of the book. Here is an [inline link](missing-summary-chapter.md) to a page missing from this translation's `SUMMARY.md`. It should have been modified to point to the page in the English version of the book.
Also, here is an [inline link](blah.md) to a page missing from both translations. It should point to this language's 404 page. Also, here is an [inline link](blah.md) to a page missing from both translations. It should point to this language's 404 page.