Skip to content

Commit

Permalink
Add support for rounding line numbers
Browse files Browse the repository at this point in the history
This adds a new feature which will let us round line number according
to a user-defined granularity.

The idea is that rounding the line numbers will minimize the updates
to the PO files: with a granularity of 10, the line numbers will be
10, 20, … and this should in turn remove some of the churn on the PO
files.

Setting granularity to zero will remove the line numbers completely
from the PO files.

Fixes #127.
  • Loading branch information
mgeisler committed Jan 15, 2024
1 parent 6293b88 commit 7a5cee9
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 2 deletions.
14 changes: 14 additions & 0 deletions i18n-helpers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ $ cargo install mdbook-i18n-helpers

Please see [USAGE](USAGE.md) for how to translate your `mdbook` project.

## Configuration

You can customize the plugins in your `book.toml` file.

### `mdbook-xgettext`

- `output.xgettext.pot-file` (no default, required): Set this to the path of
your POT file. A typical value is `messages.pot`, see examples in
[USAGE](USAGE.md).
- `output.xgettext.granularity` (default: `1`): Set this to _n_ to round all
line numbers down to the nearest multiple of _n_. Set this to `0` to remove
line numbers completely from the PO file. This can help reduce churn in your
PO files when messages edited.

## Changelog

Please see the [CHANGELOG](CHANGELOG.md) for details on the major changes in
Expand Down
116 changes: 114 additions & 2 deletions i18n-helpers/src/xgettext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,31 @@ fn add_message(catalog: &mut Catalog, msgid: &str, source: &str) {
catalog.append_or_update(message);
}

/// Build a source line for a catalog message.
///
/// Use `granularity` to round `lineno`:
///
/// - Set `granularity` to `1` if you want no rounding.
/// - Set `granularity` to `0` if you want to line number at all.
/// - Set `granularity` to `n` if you want rounding down to the
/// nearest multiple of `n`. As an example, if you set it to `10`,
/// then you will get sources like `foo.md:1`, `foo.md:10`,
/// `foo.md:20`, etc.
///
/// This can help reduce number of updates to your PO files.
fn build_source<P: AsRef<path::Path>>(path: P, lineno: usize, granularity: usize) -> String {
let path = path.as_ref();
match granularity {
0 => format!("{}", path.display()),
1 => format!("{}:{}", path.display(), lineno),
_ => format!(
"{}:{}",
path.display(),
std::cmp::max(1, lineno - (lineno % granularity))
),
}
}

/// Build catalog from RenderContext
///
/// # Arguments
Expand All @@ -75,12 +100,28 @@ where
metadata.content_transfer_encoding = String::from("8bit");
let mut catalog = Catalog::new(metadata);

// The line number granularity: we default to 1, but it can be
// overridden as needed.
let granularity = match ctx
.config
.get_renderer("xgettext")
.and_then(|cfg| cfg.get("granularity"))
{
None => 1,
Some(value) => value
.as_integer()
.and_then(|i| (i >= 0).then_some(i as usize))
.ok_or_else(|| {
anyhow!("Expected an unsigned integer for output.xgettext.granularity")
})?,
};

// First, add all chapter names and part titles from SUMMARY.md.
let summary_path = ctx.config.book.src.join("SUMMARY.md");
let summary = summary_reader(ctx.root.join(&summary_path))
.with_context(|| anyhow!("Failed to read {}", summary_path.display()))?;
for (lineno, msgid) in extract_messages(&summary) {
let source = format!("{}:{}", summary_path.display(), lineno);
let source = build_source(&summary_path, lineno, granularity);
// The summary is mostly links like "[Foo *Bar*](foo-bar.md)".
// We strip away the link to get "Foo *Bar*". The formatting
// is stripped away by mdbook when it sends the book to
Expand All @@ -97,7 +138,7 @@ where
None => continue,
};
for (lineno, msgid) in extract_messages(&chapter.content) {
let source = format!("{}:{}", path.display(), lineno);
let source = build_source(&path, lineno, granularity);
add_message(&mut catalog, &msgid, &source);
}
}
Expand Down Expand Up @@ -145,6 +186,36 @@ mod tests {
assert_eq!(strip_link("[foo *bar* `baz`](foo.md)"), "foo _bar_ `baz`");
}

#[test]
fn test_build_source_granularity_zero() {
assert_eq!(build_source("foo.md", 0, 0), "foo.md");
assert_eq!(build_source("foo.md", 1, 0), "foo.md");
assert_eq!(build_source("foo.md", 9, 0), "foo.md");
assert_eq!(build_source("foo.md", 10, 0), "foo.md");
assert_eq!(build_source("foo.md", 11, 0), "foo.md");
assert_eq!(build_source("foo.md", 20, 0), "foo.md");
}

#[test]
fn test_build_source_granularity_one() {
assert_eq!(build_source("foo.md", 0, 1), "foo.md:0");
assert_eq!(build_source("foo.md", 1, 1), "foo.md:1");
assert_eq!(build_source("foo.md", 9, 1), "foo.md:9");
assert_eq!(build_source("foo.md", 10, 1), "foo.md:10");
assert_eq!(build_source("foo.md", 11, 1), "foo.md:11");
assert_eq!(build_source("foo.md", 20, 1), "foo.md:20");
}

#[test]
fn test_build_source_granularity_ten() {
assert_eq!(build_source("foo.md", 0, 10), "foo.md:1");
assert_eq!(build_source("foo.md", 1, 10), "foo.md:1");
assert_eq!(build_source("foo.md", 9, 10), "foo.md:1");
assert_eq!(build_source("foo.md", 10, 10), "foo.md:10");
assert_eq!(build_source("foo.md", 11, 10), "foo.md:10");
assert_eq!(build_source("foo.md", 20, 10), "foo.md:20");
}

#[test]
fn test_create_catalog_defaults() -> anyhow::Result<()> {
let (ctx, _tmp) =
Expand Down Expand Up @@ -286,4 +357,45 @@ mod tests {

Ok(())
}

#[test]
fn test_create_catalog_lineno_granularity() -> anyhow::Result<()> {
let (ctx, _tmp) = create_render_context(&[
(
"book.toml",
"[book]\n\
[output.xgettext]\n\
granularity = 5",
),
("src/SUMMARY.md", "- [Foo](foo.md)"),
(
"src/foo.md",
"- Line 1\n\
\n\
- Line 3\n\
\n\
- Line 5\n\
\n\
- Line 7\n\
",
),
])?;

let catalog = create_catalog(&ctx, std::fs::read_to_string)?;
assert_eq!(
catalog
.messages()
.map(|msg| (msg.source(), msg.msgid()))
.collect::<Vec<_>>(),
&[
("src/SUMMARY.md:1", "Foo"),
("src/foo.md:1", "Line 1"),
("src/foo.md:1", "Line 3"),
("src/foo.md:5", "Line 5"),
("src/foo.md:5", "Line 7"),
]
);

Ok(())
}
}

0 comments on commit 7a5cee9

Please sign in to comment.