Skip to main content

langcodec/
lib.rs

1#![forbid(unsafe_code)]
2//! Universal localization file toolkit for Rust.
3//!
4//! Supports parsing, writing, and converting between Apple `.strings`, `.xcstrings`, Android `strings.xml`, and CSV files.  
5//! All conversion happens through the unified `Resource` model.
6//!
7//! # Quick Start
8//!
9//! ```rust,no_run
10//! use langcodec::{Codec, convert_auto};
11//!
12//! // Convert between formats automatically
13//! convert_auto("en.lproj/Localizable.strings", "strings.xml")?;
14//!
15//! // Or work with the unified Resource model
16//! let mut codec = Codec::new();
17//! codec.read_file_by_extension("en.lproj/Localizable.strings", None)?;
18//! codec.write_to_file()?;
19//!
20//! // Or use the builder pattern for fluent construction
21//! let codec = Codec::builder()
22//!     .add_file("en.lproj/Localizable.strings")?
23//!     .add_file("fr.lproj/Localizable.strings")?
24//!     .add_file("values-es/strings.xml")?
25//!     .read_file_by_extension("de.strings", Some("de".to_string()))?
26//!     .build();
27//! # Ok::<(), Box<dyn std::error::Error>>(())
28//! ```
29//!
30//! # Supported Formats
31//!
32//! - **Apple `.strings`**: Traditional iOS/macOS localization files
33//! - **Apple `.xcstrings`**: Modern Xcode localization format with plural support
34//! - **Android `strings.xml`**: Android resource files
35//! - **CSV**: Comma-separated values for simple key-value pairs
36//!
37//! # Features
38//!
39//! - ✨ Parse, write, convert, and merge multiple localization file formats
40//! - 🦀 Idiomatic, modular, and ergonomic Rust API
41//! - 📦 Designed for CLI tools, CI/CD pipelines, and library integration
42//! - 🔄 Unified internal model (`Resource`) for lossless format-agnostic processing
43//! - 📖 Well-documented, robust error handling and extensible codebase
44//!
45//! # Examples
46//!
47//! ## Basic Format Conversion
48//! ```rust,no_run
49//! use langcodec::convert_auto;
50//!
51//! // Convert Apple .strings to Android XML
52//! convert_auto("en.lproj/Localizable.strings", "values-en/strings.xml")?;
53//!
54//! // Convert to CSV for analysis
55//! convert_auto("Localizable.xcstrings", "translations.csv")?;
56//! # Ok::<(), langcodec::Error>(())
57//! ```
58//!
59//! ## Working with Resources
60//! ```rust,no_run
61//! use langcodec::{Codec, types::Entry};
62//!
63//! // Load multiple files with the builder pattern
64//! let codec = Codec::builder()
65//!     .add_file("en.lproj/Localizable.strings")?
66//!     .add_file("fr.lproj/Localizable.strings")?
67//!     .add_file("values-es/strings.xml")?
68//!     .build();
69//!
70//! // Find specific translations
71//! if let Some(en_resource) = codec.get_by_language("en") {
72//!     if let Some(entry) = en_resource.entries.iter().find(|e| e.id == "welcome") {
73//!         println!("Welcome message: {}", entry.value);
74//!     }
75//! }
76//! # Ok::<(), langcodec::Error>(())
77//! ```
78//!
79//! ## Modifying Translations
80//! ```rust,no_run
81//! use langcodec::{Codec, types::{Translation, EntryStatus}};
82//!
83//! let mut codec = Codec::builder()
84//!     .add_file("en.lproj/Localizable.strings")?
85//!     .add_file("fr.lproj/Localizable.strings")?
86//!     .build();
87//!
88//! // Update an existing translation
89//! codec.update_translation(
90//!     "welcome_message",
91//!     "en",
92//!     Translation::Singular("Hello, World!".to_string()),
93//!     Some(EntryStatus::Translated)
94//! )?;
95//!
96//! // Add a new translation
97//! codec.add_entry(
98//!     "new_feature",
99//!     "en",
100//!     Translation::Singular("Check out our new feature!".to_string()),
101//!     Some("Promotional message for new feature".to_string()),
102//!     Some(EntryStatus::New)
103//! )?;
104//!
105//! // Copy a translation from one language to another
106//! codec.copy_entry("welcome_message", "en", "fr", true)?;
107//!
108//! // Find all translations for a key
109//! for (resource, entry) in codec.find_entries("welcome_message") {
110//!     println!("{}: {}", resource.metadata.language, entry.value);
111//! }
112//!
113//! // Validate the codec
114//! if let Err(validation_error) = codec.validate() {
115//!     eprintln!("Validation failed: {}", validation_error);
116//! }
117//! # Ok::<(), langcodec::Error>(())
118//! ```
119//!
120//! ## Batch Processing
121//! ```rust,no_run
122//! use langcodec::Codec;
123//! use std::path::Path;
124//!
125//! let mut codec = Codec::new();
126//!
127//! // Load all localization files in a directory
128//! for entry in std::fs::read_dir("locales")? {
129//!     let path = entry?.path();
130//!     if path.extension().and_then(|s| s.to_str()) == Some("strings") {
131//!         codec.read_file_by_extension(&path, None)?;
132//!     }
133//! }
134//!
135//! // Write all resources back to their original formats
136//! codec.write_to_file()?;
137//! # Ok::<(), langcodec::Error>(())
138//! ```
139
140pub mod builder;
141pub mod codec;
142pub mod converter;
143pub mod error;
144pub mod formats;
145pub mod normalize;
146pub mod operations;
147pub mod placeholder;
148pub mod plural_rules;
149pub mod provenance;
150pub mod read_options;
151pub mod traits;
152pub mod types;
153
154// Re-export most used types for easy consumption
155pub use crate::{
156    builder::CodecBuilder,
157    codec::Codec,
158    converter::{
159        convert, convert_auto, convert_auto_with_normalization, convert_resources_to_format,
160        convert_with_normalization, infer_format_from_extension, infer_format_from_path,
161        infer_language_from_path, merge_resources,
162    },
163    error::{Error, ErrorCode, ErrorContext, StructuredError},
164    formats::FormatType,
165    normalize::{KeyStyle, NormalizeOptions, NormalizeReport, normalize_codec},
166    operations::{
167        DiffChangedItem, DiffOptions, DiffReport, DiffSummary, LanguageDiff, SyncIssue,
168        SyncIssueKind, SyncOptions, SyncReport, diff_resources, sync_existing_entries,
169    },
170    placeholder::{extract_placeholders, normalize_placeholders, signature},
171    plural_rules::{
172        PluralValidationReport, autofix_fill_missing_from_other_resource,
173        collect_resource_plural_issues, required_categories_for_str, validate_resource_plurals,
174    },
175    provenance::{
176        PROVENANCE_PREFIX, ProvenanceRecord, entry_provenance, resource_provenance,
177        set_entry_provenance, set_resource_provenance,
178    },
179    read_options::ReadOptions,
180    types::{
181        ConflictStrategy, Entry, EntryStatus, Metadata, Plural, PluralCategory, Resource,
182        Translation,
183    },
184};