Skip to content

Commit

Permalink
Export gettext preprocessor
Browse files Browse the repository at this point in the history
  • Loading branch information
dalance committed May 14, 2024
1 parent 39823b5 commit b8b7f91
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 66 deletions.
2 changes: 1 addition & 1 deletion i18n-helpers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ mdbook.workspace = true
polib.workspace = true
pulldown-cmark = { version = "0.9.2", default-features = false }
pulldown-cmark-to-cmark = "11.2.0"
regex = "1.10.4"
regex = "1.9"
semver = "1.0.22"
serde_json.workspace = true
syntect = "5.2.0"
Expand Down
72 changes: 12 additions & 60 deletions i18n-helpers/src/bin/mdbook-gettext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,63 +24,14 @@
//! PO files. If the PO file is not found, you'll get the untranslated
//! book.

use anyhow::{anyhow, Context};
use mdbook::preprocess::{CmdPreprocessor, PreprocessorContext};
use mdbook_i18n_helpers::gettext::{add_stripped_summary_translations, translate_book};
use polib::catalog::Catalog;
use polib::po_file;
use mdbook::preprocess::{CmdPreprocessor, Preprocessor};
use mdbook_i18n_helpers::preprocessors::Gettext;
use semver::{Version, VersionReq};
use std::path::PathBuf;
use std::{io, process};

/// Check whether the book should be transalted.
///
/// The book should be translated if:
/// * `book.language` is defined in mdbook config
/// * Corresponding {language}.po defined
fn should_translate(ctx: &PreprocessorContext) -> bool {
// Translation is a no-op when the target language is not set
if ctx.config.book.language.is_none() {
return false;
}

// Nothing to do if PO file is missing.
get_catalog_path(ctx)
.map(|path| path.try_exists().unwrap_or(false))
.unwrap_or(false)
}

/// Compute the path of the Catalog file.
fn get_catalog_path(ctx: &PreprocessorContext) -> anyhow::Result<PathBuf> {
let language = ctx
.config
.book
.language
.as_ref()
.ok_or_else(|| anyhow!("Language is not provided"))?;

let cfg = ctx
.config
.get_preprocessor("gettext")
.ok_or_else(|| anyhow!("Could not read preprocessor.gettext configuration"))?;
let po_dir = cfg.get("po-dir").and_then(|v| v.as_str()).unwrap_or("po");
Ok(ctx.root.join(po_dir).join(format!("{language}.po")))
}

/// Load the catalog with translation strings.
fn load_catalog(ctx: &PreprocessorContext) -> anyhow::Result<Catalog> {
let path = get_catalog_path(ctx)?;

let catalog = po_file::parse(&path)
.map_err(|err| anyhow!("{err}"))
.with_context(|| format!("Could not parse {path:?} as PO file"))?;

Ok(catalog)
}

/// Execute main logic by this mdbook preprocessor.
fn preprocess() -> anyhow::Result<()> {
let (ctx, mut book) = CmdPreprocessor::parse_input(io::stdin())?;
let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?;
let book_version = Version::parse(&ctx.mdbook_version)?;
let version_req = VersionReq::parse(mdbook::MDBOOK_VERSION)?;
#[allow(clippy::print_stderr)]
Expand All @@ -93,11 +44,8 @@ fn preprocess() -> anyhow::Result<()> {
);
}

if should_translate(&ctx) {
let mut catalog = load_catalog(&ctx)?;
add_stripped_summary_translations(&mut catalog);
translate_book(&catalog, &mut book);
}
let gettext = Gettext;
let book = gettext.run(&ctx, book)?;

serde_json::to_writer(io::stdout(), &book)?;

Expand All @@ -107,10 +55,14 @@ fn preprocess() -> anyhow::Result<()> {
fn main() -> anyhow::Result<()> {
if std::env::args().len() == 3 {
assert_eq!(std::env::args().nth(1).as_deref(), Some("supports"));
if let Some("xgettext") = std::env::args().nth(2).as_deref() {
process::exit(1)
if let Some(renderer) = std::env::args().nth(2).as_deref() {
let gettext = Gettext;
if gettext.supports_renderer(renderer) {
process::exit(0)
} else {
process::exit(1)
}
} else {
// Signal that we support all other renderers.
process::exit(0);
}
}
Expand Down
11 changes: 6 additions & 5 deletions i18n-helpers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use syntect::parsing::{ParseState, Scope, ScopeStack, SyntaxSet};
pub mod directives;
pub mod gettext;
pub mod normalize;
pub mod preprocessors;
pub mod xgettext;

/// Re-wrap the sources field of a message.
Expand Down Expand Up @@ -91,7 +92,7 @@ pub fn new_cmark_parser<'input, 'callback>(
/// ```
pub fn extract_events<'a>(text: &'a str, state: Option<State<'static>>) -> Vec<(usize, Event<'a>)> {
// Expand a `[foo]` style link into `[foo][foo]`.
fn expand_shortcut_link(tag: Tag) -> Tag {
fn expand_shortcut_link(tag: Tag<'_>) -> Tag<'_> {
match tag {
Tag::Link(LinkType::Shortcut, reference, title) => {
Tag::Link(LinkType::Reference, reference, title)
Expand Down Expand Up @@ -383,7 +384,7 @@ pub fn group_events<'a>(events: &'a [(usize, Event<'a>)]) -> Vec<Group<'a>> {
}

/// Returns true if the events appear to be a codeblock.
fn is_codeblock_group(events: &[(usize, Event)]) -> bool {
fn is_codeblock_group(events: &[(usize, Event<'_>)]) -> bool {
matches!(
events,
[
Expand All @@ -406,7 +407,7 @@ fn is_translate_scope(x: Scope) -> bool {

/// Creates groups by checking codeblock with heuristic way.
fn heuristic_codeblock<'a>(
events: &'a [(usize, Event)],
events: &'a [(usize, Event<'_>)],
mut ctx: GroupingContext,
) -> (Vec<Group<'a>>, GroupingContext) {
let is_translate = match events {
Expand Down Expand Up @@ -435,7 +436,7 @@ fn heuristic_codeblock<'a>(

/// Creates groups by parsing codeblock.
fn parse_codeblock<'a>(
events: &'a [(usize, Event)],
events: &'a [(usize, Event<'_>)],
mut ctx: GroupingContext,
) -> (Vec<Group<'a>>, GroupingContext) {
// Language detection from language identifier of codeblock.
Expand Down Expand Up @@ -582,7 +583,7 @@ fn extract_trailing_whitespaces<'a>(buf: &mut Vec<(usize, Event<'a>)>) -> Vec<(u
/// match the [Google developer documentation style
/// guide](https://developers.google.com/style/text-formatting).
pub fn reconstruct_markdown(
group: &[(usize, Event)],
group: &[(usize, Event<'_>)],
state: Option<State<'static>>,
) -> (String, State<'static>) {
let events = group.iter().map(|(_, event)| event);
Expand Down
2 changes: 2 additions & 0 deletions i18n-helpers/src/preprocessors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod gettext;
pub use gettext::Gettext;
91 changes: 91 additions & 0 deletions i18n-helpers/src/preprocessors/gettext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::gettext::{add_stripped_summary_translations, translate_book};
use anyhow::{anyhow, Context};
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use polib::catalog::Catalog;
use polib::po_file;
use std::path::PathBuf;

/// Check whether the book should be transalted.
///
/// The book should be translated if:
/// * `book.language` is defined in mdbook config
/// * Corresponding {language}.po defined
fn should_translate(ctx: &PreprocessorContext) -> bool {
// Translation is a no-op when the target language is not set
if ctx.config.book.language.is_none() {
return false;
}

// Nothing to do if PO file is missing.
get_catalog_path(ctx)
.map(|path| path.try_exists().unwrap_or(false))
.unwrap_or(false)
}

/// Compute the path of the Catalog file.
fn get_catalog_path(ctx: &PreprocessorContext) -> anyhow::Result<PathBuf> {
let language = ctx
.config
.book
.language
.as_ref()
.ok_or_else(|| anyhow!("Language is not provided"))?;

let cfg = ctx
.config
.get_preprocessor("gettext")
.ok_or_else(|| anyhow!("Could not read preprocessor.gettext configuration"))?;
let po_dir = cfg.get("po-dir").and_then(|v| v.as_str()).unwrap_or("po");
Ok(ctx.root.join(po_dir).join(format!("{language}.po")))
}

/// Load the catalog with translation strings.
fn load_catalog(ctx: &PreprocessorContext) -> anyhow::Result<Catalog> {
let path = get_catalog_path(ctx)?;

let catalog = po_file::parse(&path)
.map_err(|err| anyhow!("{err}"))
.with_context(|| format!("Could not parse {path:?} as PO file"))?;

Ok(catalog)
}

/// Preprocessor for gettext
pub struct Gettext;

impl Preprocessor for Gettext {
fn name(&self) -> &str {
"gettext"
}

fn run(
&self,
ctx: &PreprocessorContext,
mut book: mdbook::book::Book,
) -> anyhow::Result<mdbook::book::Book> {
if should_translate(&ctx) {
let mut catalog = load_catalog(&ctx)?;
add_stripped_summary_translations(&mut catalog);
translate_book(&catalog, &mut book);
}
Ok(book)
}

fn supports_renderer(&self, renderer: &str) -> bool {
renderer != "xgettext"
}
}

0 comments on commit b8b7f91

Please sign in to comment.