Skip to content

Commit

Permalink
Update mdBook manual to have information about translations
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruin0x11 committed Sep 15, 2021
1 parent 09a8b66 commit 5fed5e8
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 39 deletions.
14 changes: 10 additions & 4 deletions guide/src/en/cli/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ up for you:
```bash
book-test/
├── book
├── book.toml
└── src
├── chapter_1.md
└── SUMMARY.md
└── en
├── chapter_1.md
└── SUMMARY.md
```

- The `src` directory is where you write your book in markdown. It contains all
the source files, configuration files, etc.
- The `src` directory is where you write your book in Markdown. It contains all
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
to be uploaded to a server to be seen by your audience.
Expand Down
2 changes: 1 addition & 1 deletion guide/src/en/for_developers/preprocessors.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ be adapted for other preprocessors.
```rust
// nop-preprocessors.rs

{{#include ../../../examples/nop-preprocessor.rs}}
{{#include ../../../../examples/nop-preprocessor.rs}}
```
</details>

Expand Down
4 changes: 3 additions & 1 deletion guide/src/en/format/configuration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ This section details the configuration options available in the ***book.toml***:
- **[General]** configuration including the `book`, `rust`, `build` sections
- **[Preprocessor]** configuration for default and custom book preprocessors
- **[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

[General]: general.md
[Preprocessor]: preprocessors.md
[Renderer]: renderers.md
[Environment Variable]: environment-variables.md
[Translations]: translations.md
[Environment Variable]: environment-variables.md
86 changes: 86 additions & 0 deletions guide/src/en/format/configuration/translations.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions guide/src/en/misc/contributors.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ shout-out to them!
- Vivek Akupatni ([apatniv](https://github.com/apatniv))
- Eric Huss ([ehuss](https://github.com/ehuss))
- 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.
14 changes: 13 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,7 @@ mod tests {
assert_eq!(got.rust, rust_should_be);
assert_eq!(got.html_config().unwrap(), html_should_be);
assert_eq!(got.language, language_should_be);
assert_eq!(got.default_language(), Some(String::from("ja")));
}

#[test]
Expand Down Expand Up @@ -1369,7 +1370,18 @@ mod tests {

#[test]
#[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#"
[language.ja]
name = "日本語"
Expand Down
50 changes: 20 additions & 30 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};

use std::borrow::Cow;
use std::fmt::Write;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

pub use self::string::{
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
Expand Down Expand Up @@ -99,9 +99,9 @@ pub fn id_from_content(content: &str) -> String {
normalize_id(trimmed)
}

fn rewrite_if_missing(
fn rewrite_if_missing<P: AsRef<Path>>(
fixed_link: &mut String,
path_to_dest: &PathBuf,
path_to_dest: P,
dest: &str,
src_dir: &PathBuf,
language: &str,
Expand All @@ -114,15 +114,15 @@ fn rewrite_if_missing(
// if the file exists.
let mut path_on_disk = src_dir.clone();
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);

debug!("Checking if {} exists", path_on_disk.display());
if !path_on_disk.exists() {
// Now see if the file exists in the fallback language directory (like "src/en").
let mut fallback_path = src_dir.clone();
fallback_path.push(fallback_language);
fallback_path.push(path_to_dest);
fallback_path.push(path_to_dest.as_ref());
fallback_path.push(dest);

debug!(
Expand All @@ -133,7 +133,7 @@ fn rewrite_if_missing(
// We can fall back to this link. Get enough parent directories to
// reach the root source directory, append the fallback language
// 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);

let mut path_to_fallback_src = fs::path_to_root(&relative_path);
Expand All @@ -158,7 +158,6 @@ fn fix<'a>(dest: CowStr<'a>, ctx: Option<&RenderMarkdownContext>) -> CowStr<'a>
if base.ends_with(".md") {
base.replace_range(base.len() - 3.., ".html");
}
info!("{:?} {:?}", base, dest);
return format!("{}{}", base, dest).into();
} else {
return dest;
Expand All @@ -173,14 +172,16 @@ fn fix<'a>(dest: CowStr<'a>, ctx: Option<&RenderMarkdownContext>) -> CowStr<'a>
let mut fixed_link = String::new();

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
// exists, and if not rewrite the link to the fallback
// language's page.
if let Some(language) = &ctx.language {
if let Some(fallback_language) = &ctx.fallback_language {
rewrite_if_missing(
&mut fixed_link,
&ctx.path,
&base,
&dest,
&ctx.src_dir,
&language,
Expand All @@ -190,13 +191,7 @@ fn fix<'a>(dest: CowStr<'a>, ctx: Option<&RenderMarkdownContext>) -> CowStr<'a>
}

if ctx.prepend_parent {
let base = ctx
.path
.parent()
.expect("path can't be empty")
.to_str()
.expect("utf-8 paths only");

let base = base.to_str().expect("utf-8 paths only");
if !base.is_empty() {
write!(fixed_link, "{}/", base).unwrap();
}
Expand Down Expand Up @@ -562,27 +557,22 @@ more text with spaces
);
};

test("../b/summary.md", "a", true, "../b/summary.html");
test("../b/summary.md", "a", false, "../../en/../b/summary.html");
test("../c/summary.md", "a/b", true, "../c/summary.html");
test("../b/summary.md", "a.md", true, "../b/summary.html");
test(
"../c/summary.md",
"a/b",
"../b/summary.md",
"a.md",
false,
"../../../en/../c/summary.html",
"../../en/../b/summary.html",
);
test("../c/summary.md", "a/b.md", true, "../c/summary.html");
test(
"#translations",
"config.md",
true,
"#translations",
);
test(
"#translations",
"config.md",
"../c/summary.md",
"a/b.md",
false,
"#translations",
"../../../en/../c/summary.html",
);
test("#translations", "config.md", true, "#translations");
test("#translations", "config.md", false, "#translations");
}
}

Expand Down
2 changes: 2 additions & 0 deletions tests/localized_book/src/en/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Localized Book

This is a test of the book localization features.

Select a language from the dropdown to see a translation of the current page.
2 changes: 1 addition & 1 deletion tests/localized_book/src/ja/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
- [第一節](chapter/1.md)
- [第二節](chapter/2.md)
- [Untranslated Page](untranslated-page.md)
- [日本語専用のページ](translation-local-page.md)
- [内部リンクの入れ替え](inline-link-fallbacks.md)
- [日本語専用のページ](translation-local-page.md)
2 changes: 1 addition & 1 deletion tests/localized_book/src/ja/inline-link-fallbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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](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.

Expand Down

0 comments on commit 5fed5e8

Please sign in to comment.