diff --git a/l10n-db/i18n/SaveSettings.toml b/l10n-db/i18n/SaveSettings.toml index c2a4d1c..3f8d37d 100644 --- a/l10n-db/i18n/SaveSettings.toml +++ b/l10n-db/i18n/SaveSettings.toml @@ -5,3 +5,18 @@ description = "This is a label on a button which will save the settings when cli locale = "en" content = "Save Settings" modified = "2025-02-22T23:44:18.874218939Z" + +[variants.eo] +locale = "eo" +content = "Konservi Agordojn" +modified = "2025-02-24T19:32:11.246639077Z" + +[variants.de] +locale = "de" +content = "Einstellungen Speichern" +modified = "2025-02-24T19:33:19.516005843Z" + +[variants.es] +locale = "es" +content = "Guardar Configuraciones" +modified = "2025-02-24T19:33:23.861329923Z" diff --git a/l10n-db/i18n/TimeDistance.toml b/l10n-db/i18n/TimeDistance.toml index 2e541f1..3525af3 100644 --- a/l10n-db/i18n/TimeDistance.toml +++ b/l10n-db/i18n/TimeDistance.toml @@ -5,3 +5,18 @@ description = "A summary of a workout or many workouts that involve a time and a locale = "en" content = "{distance} of {activity} in {hours, plural, =1 {{hours} hour} other {{hours} hours}} and {minutes, plural, =1 {{minutes} minute} other {{minutes} minutes}}" modified = "2025-02-24T14:09:17.361641899Z" + +[variants.eo] +locale = "eo" +content = "{distance} de {activity} en {hours, plural, =1 {{hours} horo} other {{hours} horoj}} {minutes, plural, =1 {{minutes} minuto} other {{minutes} minutoj}}" +modified = "2025-02-24T19:32:11.246943602Z" + +[variants.de] +locale = "de" +content = "{distance} von {activity} in {hours, plural, one {}=1 {{hours} Stunde} other {{hours} Stunden}} und {minutes, plural, one {}=1 {{minutes} Minute} other {{minutes} Minuten}}" +modified = "2025-02-24T19:33:19.516210807Z" + +[variants.es] +locale = "es" +content = "{distance} de {activity} en {hours, plural, one {}=1 {{hours} hora} other {{hours} horas}} y {minutes, plural, one {}=1 {{minutes} minuto} other {{minutes} minutos}}" +modified = "2025-02-24T19:33:23.861604738Z" diff --git a/l10n-db/i18n/Welcome.toml b/l10n-db/i18n/Welcome.toml index 556c993..47cc700 100644 --- a/l10n-db/i18n/Welcome.toml +++ b/l10n-db/i18n/Welcome.toml @@ -1,7 +1,22 @@ key = "Welcome" description = "This is a welcome content that will be shown on first app opening, before configuration." +[variants.eo] +locale = "eo" +content = "Bonvenon al FitnessTrax" +modified = "2025-02-24T19:32:11.246407627Z" + [variants.en] locale = "en" content = "Welcome to FitnessTrax" modified = "2025-02-22T23:43:24.786544124Z" + +[variants.es] +locale = "es" +content = "Bienvenido a FitnessTrax" +modified = "2025-02-24T19:33:23.861143003Z" + +[variants.de] +locale = "de" +content = "Willkommen bei FitnessTrax" +modified = "2025-02-24T19:33:19.515861453Z" diff --git a/l10n-db/src/bin/main.rs b/l10n-db/src/bin/main.rs index d1b304e..f7cbd54 100644 --- a/l10n-db/src/bin/main.rs +++ b/l10n-db/src/bin/main.rs @@ -7,7 +7,7 @@ use std::{ use clap::{Parser, Subcommand}; use icu_locid::{langid, LanguageIdentifier}; -use l10n_db::{self, js, read_file, xliff, Bundle, Editor, ReadError}; +use l10n_db::{self, js, read_file, xliff::{self, import_file}, Bundle, Editor, ReadError}; use serde::Deserialize; #[derive(Parser)] @@ -30,6 +30,10 @@ enum Commands { ListKeys, // Search the database // Search { }, + Import { + #[arg(short, long)] + file: String, + }, /// Export the database Export { #[arg(short, long)] @@ -74,6 +78,10 @@ fn main() { println!("{}", key); } } + Some(Commands::Import { file }) => { + import_file(&mut bundle, &PathBuf::from(file)).unwrap(); + bundle.save(); + } Some(Commands::Export { format, locale }) => { let locale = locale.as_ref().map(|l| l.clone().parse::().unwrap()).unwrap_or(langid!("en")); diff --git a/l10n-db/src/formats/js.rs b/l10n-db/src/formats/js.rs index c8c5844..79d68cb 100644 --- a/l10n-db/src/formats/js.rs +++ b/l10n-db/src/formats/js.rs @@ -16,7 +16,7 @@ pub fn export_fh(bundle: &Bundle, locale: LanguageIdentifier, fh: &mut File) -> }).collect::>(); let messages: BTreeMap = messages.into_iter().collect(); - fh.write(serde_json::to_string(&messages).unwrap().as_bytes()).unwrap(); + fh.write(serde_json::to_string_pretty(&messages).unwrap().as_bytes()).unwrap(); Ok(()) } diff --git a/l10n-db/src/formats/xliff.rs b/l10n-db/src/formats/xliff.rs index 075971b..cbde7a3 100644 --- a/l10n-db/src/formats/xliff.rs +++ b/l10n-db/src/formats/xliff.rs @@ -1,9 +1,24 @@ -use std::{fs::File, io, path::Path}; +use std::{ + collections::HashMap, + fs::File, + io::{self, BufReader, Read, Write}, + path::Path, +}; -use icu_locid::LanguageIdentifier; -use xml::{writer::XmlEvent, EmitterConfig, EventWriter}; +use chrono::{DateTime, Utc}; +use icu_locid::{langid, LanguageIdentifier}; +use xml::{attribute::OwnedAttribute, reader, writer, EmitterConfig, EventReader, EventWriter}; -use crate::{Bundle, Message, WriteError}; +use crate::{Bundle, Message, ReadError, WriteError}; + +struct PartialMessage { + variants: HashMap, +} + +struct PartialVariant { + content: Option, + modified: Option>, +} pub fn export_file( bundle: &Bundle, @@ -14,33 +29,93 @@ pub fn export_file( export_fh(bundle, locale, &mut file) } -pub fn export_fh( - bundle: &Bundle, - locale: LanguageIdentifier, - fh: &mut File, -) -> Result<(), WriteError> { - let mut writer = EmitterConfig::new() - .perform_indent(true) - .create_writer(fh); +pub fn export_fh(bundle: &Bundle, locale: LanguageIdentifier, fh: W) -> Result<(), WriteError> +where + W: Write, +{ + let mut writer = EmitterConfig::new().perform_indent(true).create_writer(fh); writer .write( - XmlEvent::start_element("xliff") + writer::XmlEvent::start_element("xliff") .attr("xmlns", "urn:oasis:names:tc:xliff:document:2.0") .attr("version", "2.0") .attr("srcLang", &format!("{}", locale)), ) .unwrap(); writer - .write(XmlEvent::start_element("file").attr("id", "main")) + .write(writer::XmlEvent::start_element("file").attr("id", "main")) .unwrap(); for (key, message) in bundle.message_iter() { write_message(&mut writer, key, message, &locale); } - writer.write(XmlEvent::end_element()).unwrap(); - writer.write(XmlEvent::end_element()).unwrap(); + writer.write(writer::XmlEvent::end_element()).unwrap(); + writer.write(writer::XmlEvent::end_element()).unwrap(); + Ok(()) +} + +pub fn import_file(bundle: &mut Bundle, path: &Path) -> Result<(), ReadError> { + let file = File::open(path).unwrap(); + let file = BufReader::new(file); + + import_reader(bundle, file) +} + +pub fn import_reader(bundle: &mut Bundle, fh: R) -> Result<(), ReadError> +where + R: Read, +{ + let parser = EventReader::new(fh); + + let mut locale: LanguageIdentifier = langid!("en"); + let mut current_key = None; + let mut current_text: Option = None; + let mut in_target = false; + + for event in parser { + match event { + Ok(reader::XmlEvent::StartElement { + name, attributes, .. + }) => match name.local_name.as_ref() { + "xliff" => { + locale = find_attribute(&attributes, "trgLang") + .unwrap() + .parse::() + .unwrap(); + } + "unit" => current_key = find_attribute(&attributes, "id"), + "target" => in_target = true, + _ => println!("name: {}", name), + }, + Ok(reader::XmlEvent::EndElement { name }) => match name.local_name.as_ref() { + "unit" => { + if let Some(key) = current_key { + let message = bundle.message(key); + let variant = message.variant_mut(locale.clone()); + if let Some(ref text) = current_text { + variant.set_content(text.clone()); + } + } + current_key = None; + } + "target" => in_target = false, + _ => {} + }, + Ok(reader::XmlEvent::Characters(data)) => { + if in_target { + current_text = Some(data) + } + } + Err(e) => { + eprintln!("error: {e}"); + break; + } + _ => {} + } + } + Ok(()) } @@ -53,19 +128,30 @@ fn write_message( T: std::io::Write, { [ - XmlEvent::start_element("unit").attr("id", key).into(), - XmlEvent::start_element("notes").into(), - XmlEvent::start_element("note").into(), - XmlEvent::characters(message.description()).into(), - XmlEvent::end_element().into(), - XmlEvent::end_element().into(), - XmlEvent::start_element("segment").into(), - XmlEvent::start_element("source").into(), - XmlEvent::characters(message.variant(locale).unwrap().content()).into(), - XmlEvent::end_element().into(), - XmlEvent::end_element().into(), - XmlEvent::end_element().into(), + writer::XmlEvent::start_element("unit") + .attr("id", key) + .into(), + writer::XmlEvent::start_element("notes").into(), + writer::XmlEvent::start_element("note").into(), + writer::XmlEvent::characters(message.description()).into(), + writer::XmlEvent::end_element().into(), + writer::XmlEvent::end_element().into(), + writer::XmlEvent::start_element("segment").into(), + writer::XmlEvent::start_element("source").into(), + writer::XmlEvent::characters(message.variant(locale).unwrap().content()).into(), + writer::XmlEvent::end_element().into(), + writer::XmlEvent::end_element().into(), + writer::XmlEvent::end_element().into(), ] .into_iter() - .for_each(|elem: XmlEvent| writer.write(elem).unwrap()); + .for_each(|elem: writer::XmlEvent| writer.write(elem).unwrap()); +} + +fn find_attribute(attrs: &Vec, name: &str) -> Option { + for f in attrs { + if name == f.name.local_name { + return Some(f.value.clone()); + } + } + None }