Got universal strings to notify for missing translations.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s

This commit is contained in:
2026-03-16 13:30:55 -04:00
parent 2f078bdf32
commit 92c5ac768b
4 changed files with 147 additions and 12 deletions

View File

@@ -10,6 +10,6 @@ use record::{InternalRecord, InternalRecords, Oid};
pub use clock::Clock; pub use clock::Clock;
pub use create::{CreateDoc, IndexType}; pub use create::{CreateDoc, IndexType};
pub use definition::{DocDef, DocFuncType}; pub use definition::{DocDef, DocFuncType};
pub use field::{Field, FieldType}; pub use field::{Field, FieldType, MissingTranslation};
pub use record::{Record, Records}; pub use record::{Record, Records};
pub use session::Session; pub use session::Session;

View File

@@ -1,3 +1,4 @@
use crate::{ErrorID, MTTError};
use chrono::prelude::*; use chrono::prelude::*;
use isolang::Language; use isolang::Language;
use std::{ use std::{
@@ -577,10 +578,96 @@ mod paragraphs {
} }
} }
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct ParagraphID {
data: Uuid,
}
impl ParagraphID {
fn new() -> Self {
Self {
data: Uuid::new_v4(),
}
}
fn nil() -> Self {
Self { data: Uuid::nil() }
}
}
#[cfg(test)]
mod paragraphids {
use super::*;
#[test]
fn are_paragraph_ids_unique() {
let id1 = ParagraphID::new();
let id2 = ParagraphID::new();
assert_ne!(id1, id2);
}
#[test]
fn can_create_nil() {
let id = ParagraphID::nil();
assert_eq!(id.data, Uuid::nil());
}
}
#[derive(Clone, Debug)]
pub struct MissingTranslation {
needs: Language,
has: Language,
text: String,
}
impl MissingTranslation {
fn new(needs: Language, has: Language, text: String) -> Self {
Self {
needs: needs,
has: has,
text: text,
}
}
fn needs(&self) -> Language {
self.needs.clone()
}
fn has(&self) -> Language {
self.has.clone()
}
fn text(&self) -> String {
self.text.clone()
}
}
#[cfg(test)]
mod missing_translations {
use super::*;
#[test]
fn can_get_mising_translation_information() {
let langs = [
Language::from_639_1("en").unwrap(),
Language::from_639_1("ja").unwrap(),
];
let text = Uuid::new_v4().to_string();
let missing = MissingTranslation::new(langs[0].clone(), langs[1].clone(), text.clone());
assert_eq!(missing.needs(), langs[0]);
assert_eq!(missing.has(), langs[1]);
assert_eq!(missing.text(), text);
let missing2 = MissingTranslation::new(langs[1].clone(), langs[0].clone(), text.clone());
assert_eq!(missing2.needs(), langs[1]);
assert_eq!(missing2.has(), langs[0]);
assert_eq!(missing2.text(), text);
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct UniversalString { struct UniversalString {
paragraphs: HashMap<Uuid, Paragraph>, paragraphs: HashMap<ParagraphID, Paragraph>,
revisions: Vec<Vec<Uuid>>, revisions: Vec<Vec<ParagraphID>>,
} }
impl UniversalString { impl UniversalString {
@@ -593,20 +680,36 @@ impl UniversalString {
output output
} }
fn get(&self, lang: &Language) -> Option<String> { fn get(&self, lang: &Language) -> Result<String, MTTError> {
let latest = self.revisions.len() - 1; let latest = self.revisions.len() - 1;
self.get_revision(latest, lang) self.get_revision(latest, lang)
} }
fn get_revision(&self, rev_num: usize, lang: &Language) -> Option<String> { fn get_revision(&self, rev_num: usize, lang: &Language) -> Result<String, MTTError> {
let mut output = "".to_string(); let mut output = "".to_string();
let mut missing: Vec<MissingTranslation> = Vec::new();
for id in self.revisions[rev_num].iter() { for id in self.revisions[rev_num].iter() {
let paragraph = self.paragraphs.get(id).unwrap(); let paragraph = self.paragraphs.get(id).unwrap();
let text = paragraph.get(lang).unwrap(); let text = match paragraph.get(lang) {
Some(data) => data,
None => {
let (ori_lang, text) = paragraph.get_initial();
missing.push(MissingTranslation::new(
lang.clone(),
ori_lang.clone(),
text.clone(),
));
""
}
};
output += text; output += text;
output += "\u{2029}"; output += "\u{2029}";
} }
Some(output) if missing.is_empty() {
Ok(output)
} else {
Err(MTTError::new(ErrorID::MissingTranslation(missing)))
}
} }
fn revision_count(&self) -> usize { fn revision_count(&self) -> usize {
@@ -617,17 +720,17 @@ impl UniversalString {
let mut version = Vec::new(); let mut version = Vec::new();
for paragraph in text.as_str().split("\u{2029}") { for paragraph in text.as_str().split("\u{2029}") {
if paragraph != "" { if paragraph != "" {
let mut id = Uuid::nil(); let mut id = ParagraphID::nil();
for (key, value) in self.paragraphs.iter() { for (key, value) in self.paragraphs.iter() {
if &paragraph == value.get(&lang).unwrap() { if &paragraph == value.get(&lang).unwrap() {
id = key.clone(); id = key.clone();
break; break;
} }
} }
if id == Uuid::nil() { if id == ParagraphID::nil() {
id = Uuid::new_v4(); id = ParagraphID::new();
while self.paragraphs.contains_key(&id) { while self.paragraphs.contains_key(&id) {
id = Uuid::new_v4(); id = ParagraphID::new();
} }
self.paragraphs.insert( self.paragraphs.insert(
id.clone(), id.clone(),
@@ -645,6 +748,7 @@ impl UniversalString {
mod universal_strings { mod universal_strings {
use super::*; use super::*;
use rand::random_range; use rand::random_range;
use std::collections::HashSet;
const ENGLISH_DATA: [&str; 5] = ["one", "two", "three", "four", "five"]; const ENGLISH_DATA: [&str; 5] = ["one", "two", "three", "four", "five"];
const JAPANESE_DATA: [&str; 5] = ["", "", "", "", ""]; const JAPANESE_DATA: [&str; 5] = ["", "", "", "", ""];
@@ -764,4 +868,32 @@ mod universal_strings {
assert_eq!(ustr.paragraphs.len(), expected_paragraphs, "{:?}", ustr); assert_eq!(ustr.paragraphs.len(), expected_paragraphs, "{:?}", ustr);
assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial); assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial);
} }
#[test]
fn can_translation_be_added() {
let (elang, edata) = TestData::english();
let (jlang, jdata) = TestData::japanese();
let initial = TestData::to_input(jdata.clone());
let mut ustr = UniversalString::new(jlang.clone(), initial.clone());
assert_eq!(ustr.get(&jlang).unwrap(), initial);
let err = ustr.get(&elang).unwrap_err();
match err.get_error_ids().iter().last().unwrap() {
ErrorID::MissingTranslation(missing) => {
assert_eq!(
missing.len(),
jdata.len(),
"should return list of translations needed"
);
let mut holder: HashSet<String> = jdata.iter().cloned().collect();
for data in missing.iter() {
assert_eq!(data.needs(), elang, "needed language is incorrect");
assert_eq!(data.has(), jlang, "original language is incorrect");
assert!(holder.contains(&data.text()));
holder.remove(&data.text());
}
assert!(holder.is_empty(), "still had {:?}", holder);
}
_ => unreachable!("got {:?}, should have been needs translation", err),
}
}
} }

View File

@@ -5,7 +5,6 @@ mod mtterror;
pub mod name; pub mod name;
mod queue; mod queue;
pub use action::*;
use document::{Clock, CreateDoc, Session}; use document::{Clock, CreateDoc, Session};
use message::{wrapper::Message, MessageAction}; use message::{wrapper::Message, MessageAction};
use queue::{ use queue::{
@@ -18,6 +17,8 @@ use std::{
}; };
use uuid::Uuid; use uuid::Uuid;
pub use action::*;
pub use document::MissingTranslation;
pub use mtterror::{ErrorID, MTTError}; pub use mtterror::{ErrorID, MTTError};
pub use name::{Name, NameType}; pub use name::{Name, NameType};
pub use queue::data_director::{Include, Path}; pub use queue::data_director::{Include, Path};

View File

@@ -2,6 +2,7 @@ use crate::{
action::{Field, FieldType}, action::{Field, FieldType},
message::MessageAction, message::MessageAction,
name::{Name, NameType}, name::{Name, NameType},
MissingTranslation,
}; };
use isolang::Language; use isolang::Language;
use std::{collections::VecDeque, error::Error, fmt}; use std::{collections::VecDeque, error::Error, fmt};
@@ -21,6 +22,7 @@ pub enum ErrorID {
FieldTypeExpected(FieldType), FieldTypeExpected(FieldType),
IndexEntryAlreadyExists(Field), IndexEntryAlreadyExists(Field),
InvalidFieldName(Name), InvalidFieldName(Name),
MissingTranslation(Vec<MissingTranslation>),
NameAlreadyExists, NameAlreadyExists,
NameLanguageNotUnique, NameLanguageNotUnique,
NameNotFound(NameType), NameNotFound(NameType),