diff options
author | zaaarf <me@zaaarf.foo> | 2024-02-12 18:52:47 +0100 |
---|---|---|
committer | zaaarf <me@zaaarf.foo> | 2024-02-12 18:52:47 +0100 |
commit | 0a759cfc2977cee65c2acc0845b3c471c401a3d3 (patch) | |
tree | 831f0723ebdf4571988e550b8c19bd2609eb3109 |
feat: initial, basic implementation
-rw-r--r-- | .editorconfig | 6 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Cargo.toml | 11 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | src/error.rs | 33 | ||||
-rw-r--r-- | src/lib.rs | 79 |
6 files changed, 133 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..07e215e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = tab +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c36d7ba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fluent-fluently" +version = "0.1.0" +edition = "2021" + +[dependencies] +fluent = "0.16.0" +fluent-syntax = "0.11.0" +intl-memoizer = "0.5.1" +unic-langid = "0.9.4" +walkdir = "2.4.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..6efa688 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Fluent, fluently +I found myself needing to do localisation, one time. I decided I'd want it to happen at runtime, so that any spelling fixes may be applied without needing to recompile the whole program. [Fluent](https://github.com/projectfluent/fluent-rs) provides an API, but no real way to use it. I couldn't find any existing implementation fitting my use case, so I wrote this. diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..04541a5 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,33 @@ +use std::result::Result as StdResult; + +use fluent::FluentResource; + +pub type Result<T> = StdResult<T, Error>; + +pub enum Error { + IoError(String) +} + +impl From<std::io::Error> for Error { + fn from(value: std::io::Error) -> Self { + todo!() + } +} + +impl From<(FluentResource, Vec<fluent_syntax::parser::ParserError>)> for Error { + fn from(value: (FluentResource, Vec<fluent_syntax::parser::ParserError>)) -> Self { + todo!() + } +} + +impl From<Vec<fluent::FluentError>> for Error { + fn from(value: Vec<fluent::FluentError>) -> Self { + todo!() + } +} + +impl From<unic_langid::LanguageIdentifierError> for Error { + fn from(value: unic_langid::LanguageIdentifierError) -> Self { + todo!() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..49cc13a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,79 @@ +use std::{collections::HashMap, sync::Arc}; +use fluent::{bundle::FluentBundle, FluentResource}; +use intl_memoizer::concurrent::IntlLangMemoizer; +use unic_langid::LanguageIdentifier; +use crate::error::Result; + +pub mod error; + +type TypedFluentBundle = FluentBundle<Arc<FluentResource>, IntlLangMemoizer>; +pub struct Localiser { + bundles: HashMap<LanguageIdentifier, TypedFluentBundle>, + default_language: LanguageIdentifier +} + +impl Localiser { + pub fn try_load(path: String, default_language: String) -> Result<Self> { + let mut bundles = HashMap::new(); + let paths = std::fs::read_dir(path)? + .filter_map(|res| res.ok()) + .map(|dir_entry| dir_entry.path()) + .filter_map(|path| { + if path.extension().map_or(false, |ext| ext == "ftl") || path.is_dir() { + Some(path) + } else { + None + } + }).collect::<Vec<_>>(); + + //TODO load default first and call bundle.add_resource_overriding(default_bundle) on others + let default_language = default_language.parse::<LanguageIdentifier>()?; + + for path in paths { + // validate filename as language code + let language_code = path.file_stem() + .and_then(|f| f.to_str()) + .map(|f| f.parse::<LanguageIdentifier>()) + .and_then(|id| match id { + Ok(id) => Some(id), + Err(_) => None + }); + + if language_code.is_none() { + continue; + } + + let language_code = language_code.unwrap(); + + let mut bundle: TypedFluentBundle = fluent::bundle::FluentBundle::new_concurrent(vec![language_code.clone()]); + if path.is_dir() { //is a directory + for res in Self::path_to_resources(&path)? { + bundle.add_resource(res)?; + } + } else { //is a single file + bundle.add_resource(Self::file_to_resource(&path)?)?; + } + + bundles.insert(language_code, bundle); + } + + Ok(Self { + bundles, + default_language + }) + } + + fn path_to_resources(path: &std::path::PathBuf) -> Result<Vec<Arc<FluentResource>>> { + let mut res = Vec::new(); + for entry in walkdir::WalkDir::new(path).follow_links(true).into_iter().filter_map(|e| e.ok()) { + let entry_path = entry.path().to_path_buf(); + res.push(Self::file_to_resource(&entry_path)?); + } + Ok(res) + } + + fn file_to_resource(path: &std::path::PathBuf) -> Result<Arc<FluentResource>> { + let content = std::fs::read_to_string(path)?; + Ok(Arc::new(FluentResource::try_new(content)?)) + } +} |