Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multilingual bibliography #126

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
56 changes: 39 additions & 17 deletions src/csl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,17 @@ impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T>
let mut entry_set = IndexSet::new();
nrydanov marked this conversation as resolved.
Show resolved Hide resolved
for req in self.citations.iter() {
for item in req.items.iter() {
entry_set.insert(item.entry);
entry_set.insert(CitationItem::new(
item.entry,
item.locator,
item.locale.clone(),
item.hidden,
item.purpose,
));
}
}

let mut entries: Vec<_> =
entry_set.into_iter().map(CitationItem::with_entry).collect();
let mut entries: Vec<_> = entry_set.into_iter().collect();
bib_style.sort(
&mut entries,
bib_style.csl.bibliography.as_ref().and_then(|b| b.sort.as_ref()),
Expand Down Expand Up @@ -1526,7 +1531,8 @@ impl<'a> StyleContext<'a> {
}

/// Get the locale for the given language in the style.
fn lookup_locale<F, R>(&self, mut f: F) -> Option<R>
/// Lookup order: item language -> citation language -> fallback
fn lookup_locale<F, R>(&self, mut f: F, item_locale: Option<&LocaleCode>) -> Option<R>
where
F: FnMut(&'a Locale) -> Option<R>,
{
Expand Down Expand Up @@ -1554,10 +1560,19 @@ impl<'a> StyleContext<'a> {
locale.fallback()
};

// First, we lookup for item language
if item_locale.is_some() {
if let Some(output) = lookup(resource, item_locale) {
return Some(output);
}
}

// Then we look up for global language
if let Some(output) = lookup(resource, Some(&locale)) {
return Some(output);
}

// Then we try to use fallback
if fallback.is_some() {
if let Some(output) = lookup(resource, fallback.as_ref()) {
return Some(output);
Expand All @@ -1577,8 +1592,8 @@ impl<'a> StyleContext<'a> {
}

/// Check whether to do punctuation in quotes.
fn punctuation_in_quotes(&self) -> bool {
self.lookup_locale(|f| f.style_options?.punctuation_in_quote)
nrydanov marked this conversation as resolved.
Show resolved Hide resolved
fn punctuation_in_quotes(&self, item_lang: Option<&'a LocaleCode>) -> bool {
self.lookup_locale(|f| f.style_options?.punctuation_in_quote, item_lang)
.unwrap_or_default()
}
}
Expand Down Expand Up @@ -2210,7 +2225,7 @@ impl<'a, T: EntryLike> Context<'a, T> {
}
.into(),
TermForm::default(),
false,
false
);

if let Some(mark) = mark {
Expand Down Expand Up @@ -2424,10 +2439,13 @@ impl<'a, T: EntryLike> Context<'a, T> {

let mut form = Some(form);
while let Some(current_form) = form {
if let Some(localization) = self.style.lookup_locale(|l| {
let term = l.term(term, current_form)?;
Some(if plural { term.multiple() } else { term.single() })
}) {
if let Some(localization) = self.style.lookup_locale(
|l| {
let term = l.term(term, current_form)?;
Some(if plural { term.multiple() } else { term.single() })
},
self.instance.locale,
) {
return localization;
}

Expand All @@ -2439,8 +2457,9 @@ impl<'a, T: EntryLike> Context<'a, T> {

/// Get the gender of a term.
fn gender(&self, term: Term) -> Option<GrammarGender> {
if let Some(localization) =
self.style.lookup_locale(|l| l.term(term, TermForm::default()))
if let Some(localization) = self
.style
.lookup_locale(|l| l.term(term, TermForm::default()), self.instance.locale)
{
localization.gender
} else {
Expand All @@ -2450,21 +2469,24 @@ impl<'a, T: EntryLike> Context<'a, T> {

/// Get a localized date format.
fn localized_date(&self, form: DateForm) -> Option<&'a citationberg::Date> {
self.style
.lookup_locale(|l| l.date.iter().find(|d| d.form == Some(form)))
self.style.lookup_locale(
|l| l.date.iter().find(|d| d.form == Some(form)),
self.instance.locale,
)
}

/// Get the ordinal lookup object.
fn ordinal_lookup(&self) -> OrdinalLookup<'a> {
self.style
.lookup_locale(|l| l.ordinals())
.lookup_locale(|l| l.ordinals(), self.instance.locale)
.unwrap_or_else(OrdinalLookup::empty)
}

/// Pull the next punctuation character into the preceeding quoted content
/// if appropriate for the locale.
fn may_pull_punctuation(&mut self) {
self.writing.pull_punctuation |= self.style.punctuation_in_quotes();
self.writing.pull_punctuation |=
self.style.punctuation_in_quotes(self.instance.locale);
}

/// Set whether to strip periods.
Expand Down
28 changes: 18 additions & 10 deletions src/csl/rendering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ impl<'a, 'b> ResolvedTextTarget<'a, 'b> {
ctx.style.get_macro(name).map(ResolvedTextTarget::Macro)
}
TextTarget::Term { term, form, plural } => {
ctx.term(*term, *form, *plural).map(ResolvedTextTarget::Term)
ctx.term(*term, *form, *plural)
.map(ResolvedTextTarget::Term)
}
TextTarget::Value { val } => Some(ResolvedTextTarget::Value(val)),
}
Expand Down Expand Up @@ -374,8 +375,12 @@ fn render_page_range<T: EntryLike>(range: std::ops::Range<i32>, ctx: &mut Contex
.format(
range,
ctx,
ctx.term(OtherTerm::PageRangeDelimiter.into(), TermForm::default(), false)
.or(Some("–")),
ctx.term(
OtherTerm::PageRangeDelimiter.into(),
TermForm::default(),
false
)
.or(Some("–")),
)
.unwrap();
}
Expand Down Expand Up @@ -680,13 +685,16 @@ fn render_date_part<T: EntryLike>(
if val != 1
|| !ctx
.style
.lookup_locale(|l| {
Some(
l.style_options
.and_then(|o| o.limit_day_ordinals_to_day_1)
.unwrap_or_default(),
)
})
.lookup_locale(
|l| {
Some(
l.style_options
.and_then(|o| o.limit_day_ordinals_to_day_1)
.unwrap_or_default(),
)
},
ctx.instance.locale,
)
.unwrap_or_default() =>
{
let gender = date
Expand Down
22 changes: 18 additions & 4 deletions src/csl/rendering/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,11 @@ impl RenderCsl for Names {
&& self.variable.contains(&NameVariable::Editor)
&& self.variable.contains(&NameVariable::Translator)
&& ctx
.term(NameVariable::EditorTranslator.into(), TermForm::default(), false)
.term(
NameVariable::EditorTranslator.into(),
TermForm::default(),
false
)
.is_some()
{
let editors = ctx.resolve_name_variable(NameVariable::Editor);
Expand Down Expand Up @@ -494,7 +498,11 @@ fn add_names<T: EntryLike>(
ctx.push_str(" ");
ctx.push_str(match and {
NameAnd::Text => ctx
.term(Term::Other(OtherTerm::And), TermForm::default(), false)
.term(
Term::Other(OtherTerm::And),
TermForm::default(),
false
)
.unwrap_or_default(),
NameAnd::Symbol => "&",
});
Expand All @@ -504,7 +512,11 @@ fn add_names<T: EntryLike>(
ctx.push_str(name_opts.delimiter);
ctx.push_str(match and {
NameAnd::Text => ctx
.term(Term::Other(OtherTerm::And), TermForm::default(), false)
.term(
Term::Other(OtherTerm::And),
TermForm::default(),
false,
)
.unwrap_or_default(),
NameAnd::Symbol => "&",
});
Expand Down Expand Up @@ -553,7 +565,9 @@ fn add_names<T: EntryLike>(
}
} else if has_et_al {
let cs_et_al = names.et_al().cloned().unwrap_or_default();
if let Some(term) = ctx.term(cs_et_al.term.into(), TermForm::default(), false) {
if let Some(term) =
ctx.term(cs_et_al.term.into(), TermForm::default(), false)
{
let delim = match name_opts.delimiter_precedes_et_al {
DelimiterBehavior::Always => true,
DelimiterBehavior::Contextual if take >= 2 => true,
Expand Down
6 changes: 6 additions & 0 deletions src/interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ impl TryFrom<&tex::Entry> for Entry {
}

// TODO: entry.orig_language into item.language = Some()
//
if let Some(Ok(lang)) =
map_res(entry.language())?.map(|l| l.parse())
{
item.set_language(lang);
}

if let Some(a) =
map_res(entry.afterword())?.map(|a| a.iter().map(Into::into).collect())
Expand Down
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,11 @@ fn main() {

let mut driver = BibliographyDriver::new();
for entry in &bibliography {
let mut item = CitationItem::with_entry(entry);
let id = entry.language().unwrap_or_default();
item.locale = Some(LocaleCode(String::from(id.language.as_str())));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing this, CitationItem::with_entry should use EntryLike's resolve_standard_variable feature to set the CitationItem's locale.

Copy link
Author

@nrydanov nrydanov Jul 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm alive. Please, could you explain what's main purpose for resolve_standard_variable and how can I achieve same result with it? I can't find any useful examples in codebase.

driver.citation(CitationRequest::new(
vec![CitationItem::with_entry(entry)],
vec![item],
&style,
locale.clone(),
&locales,
Expand Down
43 changes: 43 additions & 0 deletions tests/citeproc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,3 +746,46 @@ fn access_date() {
.unwrap();
assert_eq!(buf, "Retrieved 2021, from https://example.com/");
}

#[test]
nrydanov marked this conversation as resolved.
Show resolved Hide resolved
fn language() {
let style = ArchivedStyle::by_name("gost-r-705-2008-numeric").unwrap().get();
let locales = locales();
let Style::Independent(style) = style else {
panic!("test has dependent style");
};

let lib = from_biblatex_str(
r#"@online{test,
url={https://example.com},
title={aboba},
urldate={2023-06-03},
language={ru},
location={aboba},
note={aboooooba}
}"#,
)
.unwrap();
let entry = lib.get("test").unwrap();

let locale = Some(LocaleCode(String::from(
entry.language().unwrap().language.as_str(),
)));

let mut driver: BibliographyDriver<'_, Entry> = BibliographyDriver::new();
driver.citation(CitationRequest::new(
vec![CitationItem::new(entry, None, locale, false, None)],
&style,
None,
&locales,
Some(1),
));

let rendered = driver.finish(BibliographyRequest::new(&style, None, &locales));
let mut buf = String::new();
rendered.bibliography.unwrap().items[0]
.content
.write_buf(&mut buf, hayagriva::BufWriteFormat::Plain)
.unwrap();
assert_eq!(buf, "aboba [электронный ресурс]. URL: https://example.com/ (дата обращения: 03.06.2023)");
}
Loading