Skip to content

Commit 9234e00

Browse files
committed
Add gettext command to generate translated output
This command is the second part of a Gettext-based translation (i18n) workflow. It takes an `xx.po` file with translations and uses this to translate the chapters of the book. Paragraphs without a translation are kept in the original language. Part of the solution for #5.
1 parent 5b96336 commit 9234e00

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

src/cmd/gettext.rs

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
use crate::cmd::xgettext::extract_paragraphs;
2+
use crate::get_book_dir;
3+
use crate::utils;
4+
use clap::{arg, App, Arg, ArgMatches};
5+
use mdbook::MDBook;
6+
use polib::po_file::parse;
7+
use std::path::Path;
8+
9+
// Create clap subcommand arguments
10+
pub fn make_subcommand<'help>() -> App<'help> {
11+
App::new("gettext")
12+
.about("Output translated book")
13+
.arg(
14+
Arg::new("dest-dir")
15+
.short('d')
16+
.long("dest-dir")
17+
.value_name("dest-dir")
18+
.help(
19+
"Output directory for the translated book{n}\
20+
Relative paths are interpreted relative to the book's root directory{n}\
21+
If omitted, mdBook defaults to `./src/xx` where `xx` is the language of the PO file."
22+
),
23+
)
24+
.arg(arg!(<po> "PO file to generate translation for"))
25+
.arg(arg!([dir]
26+
"Root directory for the book{n}\
27+
(Defaults to the Current Directory when omitted)"
28+
))
29+
}
30+
31+
// Gettext command implementation
32+
pub fn execute(args: &ArgMatches) -> mdbook::errors::Result<()> {
33+
let book_dir = get_book_dir(args);
34+
let book = MDBook::load(&book_dir)?;
35+
36+
let po_file = Path::new(args.value_of("po").unwrap());
37+
let lang = po_file.file_stem().unwrap();
38+
let catalog = parse(po_file).unwrap();
39+
let dest_dir = book.root.join(
40+
args.value_of("dest-dir")
41+
.map(|path| path.into())
42+
.unwrap_or(Path::new("src").join(lang)),
43+
);
44+
45+
for item in book.iter() {
46+
match item {
47+
mdbook::BookItem::Chapter(chapter) if !chapter.is_draft_chapter() => {
48+
let mut output = String::with_capacity(chapter.content.len());
49+
let mut current_lineno = 1;
50+
51+
for (lineno, paragraph) in extract_paragraphs(&chapter.content) {
52+
// Fill in blank lines between paragraphs. This is
53+
// important for code blocks where blank lines can
54+
// be significant.
55+
while current_lineno < lineno {
56+
output.push('\n');
57+
current_lineno += 1;
58+
}
59+
current_lineno += paragraph.lines().count();
60+
61+
let translated = catalog
62+
.find_message(paragraph)
63+
.and_then(|msg| msg.get_msgstr().ok())
64+
.filter(|msgstr| !msgstr.is_empty())
65+
.map(|msgstr| msgstr.as_str())
66+
.unwrap_or(paragraph);
67+
output.push_str(translated);
68+
output.push('\n');
69+
}
70+
71+
utils::fs::write_file(
72+
&dest_dir,
73+
chapter.source_path.as_ref().unwrap(),
74+
output.as_bytes(),
75+
)?;
76+
}
77+
_ => {}
78+
}
79+
}
80+
81+
Ok(())
82+
}

src/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ fn main() {
3535
Some(("serve", sub_matches)) => cmd::serve::execute(sub_matches),
3636
Some(("test", sub_matches)) => cmd::test::execute(sub_matches),
3737
Some(("xgettext", sub_matches)) => cmd::xgettext::execute(sub_matches),
38+
Some(("gettext", sub_matches)) => cmd::gettext::execute(sub_matches),
3839
Some(("completions", sub_matches)) => (|| {
3940
let shell: Shell = sub_matches
4041
.value_of("shell")
@@ -78,6 +79,7 @@ fn create_clap_app() -> App<'static> {
7879
.subcommand(cmd::test::make_subcommand())
7980
.subcommand(cmd::clean::make_subcommand())
8081
.subcommand(cmd::xgettext::make_subcommand())
82+
.subcommand(cmd::gettext::make_subcommand())
8183
.subcommand(
8284
App::new("completions")
8385
.about("Generate shell completions for your shell to stdout")

0 commit comments

Comments
 (0)