From 6dbb9832757a085f0d391110fccc56bd551d8a9f Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Thu, 11 Jan 2024 17:23:01 +0400 Subject: [PATCH 01/15] [WIP] Added localization --- src/csl/mod.rs | 69 +++++++++++++++++++++++++++----------- src/csl/rendering/mod.rs | 12 +++---- src/csl/rendering/names.rs | 10 +++--- src/interop.rs | 6 ++++ tests/citeproc.rs | 50 +++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 31 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 31ef3714..81fc071a 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -18,7 +18,6 @@ use citationberg::{ StyleClass, TermForm, ToFormatting, }; use citationberg::{DateForm, LongShortForm, OrdinalLookup, TextCase}; -use indexmap::IndexSet; use crate::csl::elem::{simplify_children, NonEmptyStack}; use crate::csl::rendering::names::NameDisambiguationProperties; @@ -102,15 +101,20 @@ impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T> let bib_style = request.style(); // Only remember each entry once, even if it is cited multiple times. - let mut entry_set = IndexSet::new(); + let mut entry_set = HashSet::new(); 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()), @@ -639,7 +643,7 @@ fn date_replacement( ) } else if let Some(no_date) = ctx .ctx(entry, cite_props.clone(), locale, term_locale, false) - .term(Term::Other(OtherTerm::NoDate), TermForm::default(), false) + .term(Term::Other(OtherTerm::NoDate), TermForm::default(), false, None) { no_date.to_string() } else { @@ -1452,7 +1456,7 @@ impl<'a> BibliographyRequest<'a> { } /// A reference to an [`crate::Entry`] within a [`CitationRequest`]. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct CitationItem<'a, T: EntryLike> { /// The entry to format. pub entry: &'a T, @@ -1470,6 +1474,11 @@ pub struct CitationItem<'a, T: EntryLike> { initial_idx: usize, } +impl<'a, T: EntryLike> Hash for CitationItem<'a, T> { + fn hash(&self, state: &mut H) { + self.entry.key().hash(state); + } +} impl<'a, T: EntryLike> CitationItem<'a, T> { /// Create a new citation item for the given entry. pub fn with_entry(entry: &'a T) -> Self { @@ -1527,7 +1536,7 @@ impl<'a> StyleContext<'a> { } /// Get the locale for the given language in the style. - fn lookup_locale(&self, mut f: F) -> Option + fn lookup_locale(&self, mut f: F, req: Option<&LocaleCode>) -> Option where F: FnMut(&'a Locale) -> Option, { @@ -1555,6 +1564,10 @@ impl<'a> StyleContext<'a> { locale.fallback() }; + if let Some(output) = lookup(resource, req) { + return Some(output); + } + if let Some(output) = lookup(resource, Some(&locale)) { return Some(output); } @@ -1579,7 +1592,7 @@ 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) + self.lookup_locale(|f| f.style_options?.punctuation_in_quote, None) .unwrap_or_default() } } @@ -2256,6 +2269,7 @@ impl<'a, T: EntryLike> Context<'a, T> { .into(), TermForm::default(), false, + None ); if let Some(mark) = mark { @@ -2278,6 +2292,7 @@ impl<'a, T: EntryLike> Context<'a, T> { .into(), TermForm::default(), false, + None, ); if let Some(mark) = mark { @@ -2289,9 +2304,13 @@ impl<'a, T: EntryLike> Context<'a, T> { fn do_pull_punctuation<'s>(&mut self, mut s: &'s str) -> &'s str { if self.writing.pull_punctuation && s.starts_with(['.', ',', ';', '!', '?']) { let close_quote = - self.term(OtherTerm::CloseQuote.into(), TermForm::default(), false); - let close_inner_quote = - self.term(OtherTerm::CloseInnerQuote.into(), TermForm::default(), false); + self.term(OtherTerm::CloseQuote.into(), TermForm::default(), false, None); + let close_inner_quote = self.term( + OtherTerm::CloseInnerQuote.into(), + TermForm::default(), + false, + None, + ); let mut used_buf = false; let buf = if self.writing.buf.is_empty() { @@ -2460,19 +2479,29 @@ impl<'a, T: EntryLike> Context<'a, T> { } /// Get a term from the style. - fn term(&self, mut term: Term, form: TermForm, plural: bool) -> Option<&'a str> { + fn term( + &self, + mut term: Term, + form: TermForm, + plural: bool, + lang: Option<&LocaleCode>, + ) -> Option<&'a str> { if term == Term::NumberVariable(csl_taxonomy::NumberVariable::Locator) { if let Some(locator) = self.instance.cite_props.speculative.locator { term = locator.0.into(); } } + 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() }) + }, + lang, + ) { return localization; } @@ -2485,7 +2514,7 @@ impl<'a, T: EntryLike> Context<'a, T> { /// Get the gender of a term. fn gender(&self, term: Term) -> Option { if let Some(localization) = - self.style.lookup_locale(|l| l.term(term, TermForm::default())) + self.style.lookup_locale(|l| l.term(term, TermForm::default()), None) { localization.gender } else { @@ -2496,13 +2525,13 @@ 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))) + .lookup_locale(|l| l.date.iter().find(|d| d.form == Some(form)), None) } /// Get the ordinal lookup object. fn ordinal_lookup(&self) -> OrdinalLookup<'a> { self.style - .lookup_locale(|l| l.ordinals()) + .lookup_locale(|l| l.ordinals(), None) .unwrap_or_else(OrdinalLookup::empty) } diff --git a/src/csl/rendering/mod.rs b/src/csl/rendering/mod.rs index bd710096..a609fb9a 100644 --- a/src/csl/rendering/mod.rs +++ b/src/csl/rendering/mod.rs @@ -202,7 +202,7 @@ 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, ctx.instance.locale).map(ResolvedTextTarget::Term) } TextTarget::Value { val } => Some(ResolvedTextTarget::Value(val)), } @@ -303,7 +303,7 @@ fn render_page_range(range: std::ops::Range, ctx: &mut Contex .format( range, ctx, - ctx.term(OtherTerm::PageRangeDelimiter.into(), TermForm::default(), false) + ctx.term(OtherTerm::PageRangeDelimiter.into(), TermForm::default(), false, None) .or(Some("–")), ) .unwrap(); @@ -357,7 +357,7 @@ impl RenderCsl for citationberg::Label { }; let content = ctx - .term(Term::from(self.variable), self.label.form, plural) + .term(Term::from(self.variable), self.label.form, plural, None) .unwrap_or_default(); render_label_with_var(&self.label, ctx, content); @@ -566,7 +566,7 @@ fn render_date_part( .and_then(|o| o.limit_day_ordinals_to_day_1) .unwrap_or_default(), ) - }) + }, None) .unwrap_or_default() => { let gender = date @@ -588,7 +588,7 @@ fn render_date_part( } DateStrongAnyForm::Month(DateMonthForm::Long) => { if let Some(month) = OtherTerm::month((val - 1) as u8) - .and_then(|m| ctx.term(m.into(), TermForm::Long, false)) + .and_then(|m| ctx.term(m.into(), TermForm::Long, false, None)) { ctx.push_str(month); } else { @@ -597,7 +597,7 @@ fn render_date_part( } DateStrongAnyForm::Month(DateMonthForm::Short) => { if let Some(month) = OtherTerm::month((val - 1) as u8) - .and_then(|m| ctx.term(m.into(), TermForm::Short, false)) + .and_then(|m| ctx.term(m.into(), TermForm::Short, false, None)) { ctx.push_str(month); } else { diff --git a/src/csl/rendering/names.rs b/src/csl/rendering/names.rs index ee4ce5de..d7aa0964 100644 --- a/src/csl/rendering/names.rs +++ b/src/csl/rendering/names.rs @@ -179,7 +179,7 @@ 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, None) .is_some() { let editors = ctx.resolve_name_variable(NameVariable::Editor, false); @@ -335,7 +335,7 @@ impl RenderCsl for Names { render_label_with_var( label, ctx, - ctx.term(variable.into(), label.form, plural) + ctx.term(variable.into(), label.form, plural, None) .unwrap_or_default(), ) } @@ -444,7 +444,7 @@ fn add_names( 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, None) .unwrap_or_default(), NameAnd::Symbol => "&", }); @@ -454,7 +454,7 @@ fn add_names( 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, None) .unwrap_or_default(), NameAnd::Symbol => "&", }); @@ -503,7 +503,7 @@ fn add_names( } } 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, None) { let delim = match name_opts.delimiter_precedes_et_al { DelimiterBehavior::Always => true, DelimiterBehavior::Contextual if take >= 2 => true, diff --git a/src/interop.rs b/src/interop.rs index 63da9cfa..881e8b91 100644 --- a/src/interop.rs +++ b/src/interop.rs @@ -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()) diff --git a/tests/citeproc.rs b/tests/citeproc.rs index b23d13e6..747b5fbe 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -16,6 +16,7 @@ use hayagriva::{ BibliographyDriver, BibliographyRequest, CitationItem, CitationRequest, CitePurpose, Entry, LocatorPayload, SpecificLocator, }; +use unic_langid::LanguageIdentifier; use unscanny::Scanner; const TEST_REPO_NAME: &str = "test-suite"; @@ -638,3 +639,52 @@ fn access_date() { .unwrap(); assert_eq!(buf, "Retrieved 2021, from https://example.com/"); } + +#[test] +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={russian}, + location={aboba}, + note={aboooooba} + }"#, + ) + .unwrap(); + let entry = lib.get("test").unwrap(); + + let mut locale: Option = None; + if let Some(lang) = entry.language() { + let lang_string = lang.language.to_string(); + if lang_string == "russian" { + locale = Some(LocaleCode(String::from("ru-RU"))); + } else { + locale = Some(LocaleCode(String::from("en-US"))); + } + } + + 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, "Retrieved 2021, from https://example.com/"); +} From 4f7bf0a247a1fca8249e50d3009b63fda117a5dd Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Fri, 12 Jan 2024 02:16:53 +0400 Subject: [PATCH 02/15] Added support for language parameter --- Cargo.toml | 1 + src/csl/mod.rs | 9 +++++---- src/csl/rendering/mod.rs | 29 +++++++++++++++++++---------- src/lang/codes.rs | 20 ++++++++++++++++++++ src/lang/mod.rs | 1 + src/main.rs | 3 +++ tests/citeproc.rs | 11 ++++------- 7 files changed, 53 insertions(+), 21 deletions(-) create mode 100644 src/lang/codes.rs diff --git a/Cargo.toml b/Cargo.toml index ef0b3306..9bbd73e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ biblatex = { version = "0.9", optional = true } ciborium = { version = "0.2.1", optional = true } clap = { version = "3.1", optional = true, features = ["cargo"] } strum = { version = "0.25", features = ["derive"], optional = true } +lazy_static = "1.4.0" [dev-dependencies] heck = "0.4" diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 81fc071a..1f19f017 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -18,6 +18,7 @@ use citationberg::{ StyleClass, TermForm, ToFormatting, }; use citationberg::{DateForm, LongShortForm, OrdinalLookup, TextCase}; +use indexmap::IndexSet; use crate::csl::elem::{simplify_children, NonEmptyStack}; use crate::csl::rendering::names::NameDisambiguationProperties; @@ -101,7 +102,7 @@ impl<'a, T: EntryLike + Hash + PartialEq + Eq + Debug> BibliographyDriver<'a, T> let bib_style = request.style(); // Only remember each entry once, even if it is cited multiple times. - let mut entry_set = HashSet::new(); + let mut entry_set = IndexSet::new(); for req in self.citations.iter() { for item in req.items.iter() { entry_set.insert(CitationItem::new( @@ -1538,7 +1539,7 @@ impl<'a> StyleContext<'a> { /// Get the locale for the given language in the style. fn lookup_locale(&self, mut f: F, req: Option<&LocaleCode>) -> Option where - F: FnMut(&'a Locale) -> Option, + F: FnMut(&'a Locale) -> Option { let mut lookup = |file: &'a [Locale], lang: Option<&LocaleCode>| { #[allow(clippy::redundant_closure)] @@ -1548,6 +1549,7 @@ impl<'a> StyleContext<'a> { let locale = self.locale(); let en_us = LocaleCode::en_us(); + for (i, resource) in [self.csl.locale.as_slice(), self.locale_files] .into_iter() .enumerate() @@ -2269,7 +2271,7 @@ impl<'a, T: EntryLike> Context<'a, T> { .into(), TermForm::default(), false, - None + None, ); if let Some(mark) = mark { @@ -2492,7 +2494,6 @@ 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( diff --git a/src/csl/rendering/mod.rs b/src/csl/rendering/mod.rs index a609fb9a..5026c364 100644 --- a/src/csl/rendering/mod.rs +++ b/src/csl/rendering/mod.rs @@ -202,7 +202,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, ctx.instance.locale).map(ResolvedTextTarget::Term) + ctx.term(*term, *form, *plural, ctx.instance.locale) + .map(ResolvedTextTarget::Term) } TextTarget::Value { val } => Some(ResolvedTextTarget::Value(val)), } @@ -303,8 +304,13 @@ fn render_page_range(range: std::ops::Range, ctx: &mut Contex .format( range, ctx, - ctx.term(OtherTerm::PageRangeDelimiter.into(), TermForm::default(), false, None) - .or(Some("–")), + ctx.term( + OtherTerm::PageRangeDelimiter.into(), + TermForm::default(), + false, + None, + ) + .or(Some("–")), ) .unwrap(); } @@ -560,13 +566,16 @@ fn render_date_part( 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(), - ) - }, None) + .lookup_locale( + |l| { + Some( + l.style_options + .and_then(|o| o.limit_day_ordinals_to_day_1) + .unwrap_or_default(), + ) + }, + None, + ) .unwrap_or_default() => { let gender = date diff --git a/src/lang/codes.rs b/src/lang/codes.rs new file mode 100644 index 00000000..c007a556 --- /dev/null +++ b/src/lang/codes.rs @@ -0,0 +1,20 @@ +use lazy_static::lazy_static; +use std::collections::HashMap; + +lazy_static! { + pub static ref LANGUAGE_CODE_MAPPING: HashMap<&'static str, &'static str> = + HashMap::from([ + ("english", "en"), + ("german", "ge"), + ("french", "fr"), + ("russian", "ru"), + ("italian", "it"), + ("chinese", "cn"), + ("japanese", "jp"), + ("ukranian", "ua") + ]); +} + +pub fn get_mapping(s: &str) -> Option<&str> { + return LANGUAGE_CODE_MAPPING.get(s).copied(); +} diff --git a/src/lang/mod.rs b/src/lang/mod.rs index 75cae079..3f9a22a1 100644 --- a/src/lang/mod.rs +++ b/src/lang/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod en; pub(crate) mod name; +pub mod codes; use std::{fmt::Write, mem}; diff --git a/src/main.rs b/src/main.rs index bf265b4a..1e6a340d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ use std::io::ErrorKind as IoErrorKind; use std::path::Path; use std::process::exit; use std::str::FromStr; +#[macro_use] +extern crate lazy_static; use citationberg::{ IndependentStyle, Locale, LocaleCode, LocaleFile, LongShortForm, Style, @@ -14,6 +16,7 @@ use strum::{EnumVariantNames, VariantNames}; use hayagriva::archive::{locales, ArchivedStyle}; use hayagriva::{io, BibliographyDriver, CitationItem, CitationRequest}; use hayagriva::{BibliographyRequest, Selector}; +use hayagriva::lang::get_mapping; #[derive(Debug, Copy, Clone, PartialEq, EnumVariantNames)] #[strum(serialize_all = "kebab_case")] diff --git a/tests/citeproc.rs b/tests/citeproc.rs index 747b5fbe..f8f463c9 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -16,7 +16,6 @@ use hayagriva::{ BibliographyDriver, BibliographyRequest, CitationItem, CitationRequest, CitePurpose, Entry, LocatorPayload, SpecificLocator, }; -use unic_langid::LanguageIdentifier; use unscanny::Scanner; const TEST_REPO_NAME: &str = "test-suite"; @@ -663,11 +662,9 @@ fn language() { let mut locale: Option = None; if let Some(lang) = entry.language() { - let lang_string = lang.language.to_string(); - if lang_string == "russian" { - locale = Some(LocaleCode(String::from("ru-RU"))); - } else { - locale = Some(LocaleCode(String::from("en-US"))); + let lang_string = lang.language.as_str(); + if let Some(value) = hayagriva::lang::codes::get_mapping(lang_string) { + locale = Some(LocaleCode(String::from(value))); } } @@ -686,5 +683,5 @@ fn language() { .content .write_buf(&mut buf, hayagriva::BufWriteFormat::Plain) .unwrap(); - assert_eq!(buf, "Retrieved 2021, from https://example.com/"); + assert_eq!(buf, "aboba [электронный ресурс]. URL: https://example.com/ (дата обращения: 03.06.2023)"); } From 60d4394fb23b62c3c5801789838d87f0e5cc1448 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Fri, 12 Jan 2024 11:34:13 +0400 Subject: [PATCH 03/15] Added docs --- src/lang/codes.rs | 4 +++- src/lang/mod.rs | 2 ++ src/main.rs | 2 -- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lang/codes.rs b/src/lang/codes.rs index c007a556..7b86ef05 100644 --- a/src/lang/codes.rs +++ b/src/lang/codes.rs @@ -1,8 +1,9 @@ use lazy_static::lazy_static; use std::collections::HashMap; + lazy_static! { - pub static ref LANGUAGE_CODE_MAPPING: HashMap<&'static str, &'static str> = + static ref LANGUAGE_CODE_MAPPING: HashMap<&'static str, &'static str> = HashMap::from([ ("english", "en"), ("german", "ge"), @@ -15,6 +16,7 @@ lazy_static! { ]); } +/// This function returns mapping for required language pub fn get_mapping(s: &str) -> Option<&str> { return LANGUAGE_CODE_MAPPING.get(s).copied(); } diff --git a/src/lang/mod.rs b/src/lang/mod.rs index 3f9a22a1..fc0bb862 100644 --- a/src/lang/mod.rs +++ b/src/lang/mod.rs @@ -2,6 +2,8 @@ pub(crate) mod en; pub(crate) mod name; + +/// This modules contains functions to use locale codes pub mod codes; use std::{fmt::Write, mem}; diff --git a/src/main.rs b/src/main.rs index 1e6a340d..0fc2dcb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ use std::io::ErrorKind as IoErrorKind; use std::path::Path; use std::process::exit; use std::str::FromStr; -#[macro_use] extern crate lazy_static; use citationberg::{ @@ -16,7 +15,6 @@ use strum::{EnumVariantNames, VariantNames}; use hayagriva::archive::{locales, ArchivedStyle}; use hayagriva::{io, BibliographyDriver, CitationItem, CitationRequest}; use hayagriva::{BibliographyRequest, Selector}; -use hayagriva::lang::get_mapping; #[derive(Debug, Copy, Clone, PartialEq, EnumVariantNames)] #[strum(serialize_all = "kebab_case")] From 41b32bcb6ddedaffe7bb32c71208c9f4c4ab0899 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Fri, 12 Jan 2024 12:02:11 +0400 Subject: [PATCH 04/15] Changes in naming --- src/csl/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 1f19f017..cfa4d145 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -1537,9 +1537,10 @@ impl<'a> StyleContext<'a> { } /// Get the locale for the given language in the style. - fn lookup_locale(&self, mut f: F, req: Option<&LocaleCode>) -> Option + /// Lookup order: item language -> citation language -> fallback + fn lookup_locale(&self, mut f: F, item_locale: Option<&LocaleCode>) -> Option where - F: FnMut(&'a Locale) -> Option + F: FnMut(&'a Locale) -> Option, { let mut lookup = |file: &'a [Locale], lang: Option<&LocaleCode>| { #[allow(clippy::redundant_closure)] @@ -1549,7 +1550,6 @@ impl<'a> StyleContext<'a> { let locale = self.locale(); let en_us = LocaleCode::en_us(); - for (i, resource) in [self.csl.locale.as_slice(), self.locale_files] .into_iter() .enumerate() @@ -1566,14 +1566,17 @@ impl<'a> StyleContext<'a> { locale.fallback() }; - if let Some(output) = lookup(resource, req) { + // First, we lookup for item language + 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); From d5ba5a5c073d5b05fef1c0c1335b1a1d187fe756 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 16:35:47 +0400 Subject: [PATCH 05/15] Returned back lazy_static dependency --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 95770cfd..ce8a84ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ unscanny = "0.1.0" url = { version = "2.4", features = ["serde"] } biblatex = { version = "0.9", optional = true } ciborium = { version = "0.2.1", optional = true } +lazy_static = "1.4.0" clap = { version = "4", optional = true, features = ["cargo"] } strum = { version = "0.26", features = ["derive"], optional = true } From 7c8bf24c1b54ff3b025a3a63affa4be558177631 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 16:37:36 +0400 Subject: [PATCH 06/15] Propagated item language settings to all lookup_locale calls --- src/csl/mod.rs | 34 ++++++++++++++++++++-------------- src/csl/rendering/mod.rs | 2 +- src/csl/rendering/names.rs | 25 +++++++++++++++++++++---- 3 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index f501a78a..f4568b82 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -1566,8 +1566,10 @@ impl<'a> StyleContext<'a> { }; // First, we lookup for item language - if let Some(output) = lookup(resource, item_locale) { - return Some(output); + if item_locale.is_some() { + if let Some(output) = lookup(resource, item_locale) { + return Some(output); + } } // Then we look up for global language @@ -1595,8 +1597,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, None) + 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() } } @@ -2252,7 +2254,7 @@ impl<'a, T: EntryLike> Context<'a, T> { .into(), TermForm::default(), false, - None, + self.instance.locale, ); if let Some(mark) = mark { @@ -2269,7 +2271,7 @@ impl<'a, T: EntryLike> Context<'a, T> { OtherTerm::CloseInnerQuote.into(), TermForm::default(), false, - None, + self.instance.locale, ); let mut used_buf = false; @@ -2444,7 +2446,7 @@ impl<'a, T: EntryLike> Context<'a, T> { mut term: Term, form: TermForm, plural: bool, - lang: Option<&LocaleCode>, + item_lang: Option<&LocaleCode>, ) -> Option<&'a str> { if term == Term::NumberVariable(csl_taxonomy::NumberVariable::Locator) { if let Some(locator) = self.instance.cite_props.speculative.locator { @@ -2459,7 +2461,7 @@ impl<'a, T: EntryLike> Context<'a, T> { let term = l.term(term, current_form)?; Some(if plural { term.multiple() } else { term.single() }) }, - lang, + item_lang, ) { return localization; } @@ -2472,8 +2474,9 @@ impl<'a, T: EntryLike> Context<'a, T> { /// Get the gender of a term. fn gender(&self, term: Term) -> Option { - if let Some(localization) = - self.style.lookup_locale(|l| l.term(term, TermForm::default()), None) + if let Some(localization) = self + .style + .lookup_locale(|l| l.term(term, TermForm::default()), self.instance.locale) { localization.gender } else { @@ -2483,21 +2486,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)), None) + 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(), None) + .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. diff --git a/src/csl/rendering/mod.rs b/src/csl/rendering/mod.rs index 3f2578f9..82268823 100644 --- a/src/csl/rendering/mod.rs +++ b/src/csl/rendering/mod.rs @@ -694,7 +694,7 @@ fn render_date_part( .unwrap_or_default(), ) }, - None, + ctx.instance.locale, ) .unwrap_or_default() => { diff --git a/src/csl/rendering/names.rs b/src/csl/rendering/names.rs index da687860..12d5aa26 100644 --- a/src/csl/rendering/names.rs +++ b/src/csl/rendering/names.rs @@ -219,7 +219,12 @@ impl RenderCsl for Names { && self.variable.contains(&NameVariable::Editor) && self.variable.contains(&NameVariable::Translator) && ctx - .term(NameVariable::EditorTranslator.into(), TermForm::default(), false, None) + .term( + NameVariable::EditorTranslator.into(), + TermForm::default(), + false, + ctx.instance.locale, + ) .is_some() { let editors = ctx.resolve_name_variable(NameVariable::Editor); @@ -494,7 +499,12 @@ fn add_names( ctx.push_str(" "); ctx.push_str(match and { NameAnd::Text => ctx - .term(Term::Other(OtherTerm::And), TermForm::default(), false, None) + .term( + Term::Other(OtherTerm::And), + TermForm::default(), + false, + None, + ) .unwrap_or_default(), NameAnd::Symbol => "&", }); @@ -504,7 +514,12 @@ fn add_names( ctx.push_str(name_opts.delimiter); ctx.push_str(match and { NameAnd::Text => ctx - .term(Term::Other(OtherTerm::And), TermForm::default(), false, None) + .term( + Term::Other(OtherTerm::And), + TermForm::default(), + false, + None, + ) .unwrap_or_default(), NameAnd::Symbol => "&", }); @@ -553,7 +568,9 @@ fn add_names( } } 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, None) { + if let Some(term) = + ctx.term(cs_et_al.term.into(), TermForm::default(), false, None) + { let delim = match name_opts.delimiter_precedes_et_al { DelimiterBehavior::Always => true, DelimiterBehavior::Contextual if take >= 2 => true, From dba2644a34799a76268024f617554d2d19b61214 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 17:42:40 +0400 Subject: [PATCH 07/15] Added specific language support for each entry in CLI --- .gitignore | 3 +++ src/main.rs | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 35729b95..cff1e484 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ Cargo.lock # macOS .DS_Store + +# Tests +archive/locales diff --git a/src/main.rs b/src/main.rs index eeebd439..7761dc25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -321,8 +321,12 @@ fn main() { let mut driver = BibliographyDriver::new(); for entry in &bibliography { + let mut item = CitationItem::with_entry(entry); + item.locale = Some(LocaleCode(String::from( + entry.language().unwrap().language.as_str(), + ))); driver.citation(CitationRequest::new( - vec![CitationItem::with_entry(entry)], + vec![item], &style, locale.clone(), &locales, From 596de0e7c811d4a12309171a01cddefae486ca49 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 17:44:39 +0400 Subject: [PATCH 08/15] Reverted .gitignore changes --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index cff1e484..35729b95 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,3 @@ Cargo.lock # macOS .DS_Store - -# Tests -archive/locales From 3b3018b809b081f166936d84444926d71f395937 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 17:48:06 +0400 Subject: [PATCH 09/15] Returned back hash deriving --- src/csl/mod.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index f4568b82..93bdeed7 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -643,7 +643,7 @@ fn date_replacement( ) } else if let Some(no_date) = ctx .ctx(entry, cite_props.clone(), locale, term_locale, false) - .term(Term::Other(OtherTerm::NoDate), TermForm::default(), false, None) + .term(Term::Other(OtherTerm::NoDate), TermForm::default(), false, term_locale) { no_date.to_string() } else { @@ -1456,7 +1456,7 @@ impl<'a> BibliographyRequest<'a> { } /// A reference to an [`crate::Entry`] within a [`CitationRequest`]. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CitationItem<'a, T: EntryLike> { /// The entry to format. pub entry: &'a T, @@ -1474,11 +1474,6 @@ pub struct CitationItem<'a, T: EntryLike> { initial_idx: usize, } -impl<'a, T: EntryLike> Hash for CitationItem<'a, T> { - fn hash(&self, state: &mut H) { - self.entry.key().hash(state); - } -} impl<'a, T: EntryLike> CitationItem<'a, T> { /// Create a new citation item for the given entry. pub fn with_entry(entry: &'a T) -> Self { From 189354588b1fbf2b07c9068df1c65a7486834546 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 17:49:31 +0400 Subject: [PATCH 10/15] Added missing term locale --- src/csl/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 93bdeed7..4b18d8b2 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -2261,7 +2261,7 @@ impl<'a, T: EntryLike> Context<'a, T> { fn do_pull_punctuation<'s>(&mut self, mut s: &'s str) -> &'s str { if self.writing.pull_punctuation && s.starts_with(['.', ',', ';', '!', '?']) { let close_quote = - self.term(OtherTerm::CloseQuote.into(), TermForm::default(), false, None); + self.term(OtherTerm::CloseQuote.into(), TermForm::default(), false, self.instance.locale); let close_inner_quote = self.term( OtherTerm::CloseInnerQuote.into(), TermForm::default(), From a64beca37caaac79ef0f20470177e19afa0fad10 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 17:54:19 +0400 Subject: [PATCH 11/15] Removed redundant term argument --- src/csl/mod.rs | 26 +++++++------------------- src/csl/rendering/mod.rs | 13 ++++++------- src/csl/rendering/names.rs | 11 ++++------- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/csl/mod.rs b/src/csl/mod.rs index 4b18d8b2..c50ad289 100644 --- a/src/csl/mod.rs +++ b/src/csl/mod.rs @@ -643,7 +643,7 @@ fn date_replacement( ) } else if let Some(no_date) = ctx .ctx(entry, cite_props.clone(), locale, term_locale, false) - .term(Term::Other(OtherTerm::NoDate), TermForm::default(), false, term_locale) + .term(Term::Other(OtherTerm::NoDate), TermForm::default(), false) { no_date.to_string() } else { @@ -2225,8 +2225,7 @@ impl<'a, T: EntryLike> Context<'a, T> { } .into(), TermForm::default(), - false, - None, + false ); if let Some(mark) = mark { @@ -2249,7 +2248,6 @@ impl<'a, T: EntryLike> Context<'a, T> { .into(), TermForm::default(), false, - self.instance.locale, ); if let Some(mark) = mark { @@ -2261,13 +2259,9 @@ impl<'a, T: EntryLike> Context<'a, T> { fn do_pull_punctuation<'s>(&mut self, mut s: &'s str) -> &'s str { if self.writing.pull_punctuation && s.starts_with(['.', ',', ';', '!', '?']) { let close_quote = - self.term(OtherTerm::CloseQuote.into(), TermForm::default(), false, self.instance.locale); - let close_inner_quote = self.term( - OtherTerm::CloseInnerQuote.into(), - TermForm::default(), - false, - self.instance.locale, - ); + self.term(OtherTerm::CloseQuote.into(), TermForm::default(), false); + let close_inner_quote = + self.term(OtherTerm::CloseInnerQuote.into(), TermForm::default(), false); let mut used_buf = false; let buf = if self.writing.buf.is_empty() { @@ -2436,13 +2430,7 @@ impl<'a, T: EntryLike> Context<'a, T> { } /// Get a term from the style. - fn term( - &self, - mut term: Term, - form: TermForm, - plural: bool, - item_lang: Option<&LocaleCode>, - ) -> Option<&'a str> { + fn term(&self, mut term: Term, form: TermForm, plural: bool) -> Option<&'a str> { if term == Term::NumberVariable(csl_taxonomy::NumberVariable::Locator) { if let Some(locator) = self.instance.cite_props.speculative.locator { term = locator.0.into(); @@ -2456,7 +2444,7 @@ impl<'a, T: EntryLike> Context<'a, T> { let term = l.term(term, current_form)?; Some(if plural { term.multiple() } else { term.single() }) }, - item_lang, + self.instance.locale, ) { return localization; } diff --git a/src/csl/rendering/mod.rs b/src/csl/rendering/mod.rs index 82268823..25f7f160 100644 --- a/src/csl/rendering/mod.rs +++ b/src/csl/rendering/mod.rs @@ -248,7 +248,7 @@ impl<'a, 'b> ResolvedTextTarget<'a, 'b> { ctx.style.get_macro(name).map(ResolvedTextTarget::Macro) } TextTarget::Term { term, form, plural } => { - ctx.term(*term, *form, *plural, ctx.instance.locale) + ctx.term(*term, *form, *plural) .map(ResolvedTextTarget::Term) } TextTarget::Value { val } => Some(ResolvedTextTarget::Value(val)), @@ -378,8 +378,7 @@ fn render_page_range(range: std::ops::Range, ctx: &mut Contex ctx.term( OtherTerm::PageRangeDelimiter.into(), TermForm::default(), - false, - None, + false ) .or(Some("–")), ) @@ -417,7 +416,7 @@ impl RenderCsl for citationberg::Label { let plural = label_pluralization(self, variable); let content = ctx - .term(Term::from(self.variable), self.label.form, plural, None) + .term(Term::from(self.variable), self.label.form, plural) .unwrap_or_default(); render_label_with_var(&self.label, ctx, content); @@ -460,7 +459,7 @@ impl RenderCsl for citationberg::Label { if let Some(num) = ctx.resolve_number_variable(self.variable) { let plural = label_pluralization(self, num); ( - ctx.term(Term::from(self.variable), self.label.form, plural, None).is_some(), + ctx.term(Term::from(self.variable), self.label.form, plural).is_some(), UsageInfo::default(), ) } else { @@ -717,7 +716,7 @@ fn render_date_part( } DateStrongAnyForm::Month(DateMonthForm::Long) => { if let Some(month) = OtherTerm::month((val - 1) as u8) - .and_then(|m| ctx.term(m.into(), TermForm::Long, false, None)) + .and_then(|m| ctx.term(m.into(), TermForm::Long, false)) { ctx.push_str(month); } else { @@ -726,7 +725,7 @@ fn render_date_part( } DateStrongAnyForm::Month(DateMonthForm::Short) => { if let Some(month) = OtherTerm::month((val - 1) as u8) - .and_then(|m| ctx.term(m.into(), TermForm::Short, false, None)) + .and_then(|m| ctx.term(m.into(), TermForm::Short, false)) { ctx.push_str(month); } else { diff --git a/src/csl/rendering/names.rs b/src/csl/rendering/names.rs index 12d5aa26..3d0c5534 100644 --- a/src/csl/rendering/names.rs +++ b/src/csl/rendering/names.rs @@ -222,8 +222,7 @@ impl RenderCsl for Names { .term( NameVariable::EditorTranslator.into(), TermForm::default(), - false, - ctx.instance.locale, + false ) .is_some() { @@ -355,7 +354,7 @@ impl RenderCsl for Names { render_label_with_var( label, ctx, - ctx.term(variable.into(), label.form, plural, None) + ctx.term(variable.into(), label.form, plural) .unwrap_or_default(), ) } @@ -502,8 +501,7 @@ fn add_names( .term( Term::Other(OtherTerm::And), TermForm::default(), - false, - None, + false ) .unwrap_or_default(), NameAnd::Symbol => "&", @@ -518,7 +516,6 @@ fn add_names( Term::Other(OtherTerm::And), TermForm::default(), false, - None, ) .unwrap_or_default(), NameAnd::Symbol => "&", @@ -569,7 +566,7 @@ fn add_names( } 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, None) + ctx.term(cs_et_al.term.into(), TermForm::default(), false) { let delim = match name_opts.delimiter_precedes_et_al { DelimiterBehavior::Always => true, From 06f0dae317a2a66f6a8af60c9981f72158f4ed8a Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 17:55:12 +0400 Subject: [PATCH 12/15] Removed lazy_static dependency --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ce8a84ee..95770cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,6 @@ unscanny = "0.1.0" url = { version = "2.4", features = ["serde"] } biblatex = { version = "0.9", optional = true } ciborium = { version = "0.2.1", optional = true } -lazy_static = "1.4.0" clap = { version = "4", optional = true, features = ["cargo"] } strum = { version = "0.26", features = ["derive"], optional = true } From c193bbfbb2a47998c44ca69f75cc53590c4bba4a Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 17:57:27 +0400 Subject: [PATCH 13/15] Removed language codes --- src/lang/codes.rs | 22 ---------------------- src/lang/mod.rs | 3 --- 2 files changed, 25 deletions(-) delete mode 100644 src/lang/codes.rs diff --git a/src/lang/codes.rs b/src/lang/codes.rs deleted file mode 100644 index 7b86ef05..00000000 --- a/src/lang/codes.rs +++ /dev/null @@ -1,22 +0,0 @@ -use lazy_static::lazy_static; -use std::collections::HashMap; - - -lazy_static! { - static ref LANGUAGE_CODE_MAPPING: HashMap<&'static str, &'static str> = - HashMap::from([ - ("english", "en"), - ("german", "ge"), - ("french", "fr"), - ("russian", "ru"), - ("italian", "it"), - ("chinese", "cn"), - ("japanese", "jp"), - ("ukranian", "ua") - ]); -} - -/// This function returns mapping for required language -pub fn get_mapping(s: &str) -> Option<&str> { - return LANGUAGE_CODE_MAPPING.get(s).copied(); -} diff --git a/src/lang/mod.rs b/src/lang/mod.rs index fc0bb862..75cae079 100644 --- a/src/lang/mod.rs +++ b/src/lang/mod.rs @@ -3,9 +3,6 @@ pub(crate) mod en; pub(crate) mod name; -/// This modules contains functions to use locale codes -pub mod codes; - use std::{fmt::Write, mem}; use crate::types::{FoldableKind, FoldableStringChunk}; From ac426e72681f948a69d748deb536effb162e206e Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Sun, 24 Mar 2024 18:01:01 +0400 Subject: [PATCH 14/15] Fixed tests --- src/main.rs | 1 - tests/citeproc.rs | 12 ++++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7761dc25..1e113a52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ use std::io::ErrorKind as IoErrorKind; use std::path::Path; use std::process::exit; use std::str::FromStr; -extern crate lazy_static; use citationberg::taxonomy::Locator; use citationberg::{ diff --git a/tests/citeproc.rs b/tests/citeproc.rs index a9a9b67a..91a02726 100644 --- a/tests/citeproc.rs +++ b/tests/citeproc.rs @@ -760,7 +760,7 @@ fn language() { url={https://example.com}, title={aboba}, urldate={2023-06-03}, - language={russian}, + language={ru}, location={aboba}, note={aboooooba} }"#, @@ -768,13 +768,9 @@ fn language() { .unwrap(); let entry = lib.get("test").unwrap(); - let mut locale: Option = None; - if let Some(lang) = entry.language() { - let lang_string = lang.language.as_str(); - if let Some(value) = hayagriva::lang::codes::get_mapping(lang_string) { - locale = Some(LocaleCode(String::from(value))); - } - } + let locale = Some(LocaleCode(String::from( + entry.language().unwrap().language.as_str(), + ))); let mut driver: BibliographyDriver<'_, Entry> = BibliographyDriver::new(); driver.citation(CitationRequest::new( From 9c496b50485453519663034347874ef0a829d635 Mon Sep 17 00:00:00 2001 From: Nikita Rydanov Date: Mon, 25 Mar 2024 11:56:21 +0400 Subject: [PATCH 15/15] Made identifier retrieval more safe --- src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1e113a52..c16b9085 100644 --- a/src/main.rs +++ b/src/main.rs @@ -321,9 +321,8 @@ fn main() { let mut driver = BibliographyDriver::new(); for entry in &bibliography { let mut item = CitationItem::with_entry(entry); - item.locale = Some(LocaleCode(String::from( - entry.language().unwrap().language.as_str(), - ))); + let id = entry.language().unwrap_or_default(); + item.locale = Some(LocaleCode(String::from(id.language.as_str()))); driver.citation(CitationRequest::new( vec![item], &style,