From 065a04570e6ac3b4b420274595bf56e0a99d6ced Mon Sep 17 00:00:00 2001 From: dalance Date: Sat, 4 May 2024 23:27:27 +0900 Subject: [PATCH] Export gettext preprocessor --- i18n-helpers/Cargo.toml | 2 +- i18n-helpers/src/bin/mdbook-gettext.rs | 72 +++--------------- i18n-helpers/src/lib.rs | 11 +-- i18n-helpers/src/preprocessors.rs | 2 + i18n-helpers/src/preprocessors/gettext.rs | 91 +++++++++++++++++++++++ 5 files changed, 112 insertions(+), 66 deletions(-) create mode 100644 i18n-helpers/src/preprocessors.rs create mode 100644 i18n-helpers/src/preprocessors/gettext.rs diff --git a/i18n-helpers/Cargo.toml b/i18n-helpers/Cargo.toml index 92ffac73..50b7190b 100644 --- a/i18n-helpers/Cargo.toml +++ b/i18n-helpers/Cargo.toml @@ -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" diff --git a/i18n-helpers/src/bin/mdbook-gettext.rs b/i18n-helpers/src/bin/mdbook-gettext.rs index a4230e6b..2917fb35 100644 --- a/i18n-helpers/src/bin/mdbook-gettext.rs +++ b/i18n-helpers/src/bin/mdbook-gettext.rs @@ -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 { - 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 { - 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)] @@ -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)?; @@ -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); } } diff --git a/i18n-helpers/src/lib.rs b/i18n-helpers/src/lib.rs index fd4919ec..43ded13b 100644 --- a/i18n-helpers/src/lib.rs +++ b/i18n-helpers/src/lib.rs @@ -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. @@ -91,7 +92,7 @@ pub fn new_cmark_parser<'input, 'callback>( /// ``` pub fn extract_events<'a>(text: &'a str, state: Option>) -> 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) @@ -383,7 +384,7 @@ pub fn group_events<'a>(events: &'a [(usize, Event<'a>)]) -> Vec> { } /// 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, [ @@ -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>, GroupingContext) { let is_translate = match events { @@ -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>, GroupingContext) { // Language detection from language identifier of codeblock. @@ -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>, ) -> (String, State<'static>) { let events = group.iter().map(|(_, event)| event); diff --git a/i18n-helpers/src/preprocessors.rs b/i18n-helpers/src/preprocessors.rs new file mode 100644 index 00000000..88aa1328 --- /dev/null +++ b/i18n-helpers/src/preprocessors.rs @@ -0,0 +1,2 @@ +pub mod gettext; +pub use gettext::Gettext; diff --git a/i18n-helpers/src/preprocessors/gettext.rs b/i18n-helpers/src/preprocessors/gettext.rs new file mode 100644 index 00000000..10e2e510 --- /dev/null +++ b/i18n-helpers/src/preprocessors/gettext.rs @@ -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 { + 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 { + 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 { + 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" + } +}