Skip to content

Commit

Permalink
Implements a comment directive to skip the next translation group.
Browse files Browse the repository at this point in the history
This adds support for adding a comment of the form: `<!-- mdbook-xgettext: skip -->`.  This will cause the system to skip the next message group that would otherwise be translated.

It adds a dependency to the regex crate to match for the comment skip
pattern.
  • Loading branch information
dyoo committed Aug 30, 2023
1 parent 540ba32 commit e735b8e
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 12 deletions.
13 changes: 7 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mdbook = { version = "0.4.25", default-features = false }
polib = "0.2.0"
pulldown-cmark = { version = "0.9.2", default-features = false }
pulldown-cmark-to-cmark = "10.0.4"
regex = "1.9.4"
semver = "1.0.16"
serde_json = "1.0.91"

Expand Down
152 changes: 146 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
use polib::catalog::Catalog;
use pulldown_cmark::{Event, LinkType, Tag};
use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options, State};
use regex::Regex;
use std::sync::OnceLock;

/// Like `mdbook::utils::new_cmark_parser`, but also passes a
/// `BrokenLinkCallback`.
Expand Down Expand Up @@ -190,15 +192,28 @@ pub fn group_events<'a>(events: &'a [(usize, Event<'a>)]) -> Vec<Group<'a>> {
}

impl State {
fn into_group<'a>(self, idx: usize, events: &'a [(usize, Event<'a>)]) -> Group<'a> {
fn into_group<'a>(
self,
idx: usize,
events: &'a [(usize, Event<'a>)],
skip_next_group: &mut bool,
) -> Group<'a> {
match self {
State::Translate(start) => Group::Translate(&events[start..idx]),
State::Translate(start) => {
if *skip_next_group {
*skip_next_group = false;
Group::Skip(&events[start..idx])
} else {
Group::Translate(&events[start..idx])
}
}
State::Skip(start) => Group::Skip(&events[start..idx]),
}
}
}

let mut state = State::Skip(0);
let mut skip_next_group = false;

for (idx, (_, event)) in events.iter().enumerate() {
match event {
Expand All @@ -207,13 +222,14 @@ pub fn group_events<'a>(events: &'a [(usize, Event<'a>)]) -> Vec<Group<'a>> {
// make the group self-contained.
Event::Start(Tag::Paragraph | Tag::CodeBlock(..)) => {
// A translatable group starts here.
groups.push(state.into_group(idx, events));
groups.push(state.into_group(idx, events, &mut skip_next_group));

state = State::Translate(idx);
}
Event::End(Tag::Paragraph | Tag::CodeBlock(..)) => {
// A translatable group ends after `idx`.
let idx = idx + 1;
groups.push(state.into_group(idx, events));
groups.push(state.into_group(idx, events, &mut skip_next_group));
state = State::Skip(idx);
}

Expand All @@ -231,12 +247,24 @@ pub fn group_events<'a>(events: &'a [(usize, Event<'a>)]) -> Vec<Group<'a>> {
| Event::HardBreak => {
// If we're currently skipping, then a new
// translatable group starts here.
if let State::Skip(start) = state {
groups.push(Group::Skip(&events[start..idx]));
if let State::Skip(_) = state {
groups.push(state.into_group(idx, events, &mut skip_next_group));

state = State::Translate(idx);
}
}

// An HTML comment directive to skip the next translation group.
Event::Html(s) if is_comment_skip_directive(s) => {
// If in the middle of translation, finish it.
if let State::Translate(_) = state {
groups.push(state.into_group(idx, events, &mut skip_next_group));
state = State::Skip(idx);
}

skip_next_group = true;
}

// All other block-level events start or continue a
// skipping group.
_ => {
Expand All @@ -256,6 +284,15 @@ pub fn group_events<'a>(events: &'a [(usize, Event<'a>)]) -> Vec<Group<'a>> {
groups
}

/// Check whether the HTML is a directive to skip the next translation group.
fn is_comment_skip_directive(html: &str) -> bool {
static RE: OnceLock<Regex> = OnceLock::new();

let re =
RE.get_or_init(|| Regex::new(r"<!-{2,}\s*mdbook-xgettext\s*:\s*skip\s*-{2,}>").unwrap());
re.is_match(html.trim())
}

/// Render a slice of Markdown events back to Markdown.
///
/// # Examples
Expand Down Expand Up @@ -578,6 +615,19 @@ mod tests {
);
}

#[test]
fn extract_events_comments() {
assert_eq!(
extract_events("<!--- mdbook-xgettext:skip -->\nHello", None),
vec![
(1, Html("<!--- mdbook-xgettext:skip -->\n".into())),
(2, Start(Paragraph)),
(2, Text("Hello".into())),
(2, End(Paragraph)),
]
);
}

#[test]
fn extract_messages_empty() {
assert_extract_messages("", vec![]);
Expand Down Expand Up @@ -951,4 +1001,94 @@ BOB
],
);
}

#[test]
fn test_is_comment_skip_directive_simple() {
assert_eq!(
is_comment_skip_directive("<!-- mdbook-xgettext:skip -->"),
true
);
}

#[test]
fn test_is_comment_skip_directive_tolerates_spaces() {
assert_eq!(
is_comment_skip_directive("<!-- mdbook-xgettext: skip -->"),
true
);
}

#[test]
fn test_is_comment_skip_directive_tolerates_dashes() {
assert_eq!(
is_comment_skip_directive("<!--- mdbook-xgettext:skip ---->"),
true
);
}

#[test]
fn test_is_comment_skip_directive_needs_skip() {
assert_eq!(
is_comment_skip_directive("<!-- mdbook-xgettext: foo -->"),
false
);
}
#[test]
fn test_is_comment_skip_directive_needs_to_be_a_comment() {
assert_eq!(
is_comment_skip_directive("<div>mdbook-xgettext: skip</div>"),
false
);
}

#[test]
fn extract_messages_skip_simple() {
assert_extract_messages(
r#"<!--- mdbook-xgettext:skip -->
This is a paragraph."#,
vec![],
);
}

#[test]
fn extract_messages_skip_next_paragraph_ok() {
assert_extract_messages(
r#"<!--- mdbook-xgettext:skip -->
This is a paragraph.
This should be translated.
"#,
vec![(4, "This should be translated.")],
);
}

#[test]
fn extract_messages_skip_next_codeblock() {
assert_extract_messages(
r#"<!--- mdbook-xgettext:skip -->
```
def f(x): return x * x
```
This should be translated.
"#,
vec![(5, "This should be translated.")],
);
}

#[test]
fn extract_messages_skip_back_to_back() {
assert_extract_messages(
r#"<!--- mdbook-xgettext:skip -->
```
def f(x): return x * x
```
<!--- mdbook-xgettext:skip -->
This should not translated.
But *this* should!
"#,
vec![(8, "But _this_ should!")],
);
}
}

0 comments on commit e735b8e

Please sign in to comment.