1 stable release

Uses old Rust 2015

new 1.0.0 May 3, 2025

#55 in Internationalization (i18n)

MIT/Apache

170KB
4K SLoC

spiritless_po

spiritless_po for Rust is a kind of gettext library for Rust and inspired by spirit-po. Original C++ version of spiritless_po doesn't depend on Boost library. Similarly, this library has no dependencies.

spiritless_po has some features (same as spirit-po):

  • The catalog can read the messages from multiple PO files, instead of a single MO file. You can add new messages to a single catalog any number of times.
  • A catalog handles only one textdomain and only one language, doesn't handle multiple textdomains and multiple languages. You can use multiple catalogs to handle them.
  • The catalog doesn't care the locale.
  • The catalog doesn't handle the character encoding. The input text must be encoded by UTF-8.

spiritless_po is designed for the software that has capability to extend it with user made contents, including translation plugins that provide the translation for the software and/or some user made contents.

Example

Read from a string:

    let mut catalog = spiritless_po::Catalog::new();
    let result = catalog.add(&mut r#"
msgid ""
msgstr "Plural-Forms: nplurals=2; plural=n != 1;\n"

msgid "apple"
msgstr "APPLE"

msgid "banana"
msgid_plural "bananas"
msgstr[0] "BANANA"
msgstr[1] "BANANAS"

msgctxt "food"
msgid "cherry"
msgstr "CHERRY"

msgctxt "food"
msgid "durian"
msgid_plural "durians"
msgstr[0] "DURIAN"
msgstr[1] "DURIANS"
"#.chars());
    assert!(matches!(result, Ok(_)));
    assert_eq!(catalog.gettext("apple"), "APPLE");
    assert_eq!(catalog.ngettext("banana", "bananas", 1), "BANANA");
    assert_eq!(catalog.ngettext("banana", "bananas", 2), "BANANAS");
    assert_eq!(catalog.pgettext("food", "cherry"), "CHERRY");
    assert_eq!(catalog.npgettext("food", "durian", "durians", 1), "DURIAN");
    assert_eq!(catalog.npgettext("food", "durian", "durians", 2), "DURIANS");
    assert_eq!(catalog.statistics().total(), 5);
    assert_eq!(catalog.statistics().metadata(), 1);
    assert_eq!(catalog.statistics().translated(), 4);
    assert_eq!(catalog.statistics().discarded(), 0);

Read a file and dump all PO entires:

extern crate spiritless_po;
use std::io::Read;

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() <= 1 {
        eprintln!("This program needs one filename.");
        std::process::exit(1);
    }

    let filename = &args[1];
    let f = match std::fs::File::open(filename) {
        Ok(f_ok) => f_ok,
        Err(_) => {
            eprintln!("The file \"{}\" cannot be opened.", filename);
            std::process::exit(1);
        }
    };
    let reader = std::io::BufReader::new(f);
    let mut b_it = reader.bytes();
    let mut catalog = spiritless_po::Catalog::new();
    let result = catalog.add_bytes(&mut b_it);

    println!("String Table:");
    for (i, s) in catalog.string_table().iter().enumerate() {
        println!("{}: {}", i, s);
    }
    println!("Index:");
    for (k, v) in catalog.index().iter() {
        let idx = v.string_table_index();
        let total = v.total_plurals() as usize;
        if total == 1 {
            println!("{}: {}", k, idx);
        } else {
            println!("{}: {} - {}", k, idx, idx + total - 1);
        }
    }
    println!("Metadata:");
    for (k, v) in catalog.metadata().iter() {
        println!("{}: {}", k, v);
    }
    println!("Errors:");
    if let Err(err) = result {
        for msg in err.error_messages().iter() {
            eprintln!("{}", msg);
        }
    }
    println!("Statistics:");
    println!("  Total: {}", catalog.statistics().total());
    println!("  Metadata: {}", catalog.statistics().metadata());
    println!("  Translated: {}", catalog.statistics().translated());
    println!("  Discarded: {}", catalog.statistics().discarded());
}

License

Licensed under either of

at your option.

Copyright © 2025 OOTA, Masato

This file is part of spiritless_po for Rust.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

OR

Licensed under the Apache License, Version 2.0 (the "License"); you may not use spiritless_po for Rust except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

No runtime deps

Features