1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![warn(missing_docs, rustdoc::broken_intra_doc_links)]
3#[cfg(feature = "catalog")]
99#[cfg_attr(docsrs, doc(cfg(feature = "catalog")))]
100mod api;
101mod borrowed;
102pub mod diagnostic_codes;
103mod line_state;
104mod merge;
105mod parse;
106mod scan;
107mod serialize;
108mod text;
109mod utf8;
110
111#[cfg(feature = "catalog")]
112#[cfg_attr(docsrs, doc(cfg(feature = "catalog")))]
113pub use api::{
114 AiProvenance, ApiError, COMPILED_CATALOG_ARTIFACT_SCHEMA_VERSION, CatalogAuditChecks,
115 CatalogAuditDiagnostic, CatalogAuditIcuOptions, CatalogAuditMessageRef, CatalogAuditOptions,
116 CatalogAuditReport, CatalogAuditSummary, CatalogCombineInput, CatalogCombineResult,
117 CatalogCombineSelection, CatalogCombineStats, CatalogConflictStrategy, CatalogCoverageMessage,
118 CatalogCoverageOptions, CatalogCoverageReport, CatalogFileCombineResult, CatalogFileFormat,
119 CatalogLocaleCoverage, CatalogLocaleReview, CatalogMachineTranslationMessage,
120 CatalogMachineTranslationReview, CatalogMachineTranslationStatus, CatalogMessage,
121 CatalogMessageKey, CatalogMessageStatus, CatalogMode, CatalogOrigin, CatalogReviewOptions,
122 CatalogReviewReport, CatalogReviewSummary, CatalogReviewTranslation, CatalogSemantics,
123 CatalogSourceChange, CatalogSourceChangeKind, CatalogSourceChangeReport, CatalogStats,
124 CatalogStorageFormat, CatalogTranslationChange, CatalogTranslationChangeReport,
125 CatalogUpdateInput, CatalogUpdateResult, CombineCatalogFilesOptions, CombineCatalogOptions,
126 CompileCatalogArtifactIcuOptions, CompileCatalogArtifactOptions,
127 CompileCatalogArtifactReportOptions, CompileCatalogArtifactReportSelection,
128 CompileCatalogOptions, CompileSelectedCatalogArtifactOptions, CompiledCatalog,
129 CompiledCatalogArtifact, CompiledCatalogArtifactReport, CompiledCatalogDiagnostic,
130 CompiledCatalogIdDescription, CompiledCatalogIdIndex, CompiledCatalogMissingMessage,
131 CompiledCatalogProvenanceReport, CompiledCatalogPseudolocalizationOptions,
132 CompiledCatalogResolution, CompiledCatalogResolutionKind, CompiledCatalogTranslationKind,
133 CompiledCatalogUnavailableId, CompiledKeyStrategy, CompiledMessage, CompiledTranslation,
134 DescribeCompiledIdsReport, Diagnostic, DiagnosticSeverity, EffectiveTranslation,
135 EffectiveTranslationRef, ExtractedMessage, ExtractedPluralMessage, ExtractedSingularMessage,
136 IcuFormatterSupportPolicy, IcuPseudolocalizationOptions, IcuSyntaxPolicy, MachineMetadata,
137 NormalizedParsedCatalog, ObsoleteInfo, ObsoleteStrategy, OrderBy, ParseCatalogOptions,
138 ParsedCatalog, PlaceholderCommentMode, PluralEncoding, PluralSource, RenderOptions,
139 SourceExtractedMessage, TranslationShape, UpdateCatalogFileOptions, UpdateCatalogOptions,
140 audit_catalogs, combine_catalog_files, combine_catalogs, compile_catalog_artifact,
141 compile_catalog_artifact_report, compile_catalog_artifact_selected, compiled_key,
142 machine_translation_hash, measure_catalog_coverage, parse_catalog,
143 pseudolocalize_compiled_catalog_artifact, review_catalogs, update_catalog, update_catalog_file,
144};
145pub use borrowed::{
146 BorrowedHeader, BorrowedMsgStr, BorrowedPoFile, BorrowedPoItem, parse_po_borrowed,
147};
148pub use diagnostic_codes::DiagnosticCode;
149pub use merge::{MergeMessageInput, merge_catalog};
150pub use parse::{parse_po, parse_po_bytes};
151pub use serialize::stringify_po;
152pub use text::{escape_string, extract_quoted, extract_quoted_cow, unescape_string};
153
154use core::{
155 fmt,
156 iter::FusedIterator,
157 ops::{Deref, DerefMut, Index},
158 slice,
159};
160
161#[cfg(feature = "serde")]
162use serde::{Deserialize, Serialize};
163
164use smallvec::{IntoIter as SmallVecIntoIter, SmallVec};
165
166#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
174#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
175#[cfg_attr(feature = "serde", serde(transparent))]
176pub struct PoVec<T>(SmallVec<[T; 1]>);
177
178impl<T> PoVec<T> {
179 #[must_use]
181 pub fn new() -> Self {
182 Self(SmallVec::new())
183 }
184
185 #[must_use]
187 pub fn with_capacity(capacity: usize) -> Self {
188 Self(SmallVec::with_capacity(capacity))
189 }
190
191 #[must_use]
193 pub fn len(&self) -> usize {
194 self.0.len()
195 }
196
197 #[must_use]
199 pub fn is_empty(&self) -> bool {
200 self.0.is_empty()
201 }
202
203 #[must_use]
205 pub fn as_slice(&self) -> &[T] {
206 self.0.as_slice()
207 }
208
209 #[must_use]
211 pub fn as_mut_slice(&mut self) -> &mut [T] {
212 self.0.as_mut_slice()
213 }
214
215 pub fn iter(&self) -> slice::Iter<'_, T> {
217 self.as_slice().iter()
218 }
219
220 pub fn iter_mut(&mut self) -> slice::IterMut<'_, T> {
222 self.as_mut_slice().iter_mut()
223 }
224
225 pub fn push(&mut self, value: T) {
227 self.0.push(value);
228 }
229
230 pub fn clear(&mut self) {
232 self.0.clear();
233 }
234
235 #[must_use]
237 pub fn into_vec(self) -> Vec<T> {
238 self.0.into_vec()
239 }
240}
241
242impl<T> AsRef<[T]> for PoVec<T> {
243 fn as_ref(&self) -> &[T] {
244 self.as_slice()
245 }
246}
247
248impl<T> AsMut<[T]> for PoVec<T> {
249 fn as_mut(&mut self) -> &mut [T] {
250 self.as_mut_slice()
251 }
252}
253
254impl<T> Deref for PoVec<T> {
255 type Target = [T];
256
257 fn deref(&self) -> &Self::Target {
258 self.as_slice()
259 }
260}
261
262impl<T> DerefMut for PoVec<T> {
263 fn deref_mut(&mut self) -> &mut Self::Target {
264 self.as_mut_slice()
265 }
266}
267
268impl<T> Extend<T> for PoVec<T> {
269 fn extend<I>(&mut self, iter: I)
270 where
271 I: IntoIterator<Item = T>,
272 {
273 self.0.extend(iter);
274 }
275}
276
277impl<T> From<Vec<T>> for PoVec<T> {
278 fn from(value: Vec<T>) -> Self {
279 Self(SmallVec::from_vec(value))
280 }
281}
282
283impl<T, const N: usize> From<[T; N]> for PoVec<T> {
284 fn from(value: [T; N]) -> Self {
285 Self(value.into_iter().collect())
286 }
287}
288
289impl<T> FromIterator<T> for PoVec<T> {
290 fn from_iter<I>(iter: I) -> Self
291 where
292 I: IntoIterator<Item = T>,
293 {
294 Self(iter.into_iter().collect())
295 }
296}
297
298impl<T> From<PoVec<T>> for Vec<T> {
299 fn from(value: PoVec<T>) -> Self {
300 value.into_vec()
301 }
302}
303
304impl<T> IntoIterator for PoVec<T> {
305 type Item = T;
306 type IntoIter = PoVecIntoIter<T>;
307
308 fn into_iter(self) -> Self::IntoIter {
309 PoVecIntoIter {
310 inner: self.0.into_iter(),
311 }
312 }
313}
314
315impl<'a, T> IntoIterator for &'a PoVec<T> {
316 type Item = &'a T;
317 type IntoIter = slice::Iter<'a, T>;
318
319 fn into_iter(self) -> Self::IntoIter {
320 self.iter()
321 }
322}
323
324impl<'a, T> IntoIterator for &'a mut PoVec<T> {
325 type Item = &'a mut T;
326 type IntoIter = slice::IterMut<'a, T>;
327
328 fn into_iter(self) -> Self::IntoIter {
329 self.iter_mut()
330 }
331}
332
333impl<T, U> PartialEq<[U]> for PoVec<T>
334where
335 T: PartialEq<U>,
336{
337 fn eq(&self, other: &[U]) -> bool {
338 self.as_slice() == other
339 }
340}
341
342impl<T, U> PartialEq<&[U]> for PoVec<T>
343where
344 T: PartialEq<U>,
345{
346 fn eq(&self, other: &&[U]) -> bool {
347 self.as_slice() == *other
348 }
349}
350
351impl<T, U> PartialEq<Vec<U>> for PoVec<T>
352where
353 T: PartialEq<U>,
354{
355 fn eq(&self, other: &Vec<U>) -> bool {
356 self.as_slice() == other.as_slice()
357 }
358}
359
360pub struct PoVecIntoIter<T> {
362 inner: SmallVecIntoIter<[T; 1]>,
363}
364
365impl<T> Iterator for PoVecIntoIter<T> {
366 type Item = T;
367
368 fn next(&mut self) -> Option<Self::Item> {
369 self.inner.next()
370 }
371
372 fn size_hint(&self) -> (usize, Option<usize>) {
373 self.inner.size_hint()
374 }
375}
376
377impl<T> DoubleEndedIterator for PoVecIntoIter<T> {
378 fn next_back(&mut self) -> Option<Self::Item> {
379 self.inner.next_back()
380 }
381}
382
383impl<T> ExactSizeIterator for PoVecIntoIter<T> {}
384
385impl<T> FusedIterator for PoVecIntoIter<T> {}
386
387#[derive(Debug, Clone, PartialEq, Eq, Default)]
389#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
390#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
391pub struct PoFile {
392 pub comments: Vec<String>,
394 pub extracted_comments: Vec<String>,
396 pub headers: Vec<Header>,
398 pub items: Vec<PoItem>,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Default)]
404#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
405#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
406pub struct Header {
407 pub key: String,
409 pub value: String,
411}
412
413#[derive(Debug, Clone, PartialEq, Eq, Default)]
415#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
416#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
417pub struct PoItem {
418 pub msgid: String,
420 pub msgctxt: Option<String>,
422 pub references: PoVec<String>,
424 pub msgid_plural: Option<String>,
426 pub msgstr: MsgStr,
428 pub comments: PoVec<String>,
430 pub extracted_comments: PoVec<String>,
432 pub flags: PoVec<String>,
440 pub metadata: PoVec<(String, String)>,
442 pub obsolete: bool,
444 pub nplurals: usize,
446}
447
448impl PoItem {
449 #[must_use]
451 pub fn new(nplurals: usize) -> Self {
452 Self {
453 nplurals,
454 ..Self::default()
455 }
456 }
457
458 pub(crate) fn clear_for_reuse(&mut self, nplurals: usize) {
459 self.msgid.clear();
460 self.msgctxt = None;
461 self.references.clear();
462 self.msgid_plural = None;
463 self.msgstr = MsgStr::None;
464 self.comments.clear();
465 self.extracted_comments.clear();
466 self.flags.clear();
467 self.metadata.clear();
468 self.obsolete = false;
469 self.nplurals = nplurals;
470 }
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, Default)]
475#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
476#[cfg_attr(
477 feature = "serde",
478 serde(tag = "kind", content = "value", rename_all = "snake_case")
479)]
480pub enum MsgStr {
481 #[default]
483 None,
484 Singular(String),
486 Plural(Vec<String>),
488}
489
490impl MsgStr {
491 #[must_use]
498 pub fn plural(values: Vec<String>) -> Self {
499 Self::Plural(values)
500 }
501
502 #[must_use]
504 pub const fn is_empty(&self) -> bool {
505 matches!(self, Self::None)
506 }
507
508 #[must_use]
510 pub fn len(&self) -> usize {
511 match self {
512 Self::None => 0,
513 Self::Singular(_) => 1,
514 Self::Plural(values) => values.len(),
515 }
516 }
517
518 #[must_use]
520 pub fn first(&self) -> Option<&str> {
521 match self {
522 Self::None => None,
523 Self::Singular(value) => Some(value.as_str()),
524 Self::Plural(values) => values.first().map(String::as_str),
525 }
526 }
527
528 #[must_use]
530 pub fn get(&self, index: usize) -> Option<&str> {
531 match self {
532 Self::Singular(value) if index == 0 => Some(value.as_str()),
533 Self::None | Self::Singular(_) => None,
534 Self::Plural(values) => values.get(index).map(String::as_str),
535 }
536 }
537
538 #[must_use]
540 pub fn iter(&self) -> MsgStrIter<'_> {
541 match self {
542 Self::None => MsgStrIter::empty(),
543 Self::Singular(value) => MsgStrIter::single(value.as_str()),
544 Self::Plural(values) => MsgStrIter::many(values.iter()),
545 }
546 }
547
548 #[must_use]
550 pub fn into_vec(self) -> Vec<String> {
551 match self {
552 Self::None => Vec::new(),
553 Self::Singular(value) => vec![value],
554 Self::Plural(values) => values,
555 }
556 }
557}
558
559impl From<String> for MsgStr {
560 fn from(value: String) -> Self {
561 Self::Singular(value)
562 }
563}
564
565impl From<Vec<String>> for MsgStr {
566 fn from(values: Vec<String>) -> Self {
567 match values.len() {
568 0 => Self::None,
569 1 => Self::Singular(values.into_iter().next().expect("single msgstr value")),
570 _ => Self::Plural(values),
571 }
572 }
573}
574
575impl<'a> IntoIterator for &'a MsgStr {
576 type Item = &'a str;
577 type IntoIter = MsgStrIter<'a>;
578
579 fn into_iter(self) -> Self::IntoIter {
580 self.iter()
581 }
582}
583
584impl Index<usize> for MsgStr {
585 type Output = String;
586
587 fn index(&self, index: usize) -> &Self::Output {
588 match self {
589 Self::None => panic!("msgstr index out of bounds: no translations present"),
590 Self::Singular(value) if index == 0 => value,
591 Self::Singular(_) => panic!("msgstr index out of bounds: singular translation"),
592 Self::Plural(values) => &values[index],
593 }
594 }
595}
596
597pub struct MsgStrIter<'a> {
599 inner: MsgStrIterInner<'a>,
600}
601
602enum MsgStrIterInner<'a> {
603 Empty,
604 Single(Option<&'a str>),
605 Many(std::slice::Iter<'a, String>),
606}
607
608impl<'a> MsgStrIter<'a> {
609 const fn empty() -> Self {
610 Self {
611 inner: MsgStrIterInner::Empty,
612 }
613 }
614
615 const fn single(value: &'a str) -> Self {
616 Self {
617 inner: MsgStrIterInner::Single(Some(value)),
618 }
619 }
620
621 const fn many(iter: std::slice::Iter<'a, String>) -> Self {
622 Self {
623 inner: MsgStrIterInner::Many(iter),
624 }
625 }
626}
627
628impl<'a> Iterator for MsgStrIter<'a> {
629 type Item = &'a str;
630
631 fn next(&mut self) -> Option<Self::Item> {
632 match &mut self.inner {
633 MsgStrIterInner::Empty => None,
634 MsgStrIterInner::Single(value) => value.take(),
635 MsgStrIterInner::Many(iter) => iter.next().map(String::as_str),
636 }
637 }
638}
639
640#[derive(Debug, Clone, PartialEq, Eq)]
642#[non_exhaustive]
643pub struct SerializeOptions {
644 pub fold_length: usize,
646 pub compact_multiline: bool,
648}
649
650impl Default for SerializeOptions {
651 fn default() -> Self {
652 Self {
653 fold_length: 80,
654 compact_multiline: true,
655 }
656 }
657}
658
659impl SerializeOptions {
660 #[must_use]
662 pub fn with_fold_length(mut self, fold_length: usize) -> Self {
663 self.fold_length = fold_length;
664 self
665 }
666
667 #[must_use]
669 pub fn with_compact_multiline(mut self, compact_multiline: bool) -> Self {
670 self.compact_multiline = compact_multiline;
671 self
672 }
673}
674
675#[derive(Debug, Clone, Copy, PartialEq, Eq)]
677pub struct ParsePosition {
678 offset: usize,
679 line: usize,
680 column: usize,
681}
682
683impl ParsePosition {
684 #[must_use]
689 pub const fn new(offset: usize, line: usize, column: usize) -> Self {
690 Self {
691 offset,
692 line,
693 column,
694 }
695 }
696
697 #[must_use]
699 pub const fn offset(self) -> usize {
700 self.offset
701 }
702
703 #[must_use]
705 pub const fn line(self) -> usize {
706 self.line
707 }
708
709 #[must_use]
711 pub const fn column(self) -> usize {
712 self.column
713 }
714}
715
716#[derive(Debug, Clone, PartialEq, Eq)]
718pub struct ParseError {
719 message: String,
720 position: Option<ParsePosition>,
721}
722
723impl ParseError {
724 #[must_use]
726 pub fn new(message: impl Into<String>) -> Self {
727 Self {
728 message: message.into(),
729 position: None,
730 }
731 }
732
733 #[must_use]
735 pub fn with_position(message: impl Into<String>, position: ParsePosition) -> Self {
736 Self {
737 message: message.into(),
738 position: Some(position),
739 }
740 }
741
742 #[must_use]
744 pub fn message(&self) -> &str {
745 &self.message
746 }
747
748 #[must_use]
750 pub const fn position(&self) -> Option<ParsePosition> {
751 self.position
752 }
753
754 pub(crate) fn with_position_if_missing(mut self, position: ParsePosition) -> Self {
755 self.position.get_or_insert(position);
756 self
757 }
758}
759
760impl fmt::Display for ParseError {
761 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
762 f.write_str(&self.message)
763 }
764}
765
766impl std::error::Error for ParseError {}
767
768#[cfg(test)]
769mod tests {
770 use super::{MsgStr, ParseError, ParsePosition, PoVec, SerializeOptions};
771
772 #[cfg(feature = "serde")]
773 use super::{Header, PoFile, PoItem};
774
775 #[test]
776 fn parse_error_accessors_preserve_message_and_optional_position() {
777 let error = ParseError::new("invalid PO string");
778 assert_eq!(error.message(), "invalid PO string");
779 assert_eq!(error.position(), None);
780 assert_eq!(error.to_string(), "invalid PO string");
781
782 let position = ParsePosition::new(12, 2, 3);
783 let positioned = ParseError::with_position("invalid PO string", position);
784 assert_eq!(positioned.message(), "invalid PO string");
785 assert_eq!(positioned.position(), Some(position));
786 assert_eq!(positioned.position().map(ParsePosition::offset), Some(12));
787 assert_eq!(positioned.position().map(ParsePosition::line), Some(2));
788 assert_eq!(positioned.position().map(ParsePosition::column), Some(3));
789 assert_eq!(positioned.to_string(), "invalid PO string");
790 }
791
792 #[test]
793 fn msgstr_get_returns_none_for_empty_values() {
794 let msgstr = MsgStr::None;
795
796 assert_eq!(msgstr.get(0), None);
797 }
798
799 #[test]
800 fn msgstr_get_returns_singular_value_at_zero() {
801 let msgstr = MsgStr::from("Hallo".to_owned());
802
803 assert_eq!(msgstr.get(0), Some("Hallo"));
804 assert_eq!(msgstr.get(1), None);
805 }
806
807 #[test]
808 fn msgstr_get_returns_plural_values_by_index() {
809 let msgstr = MsgStr::from(vec!["eins".to_owned(), "viele".to_owned()]);
810
811 assert_eq!(msgstr.get(0), Some("eins"));
812 assert_eq!(msgstr.get(1), Some("viele"));
813 assert_eq!(msgstr.get(2), None);
814 }
815
816 #[test]
817 fn msgstr_plural_constructor_preserves_single_slot_shape() {
818 let values = vec!["translation".to_owned()];
819 let plural = MsgStr::plural(values.clone());
820
821 assert_eq!(plural, MsgStr::Plural(values.clone()));
822 assert_ne!(plural, MsgStr::from(values.clone()));
823 assert_eq!(plural.len(), 1);
824 assert_eq!(plural.first(), Some("translation"));
825 assert_eq!(plural.into_vec(), values);
826 }
827
828 #[test]
829 fn msgstr_helpers_cover_empty_singular_and_plural_shapes() {
830 let empty = MsgStr::from(Vec::<String>::new());
831 assert!(empty.is_empty());
832 assert_eq!(empty.len(), 0);
833 assert_eq!(empty.first(), None);
834 assert_eq!(empty.iter().count(), 0);
835 assert_eq!(empty.into_vec(), Vec::<String>::new());
836
837 let singular = MsgStr::from(vec!["Hallo".to_owned()]);
838 assert!(!singular.is_empty());
839 assert_eq!(singular.len(), 1);
840 assert_eq!(singular.first(), Some("Hallo"));
841 assert_eq!((&singular).into_iter().collect::<Vec<_>>(), vec!["Hallo"]);
842 assert_eq!(singular[0], "Hallo");
843 assert_eq!(singular.into_vec(), vec!["Hallo"]);
844
845 let plural = MsgStr::from(vec!["eins".to_owned(), "viele".to_owned()]);
846 assert_eq!(plural.len(), 2);
847 assert_eq!(plural.first(), Some("eins"));
848 assert_eq!(plural.iter().collect::<Vec<_>>(), vec!["eins", "viele"]);
849 assert_eq!(plural[1], "viele");
850 assert_eq!(plural.into_vec(), vec!["eins", "viele"]);
851 }
852
853 #[test]
854 fn povec_keeps_slice_iteration_and_vec_conversion_ergonomics() {
855 let mut values = PoVec::new();
856 assert!(values.is_empty());
857
858 values.push("src/app.rs:10".to_owned());
859 values.extend(["src/app.rs:20".to_owned()]);
860
861 assert_eq!(values.len(), 2);
862 assert_eq!(
863 values.as_slice(),
864 ["src/app.rs:10".to_owned(), "src/app.rs:20".to_owned()]
865 );
866 assert_eq!(
867 values.iter().map(String::as_str).collect::<Vec<_>>(),
868 ["src/app.rs:10", "src/app.rs:20"]
869 );
870
871 let from_vec = PoVec::from(vec!["fuzzy".to_owned()]);
872 assert_eq!(from_vec, vec!["fuzzy".to_owned()]);
873 assert_eq!(Vec::<String>::from(from_vec), vec!["fuzzy".to_owned()]);
874 }
875
876 #[test]
877 fn povec_trait_views_cover_mutable_slice_and_reference_iteration() {
878 let mut values = PoVec::with_capacity(2);
879 values.extend([1, 2]);
880
881 assert_eq!(AsRef::<[i32]>::as_ref(&values), [1, 2]);
882
883 AsMut::<[i32]>::as_mut(&mut values)[0] = 3;
884 values.as_mut_slice()[1] = 4;
885 for value in &mut values {
886 *value += 1;
887 }
888 values.iter_mut().for_each(|value| *value *= 2);
889
890 let as_slice: &[i32] = &values;
891 assert_eq!(as_slice, [8, 10]);
892
893 let as_mut_slice: &mut [i32] = &mut values;
894 as_mut_slice[0] += 1;
895
896 let expected = [9, 10];
897 assert!(values == expected[..]);
898 assert!(values == expected.as_slice());
899 }
900
901 #[test]
902 fn povec_owned_iterator_preserves_values_without_exposing_backing_type() {
903 let values = PoVec::from(["one".to_owned(), "other".to_owned()]);
904
905 assert_eq!(
906 values.into_iter().collect::<Vec<_>>(),
907 vec!["one".to_owned(), "other".to_owned()]
908 );
909 }
910
911 #[test]
912 fn povec_owned_iterator_supports_double_ended_size_hints() {
913 let mut iter = PoVec::from([1, 2, 3]).into_iter();
914
915 assert_eq!(iter.size_hint(), (3, Some(3)));
916 assert_eq!(iter.next_back(), Some(3));
917 assert_eq!(iter.size_hint(), (2, Some(2)));
918 assert_eq!(iter.next(), Some(1));
919 assert_eq!(iter.next_back(), Some(2));
920 assert_eq!(iter.next(), None);
921 assert_eq!(iter.next_back(), None);
922 }
923
924 #[test]
925 fn serialize_option_builders_set_fields() {
926 let options = SerializeOptions::default()
927 .with_fold_length(120)
928 .with_compact_multiline(false);
929
930 assert_eq!(options.fold_length, 120);
931 assert!(!options.compact_multiline);
932 }
933
934 #[cfg(feature = "serde")]
935 #[test]
936 fn po_file_serde_round_trips_owned_document_shape() {
937 let file = PoFile {
938 comments: vec!["translator note".to_owned()],
939 headers: vec![Header {
940 key: "Language".to_owned(),
941 value: "de".to_owned(),
942 }],
943 items: vec![PoItem {
944 msgid: "Hello".to_owned(),
945 msgstr: MsgStr::from("Hallo".to_owned()),
946 references: vec!["src/app.rs:10".to_owned()].into(),
947 nplurals: 1,
948 ..PoItem::default()
949 }],
950 ..PoFile::default()
951 };
952
953 let json = serde_json::to_value(&file).expect("PO file serialization must succeed");
954 assert_eq!(json["items"][0]["msgstr"]["kind"], "singular");
955 assert_eq!(json["items"][0]["msgstr"]["value"], "Hallo");
956
957 let roundtrip: PoFile =
958 serde_json::from_value(json).expect("PO file deserialization must succeed");
959 assert_eq!(roundtrip, file);
960 }
961}