Localize engine progress messages
This commit is contained in:
@@ -6,6 +6,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use anyhow::{Context, Result, anyhow, bail};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
use bds_core::db::{Database, DbConnection};
|
use bds_core::db::{Database, DbConnection};
|
||||||
use bds_core::engine::{self, cli_sync, domain_events};
|
use bds_core::engine::{self, cli_sync, domain_events};
|
||||||
|
use bds_core::i18n::UiLocale;
|
||||||
use bds_core::model::{DomainEntity, NotificationAction, Project, ScriptKind};
|
use bds_core::model::{DomainEntity, NotificationAction, Project, ScriptKind};
|
||||||
use bds_core::scripting::{CoreHost, ExecutionControl, ExecutionKind, execute_many_with_host};
|
use bds_core::scripting::{CoreHost, ExecutionControl, ExecutionKind, execute_many_with_host};
|
||||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||||
@@ -359,7 +360,8 @@ fn rebuild(db: &Database, incremental: bool) -> Result<CommandOutput> {
|
|||||||
db.conn(),
|
db.conn(),
|
||||||
&data_dir,
|
&data_dir,
|
||||||
&project.id,
|
&project.id,
|
||||||
Some(Arc::new(move |value, message| {
|
Some(Arc::new(move |value, event| {
|
||||||
|
let message = event.localized(UiLocale::En);
|
||||||
let line = format!("[{:>3}%] {message}", (value * 100.0).round() as i32);
|
let line = format!("[{:>3}%] {message}", (value * 100.0).round() as i32);
|
||||||
let mut progress = progress_sink
|
let mut progress = progress_sink
|
||||||
.lock()
|
.lock()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::engine::ai::{
|
|||||||
TranslationResult,
|
TranslationResult,
|
||||||
};
|
};
|
||||||
use crate::engine::{EngineError, EngineResult};
|
use crate::engine::{EngineError, EngineResult};
|
||||||
|
use crate::i18n::{UiLocale, translate, translate_with};
|
||||||
use crate::model::{Media, Post, PostStatus};
|
use crate::model::{Media, Post, PostStatus};
|
||||||
use crate::util::frontmatter::read_post_file;
|
use crate::util::frontmatter::read_post_file;
|
||||||
|
|
||||||
@@ -26,6 +27,29 @@ pub struct FillMissingTranslationsReport {
|
|||||||
pub errors: Vec<String>,
|
pub errors: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum FillMissingTranslationsProgress {
|
||||||
|
ScanningPublishedPosts,
|
||||||
|
TranslatingPost { title: String, language: String },
|
||||||
|
Complete,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FillMissingTranslationsProgress {
|
||||||
|
pub fn localized(&self, locale: UiLocale) -> String {
|
||||||
|
match self {
|
||||||
|
Self::ScanningPublishedPosts => {
|
||||||
|
translate(locale, "engine.progress.scanningPublishedPosts")
|
||||||
|
}
|
||||||
|
Self::TranslatingPost { title, language } => translate_with(
|
||||||
|
locale,
|
||||||
|
"engine.progress.translatingPost",
|
||||||
|
&[("title", title), ("language", language)],
|
||||||
|
),
|
||||||
|
Self::Complete => translate(locale, "engine.progress.translationBatchComplete"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn configured_languages(main_language: &str, blog_languages: &[String]) -> Vec<String> {
|
pub fn configured_languages(main_language: &str, blog_languages: &[String]) -> Vec<String> {
|
||||||
let mut seen = HashSet::new();
|
let mut seen = HashSet::new();
|
||||||
std::iter::once(main_language.to_string())
|
std::iter::once(main_language.to_string())
|
||||||
@@ -64,7 +88,7 @@ pub fn fill_missing_translations(
|
|||||||
main_language: &str,
|
main_language: &str,
|
||||||
blog_languages: &[String],
|
blog_languages: &[String],
|
||||||
offline_mode: bool,
|
offline_mode: bool,
|
||||||
mut on_progress: impl FnMut(f32, &str) -> bool,
|
mut on_progress: impl FnMut(f32, &FillMissingTranslationsProgress) -> bool,
|
||||||
) -> EngineResult<FillMissingTranslationsReport> {
|
) -> EngineResult<FillMissingTranslationsReport> {
|
||||||
fill_missing_translations_with(
|
fill_missing_translations_with(
|
||||||
conn,
|
conn,
|
||||||
@@ -90,7 +114,7 @@ fn fill_missing_translations_with(
|
|||||||
blog_languages: &[String],
|
blog_languages: &[String],
|
||||||
post_translator: &mut dyn FnMut(&Post, &str) -> EngineResult<TranslationResult>,
|
post_translator: &mut dyn FnMut(&Post, &str) -> EngineResult<TranslationResult>,
|
||||||
media_translator: &mut dyn FnMut(&Media, &str) -> EngineResult<MediaTranslationResult>,
|
media_translator: &mut dyn FnMut(&Media, &str) -> EngineResult<MediaTranslationResult>,
|
||||||
on_progress: &mut dyn FnMut(f32, &str) -> bool,
|
on_progress: &mut dyn FnMut(f32, &FillMissingTranslationsProgress) -> bool,
|
||||||
) -> EngineResult<FillMissingTranslationsReport> {
|
) -> EngineResult<FillMissingTranslationsReport> {
|
||||||
let configured = configured_languages(main_language, blog_languages);
|
let configured = configured_languages(main_language, blog_languages);
|
||||||
if configured.len() <= 1 {
|
if configured.len() <= 1 {
|
||||||
@@ -100,7 +124,10 @@ fn fill_missing_translations_with(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
let posts = qp::list_posts_by_project(conn, project_id)?;
|
let posts = qp::list_posts_by_project(conn, project_id)?;
|
||||||
if !on_progress(0.0, "Scanning published posts") {
|
if !on_progress(
|
||||||
|
0.0,
|
||||||
|
&FillMissingTranslationsProgress::ScanningPublishedPosts,
|
||||||
|
) {
|
||||||
return Err(EngineError::Validation("cancelled".to_string()));
|
return Err(EngineError::Validation("cancelled".to_string()));
|
||||||
}
|
}
|
||||||
let mut work = Vec::new();
|
let mut work = Vec::new();
|
||||||
@@ -108,7 +135,10 @@ fn fill_missing_translations_with(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|post| post.status == PostStatus::Published && !post.do_not_translate)
|
.filter(|post| post.status == PostStatus::Published && !post.do_not_translate)
|
||||||
{
|
{
|
||||||
if !on_progress(0.0, "Scanning published posts") {
|
if !on_progress(
|
||||||
|
0.0,
|
||||||
|
&FillMissingTranslationsProgress::ScanningPublishedPosts,
|
||||||
|
) {
|
||||||
return Err(EngineError::Validation("cancelled".to_string()));
|
return Err(EngineError::Validation("cancelled".to_string()));
|
||||||
}
|
}
|
||||||
for language in missing_languages(conn, &post, &configured)? {
|
for language in missing_languages(conn, &post, &configured)? {
|
||||||
@@ -126,7 +156,10 @@ fn fill_missing_translations_with(
|
|||||||
for (index, (post, language)) in work.iter().enumerate() {
|
for (index, (post, language)) in work.iter().enumerate() {
|
||||||
if !on_progress(
|
if !on_progress(
|
||||||
0.15 + (index as f32 / work.len() as f32) * 0.85,
|
0.15 + (index as f32 / work.len() as f32) * 0.85,
|
||||||
&format!("{} → {language}", post.title),
|
&FillMissingTranslationsProgress::TranslatingPost {
|
||||||
|
title: post.title.clone(),
|
||||||
|
language: language.clone(),
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
return Err(EngineError::Validation("cancelled".to_string()));
|
return Err(EngineError::Validation("cancelled".to_string()));
|
||||||
}
|
}
|
||||||
@@ -151,7 +184,7 @@ fn fill_missing_translations_with(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !on_progress(1.0, "Translation batch complete") {
|
if !on_progress(1.0, &FillMissingTranslationsProgress::Complete) {
|
||||||
return Err(EngineError::Validation("cancelled".to_string()));
|
return Err(EngineError::Validation("cancelled".to_string()));
|
||||||
}
|
}
|
||||||
Ok(report)
|
Ok(report)
|
||||||
@@ -399,6 +432,26 @@ mod tests {
|
|||||||
use crate::engine::post::{create_post, publish_post, upsert_translation};
|
use crate::engine::post::{create_post, publish_post, upsert_translation};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn progress_events_use_the_selected_ui_locale() {
|
||||||
|
assert_eq!(
|
||||||
|
FillMissingTranslationsProgress::ScanningPublishedPosts.localized(UiLocale::De),
|
||||||
|
"Veröffentlichte Beiträge werden durchsucht…"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
FillMissingTranslationsProgress::TranslatingPost {
|
||||||
|
title: "Hallo".into(),
|
||||||
|
language: "fr".into(),
|
||||||
|
}
|
||||||
|
.localized(UiLocale::Fr),
|
||||||
|
"Traduction de Hallo → fr"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
FillMissingTranslationsProgress::Complete.localized(UiLocale::Es),
|
||||||
|
"Lote de traducciones completado"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn batch_translates_only_missing_languages_and_publishes() {
|
fn batch_translates_only_missing_languages_and_publishes() {
|
||||||
let db = Database::open_in_memory().unwrap();
|
let db = Database::open_in_memory().unwrap();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use crate::engine::media;
|
|||||||
use crate::engine::post;
|
use crate::engine::post;
|
||||||
use crate::engine::script_rebuild;
|
use crate::engine::script_rebuild;
|
||||||
use crate::engine::template_rebuild;
|
use crate::engine::template_rebuild;
|
||||||
|
use crate::i18n::{UiLocale, translate, translate_with};
|
||||||
|
|
||||||
/// Report from a full rebuild operation.
|
/// Report from a full rebuild operation.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -74,8 +75,69 @@ pub fn rebuild_incremental(
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Progress callback: (percent 0.0..1.0, phase description).
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub type ProgressFn = Arc<dyn Fn(f32, &str) + Send + Sync>;
|
pub enum RebuildProgress {
|
||||||
|
LoadingProjectMetadata,
|
||||||
|
ScanningPosts,
|
||||||
|
PostItem {
|
||||||
|
current: usize,
|
||||||
|
total: usize,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
ScanningMedia,
|
||||||
|
MediaItem {
|
||||||
|
current: usize,
|
||||||
|
total: usize,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
RebuildingTemplates,
|
||||||
|
RebuildingScripts,
|
||||||
|
ImportingTags,
|
||||||
|
RefreshingSemanticIndex,
|
||||||
|
Complete,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RebuildProgress {
|
||||||
|
pub fn localized(&self, locale: UiLocale) -> String {
|
||||||
|
match self {
|
||||||
|
Self::LoadingProjectMetadata => {
|
||||||
|
translate(locale, "engine.progress.loadingProjectMetadata")
|
||||||
|
}
|
||||||
|
Self::ScanningPosts => translate(locale, "engine.progress.scanningPosts"),
|
||||||
|
Self::PostItem {
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
name,
|
||||||
|
} => localize_item(locale, "engine.progress.postItem", *current, *total, name),
|
||||||
|
Self::ScanningMedia => translate(locale, "engine.progress.scanningMedia"),
|
||||||
|
Self::MediaItem {
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
name,
|
||||||
|
} => localize_item(locale, "engine.progress.mediaItem", *current, *total, name),
|
||||||
|
Self::RebuildingTemplates => translate(locale, "engine.progress.rebuildingTemplates"),
|
||||||
|
Self::RebuildingScripts => translate(locale, "engine.progress.rebuildingScripts"),
|
||||||
|
Self::ImportingTags => translate(locale, "engine.progress.importingTags"),
|
||||||
|
Self::RefreshingSemanticIndex => {
|
||||||
|
translate(locale, "engine.progress.refreshingSemanticIndex")
|
||||||
|
}
|
||||||
|
Self::Complete => translate(locale, "engine.progress.rebuildComplete"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn localize_item(locale: UiLocale, key: &str, current: usize, total: usize, name: &str) -> String {
|
||||||
|
let current = current.to_string();
|
||||||
|
let total = total.to_string();
|
||||||
|
translate_with(
|
||||||
|
locale,
|
||||||
|
key,
|
||||||
|
&[("current", ¤t), ("total", &total), ("name", name)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Progress callback: (percent 0.0..1.0, semantic progress event).
|
||||||
|
pub type ProgressFn = Arc<dyn Fn(f32, &RebuildProgress) + Send + Sync>;
|
||||||
|
|
||||||
/// Orchestrate a full rebuild from filesystem into the database.
|
/// Orchestrate a full rebuild from filesystem into the database.
|
||||||
///
|
///
|
||||||
@@ -116,23 +178,23 @@ fn rebuild_from_filesystem_inner(
|
|||||||
on_progress: Option<ProgressFn>,
|
on_progress: Option<ProgressFn>,
|
||||||
) -> EngineResult<FullRebuildReport> {
|
) -> EngineResult<FullRebuildReport> {
|
||||||
let mut report = FullRebuildReport::default();
|
let mut report = FullRebuildReport::default();
|
||||||
let progress = |pct: f32, msg: &str| {
|
let progress = |pct: f32, event: RebuildProgress| {
|
||||||
if let Some(ref f) = on_progress {
|
if let Some(ref f) = on_progress {
|
||||||
f(pct, msg);
|
f(pct, &event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Phase weights: posts 0.0..0.35, media 0.35..0.70, templates 0.70..0.85, scripts 0.85..1.0
|
// Phase weights: posts 0.0..0.35, media 0.35..0.70, templates 0.70..0.85, scripts 0.85..1.0
|
||||||
|
|
||||||
// 1. Load portable project metadata and clear all reconstructible rows.
|
// 1. Load portable project metadata and clear all reconstructible rows.
|
||||||
progress(0.0, "Loading project metadata...");
|
progress(0.0, RebuildProgress::LoadingProjectMetadata);
|
||||||
fts::ensure_fts_tables(conn)?;
|
fts::ensure_fts_tables(conn)?;
|
||||||
crate::engine::meta::startup_sync(data_dir)?;
|
crate::engine::meta::startup_sync(data_dir)?;
|
||||||
crate::engine::meta::sync_metadata_from_filesystem(conn, data_dir, project_id)?;
|
crate::engine::meta::sync_metadata_from_filesystem(conn, data_dir, project_id)?;
|
||||||
clear_project_rows(conn, project_id)?;
|
clear_project_rows(conn, project_id)?;
|
||||||
|
|
||||||
// 2. Rebuild posts (0.00 .. 0.35)
|
// 2. Rebuild posts (0.00 .. 0.35)
|
||||||
progress(0.01, "Scanning posts...");
|
progress(0.01, RebuildProgress::ScanningPosts);
|
||||||
let post_item_cb: Option<post::ItemProgressFn> = on_progress.as_ref().map(|cb| {
|
let post_item_cb: Option<post::ItemProgressFn> = on_progress.as_ref().map(|cb| {
|
||||||
let cb = Arc::clone(cb);
|
let cb = Arc::clone(cb);
|
||||||
let f: post::ItemProgressFn = Box::new(move |current, total, name| {
|
let f: post::ItemProgressFn = Box::new(move |current, total, name| {
|
||||||
@@ -142,8 +204,14 @@ fn rebuild_from_filesystem_inner(
|
|||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
let global_pct = 0.01 + phase_pct * 0.34;
|
let global_pct = 0.01 + phase_pct * 0.34;
|
||||||
let msg = format!("Posts: {current}/{total} \u{2014} {name}");
|
cb(
|
||||||
cb(global_pct, &msg);
|
global_pct,
|
||||||
|
&RebuildProgress::PostItem {
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
name: name.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
f
|
f
|
||||||
});
|
});
|
||||||
@@ -160,7 +228,7 @@ fn rebuild_from_filesystem_inner(
|
|||||||
report.errors.extend(post_report.errors);
|
report.errors.extend(post_report.errors);
|
||||||
|
|
||||||
// 3. Rebuild media (0.35 .. 0.70)
|
// 3. Rebuild media (0.35 .. 0.70)
|
||||||
progress(0.35, "Scanning media...");
|
progress(0.35, RebuildProgress::ScanningMedia);
|
||||||
let media_item_cb: Option<media::ItemProgressFn> = on_progress.as_ref().map(|cb| {
|
let media_item_cb: Option<media::ItemProgressFn> = on_progress.as_ref().map(|cb| {
|
||||||
let cb = Arc::clone(cb);
|
let cb = Arc::clone(cb);
|
||||||
let f: media::ItemProgressFn = Box::new(move |current, total, name| {
|
let f: media::ItemProgressFn = Box::new(move |current, total, name| {
|
||||||
@@ -170,8 +238,14 @@ fn rebuild_from_filesystem_inner(
|
|||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
let global_pct = 0.35 + phase_pct * 0.35;
|
let global_pct = 0.35 + phase_pct * 0.35;
|
||||||
let msg = format!("Media: {current}/{total} \u{2014} {name}");
|
cb(
|
||||||
cb(global_pct, &msg);
|
global_pct,
|
||||||
|
&RebuildProgress::MediaItem {
|
||||||
|
current,
|
||||||
|
total,
|
||||||
|
name: name.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
f
|
f
|
||||||
});
|
});
|
||||||
@@ -188,7 +262,7 @@ fn rebuild_from_filesystem_inner(
|
|||||||
report.errors.extend(media_report.errors);
|
report.errors.extend(media_report.errors);
|
||||||
|
|
||||||
// 4. Rebuild templates (0.70 .. 0.85)
|
// 4. Rebuild templates (0.70 .. 0.85)
|
||||||
progress(0.70, "Rebuilding templates...");
|
progress(0.70, RebuildProgress::RebuildingTemplates);
|
||||||
let tpl_report =
|
let tpl_report =
|
||||||
template_rebuild::rebuild_templates_from_filesystem(conn, data_dir, project_id)?;
|
template_rebuild::rebuild_templates_from_filesystem(conn, data_dir, project_id)?;
|
||||||
report.templates_created = tpl_report.created;
|
report.templates_created = tpl_report.created;
|
||||||
@@ -196,7 +270,7 @@ fn rebuild_from_filesystem_inner(
|
|||||||
report.errors.extend(tpl_report.errors);
|
report.errors.extend(tpl_report.errors);
|
||||||
|
|
||||||
// 5. Rebuild scripts (0.85 .. 0.95)
|
// 5. Rebuild scripts (0.85 .. 0.95)
|
||||||
progress(0.85, "Rebuilding scripts...");
|
progress(0.85, RebuildProgress::RebuildingScripts);
|
||||||
let script_report =
|
let script_report =
|
||||||
script_rebuild::rebuild_scripts_from_filesystem(conn, data_dir, project_id)?;
|
script_rebuild::rebuild_scripts_from_filesystem(conn, data_dir, project_id)?;
|
||||||
report.scripts_created = script_report.created;
|
report.scripts_created = script_report.created;
|
||||||
@@ -204,7 +278,7 @@ fn rebuild_from_filesystem_inner(
|
|||||||
report.errors.extend(script_report.errors);
|
report.errors.extend(script_report.errors);
|
||||||
|
|
||||||
// 6. Restore relationships and tags (0.95 .. 1.0)
|
// 6. Restore relationships and tags (0.95 .. 1.0)
|
||||||
progress(0.95, "Importing tags...");
|
progress(0.95, RebuildProgress::ImportingTags);
|
||||||
super::tag::import_tags_from_file(conn, data_dir, project_id)?;
|
super::tag::import_tags_from_file(conn, data_dir, project_id)?;
|
||||||
super::tag::sync_tags_from_posts(conn, project_id)?;
|
super::tag::sync_tags_from_posts(conn, project_id)?;
|
||||||
post::rebuild_all_links(conn, data_dir, project_id)?;
|
post::rebuild_all_links(conn, data_dir, project_id)?;
|
||||||
@@ -216,11 +290,11 @@ fn rebuild_from_filesystem_inner(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
progress(0.98, "Refreshing semantic index...");
|
progress(0.98, RebuildProgress::RefreshingSemanticIndex);
|
||||||
crate::engine::embedding::EmbeddingService::production(conn, data_dir)
|
crate::engine::embedding::EmbeddingService::production(conn, data_dir)
|
||||||
.index_unindexed(project_id)?;
|
.index_unindexed(project_id)?;
|
||||||
|
|
||||||
progress(1.0, "Rebuild complete");
|
progress(1.0, RebuildProgress::Complete);
|
||||||
Ok(report)
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,6 +377,27 @@ mod tests {
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn progress_events_use_the_selected_ui_locale() {
|
||||||
|
assert_eq!(
|
||||||
|
RebuildProgress::LoadingProjectMetadata.localized(UiLocale::It),
|
||||||
|
"Caricamento dei metadati del progetto…"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
RebuildProgress::PostItem {
|
||||||
|
current: 2,
|
||||||
|
total: 4,
|
||||||
|
name: "Bonjour".into(),
|
||||||
|
}
|
||||||
|
.localized(UiLocale::Fr),
|
||||||
|
"Articles : 2/4 — Bonjour"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
RebuildProgress::Complete.localized(UiLocale::De),
|
||||||
|
"Neuaufbau abgeschlossen"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn setup() -> (Database, TempDir) {
|
fn setup() -> (Database, TempDir) {
|
||||||
let db = Database::open_in_memory().unwrap();
|
let db = Database::open_in_memory().unwrap();
|
||||||
db.migrate().unwrap();
|
db.migrate().unwrap();
|
||||||
|
|||||||
@@ -234,6 +234,47 @@ static RENDER_MAPS: LazyLock<[HashMap<String, String>; 5]> = LazyLock::new(|| {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn engine_progress_keys_exist_in_every_ui_catalog() {
|
||||||
|
let keys = [
|
||||||
|
"engine-progress-scanningPublishedPosts",
|
||||||
|
"engine-progress-translatingPost",
|
||||||
|
"engine-progress-translationBatchComplete",
|
||||||
|
"engine-progress-loadingProjectMetadata",
|
||||||
|
"engine-progress-scanningPosts",
|
||||||
|
"engine-progress-postItem",
|
||||||
|
"engine-progress-scanningMedia",
|
||||||
|
"engine-progress-mediaItem",
|
||||||
|
"engine-progress-rebuildingTemplates",
|
||||||
|
"engine-progress-rebuildingScripts",
|
||||||
|
"engine-progress-importingTags",
|
||||||
|
"engine-progress-refreshingSemanticIndex",
|
||||||
|
"engine-progress-rebuildComplete",
|
||||||
|
];
|
||||||
|
let catalogs = [
|
||||||
|
("en", UI_EN),
|
||||||
|
("de", include_str!("../../../../locales/ui/de.ftl")),
|
||||||
|
("fr", include_str!("../../../../locales/ui/fr.ftl")),
|
||||||
|
("it", include_str!("../../../../locales/ui/it.ftl")),
|
||||||
|
("es", include_str!("../../../../locales/ui/es.ftl")),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (language, source) in catalogs {
|
||||||
|
let present = resource(source)
|
||||||
|
.entries()
|
||||||
|
.filter_map(|entry| match entry {
|
||||||
|
fluent_syntax::ast::Entry::Message(message) => {
|
||||||
|
Some(message.id.name.to_string())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect::<std::collections::HashSet<_>>();
|
||||||
|
for key in keys {
|
||||||
|
assert!(present.contains(key), "{language} is missing {key}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LanguageNormalization invariant: base-extract and fallback
|
// LanguageNormalization invariant: base-extract and fallback
|
||||||
#[test]
|
#[test]
|
||||||
fn normalize_strips_region() {
|
fn normalize_strips_region() {
|
||||||
|
|||||||
@@ -849,14 +849,27 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn video_macros_with_missing_ids_still_use_the_localized_templates() {
|
fn video_macros_with_missing_ids_still_use_the_localized_templates() {
|
||||||
let rendered =
|
for (language, youtube_title, vimeo_title) in [
|
||||||
expand_builtin_macros("[[youtube]] [[vimeo]]", &MacroRenderContext::default());
|
("en", "YouTube video", "Vimeo video"),
|
||||||
|
("de", "YouTube-Video", "Vimeo-Video"),
|
||||||
|
("fr", "Vidéo YouTube", "Vidéo Vimeo"),
|
||||||
|
("it", "Video YouTube", "Video Vimeo"),
|
||||||
|
("es", "Vídeo de YouTube", "Vídeo de Vimeo"),
|
||||||
|
] {
|
||||||
|
let mut roots = serde_json::Map::new();
|
||||||
|
roots.insert("language".into(), serde_json::json!(language));
|
||||||
|
let context = MacroRenderContext {
|
||||||
|
roots,
|
||||||
|
..MacroRenderContext::default()
|
||||||
|
};
|
||||||
|
let rendered = expand_builtin_macros("[[youtube]] [[vimeo]]", &context);
|
||||||
|
|
||||||
assert!(rendered.contains("https://www.youtube.com/embed/?rel=0"));
|
assert!(rendered.contains("https://www.youtube.com/embed/?rel=0"));
|
||||||
assert!(rendered.contains("https://player.vimeo.com/video/"));
|
assert!(rendered.contains("https://player.vimeo.com/video/"));
|
||||||
assert!(rendered.contains("title=\"YouTube video\""));
|
assert!(rendered.contains(&format!("title=\"{youtube_title}\"")));
|
||||||
assert!(rendered.contains("title=\"Vimeo video\""));
|
assert!(rendered.contains(&format!("title=\"{vimeo_title}\"")));
|
||||||
assert!(!rendered.contains("Missing"));
|
assert!(!rendered.contains("Missing"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -4340,11 +4340,11 @@ impl BdsApp {
|
|||||||
main_language,
|
main_language,
|
||||||
&meta.blog_languages,
|
&meta.blog_languages,
|
||||||
offline_mode,
|
offline_mode,
|
||||||
move |progress, _message| {
|
move |progress, event| {
|
||||||
task_manager_for_progress.report_progress(
|
task_manager_for_progress.report_progress(
|
||||||
task_id,
|
task_id,
|
||||||
Some(progress),
|
Some(progress),
|
||||||
Some(t(locale, "engine.translatingContent")),
|
Some(event.localized(locale)),
|
||||||
);
|
);
|
||||||
!task_manager_for_progress.is_cancelled(task_id)
|
!task_manager_for_progress.is_cancelled(task_id)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,29 +3,33 @@ use super::*;
|
|||||||
impl BdsApp {
|
impl BdsApp {
|
||||||
pub(super) fn handle_engine_message(&mut self, message: Message) -> Task<Message> {
|
pub(super) fn handle_engine_message(&mut self, message: Message) -> Task<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::RebuildDatabase => self.spawn_engine_task(
|
Message::RebuildDatabase => {
|
||||||
"engine.rebuildStarted",
|
let locale = self.ui_locale;
|
||||||
|db_path, project_id, data_dir, tm, tid| {
|
self.spawn_engine_task(
|
||||||
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
"engine.rebuildStarted",
|
||||||
let on_progress: engine::rebuild::ProgressFn = Arc::new(move |pct, msg| {
|
move |db_path, project_id, data_dir, tm, tid| {
|
||||||
tm.report_progress(tid, Some(pct), Some(msg.to_string()));
|
let db = Database::open(&db_path).map_err(|e| e.to_string())?;
|
||||||
});
|
let on_progress: engine::rebuild::ProgressFn =
|
||||||
let report = engine::rebuild::rebuild_from_filesystem_with_progress(
|
Arc::new(move |pct, event| {
|
||||||
db.conn(),
|
tm.report_progress(tid, Some(pct), Some(event.localized(locale)));
|
||||||
&data_dir,
|
});
|
||||||
&project_id,
|
let report = engine::rebuild::rebuild_from_filesystem_with_progress(
|
||||||
Some(on_progress),
|
db.conn(),
|
||||||
)
|
&data_dir,
|
||||||
.map_err(|e| e.to_string())?;
|
&project_id,
|
||||||
let posts = report.posts_created + report.posts_updated;
|
Some(on_progress),
|
||||||
let media = report.media_created + report.media_updated;
|
)
|
||||||
let templates = report.templates_created + report.templates_updated;
|
.map_err(|e| e.to_string())?;
|
||||||
let scripts = report.scripts_created + report.scripts_updated;
|
let posts = report.posts_created + report.posts_updated;
|
||||||
Ok(format!(
|
let media = report.media_created + report.media_updated;
|
||||||
"posts={posts}, media={media}, templates={templates}, scripts={scripts}"
|
let templates = report.templates_created + report.templates_updated;
|
||||||
))
|
let scripts = report.scripts_created + report.scripts_updated;
|
||||||
},
|
Ok(format!(
|
||||||
),
|
"posts={posts}, media={media}, templates={templates}, scripts={scripts}"
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
Message::ReindexText => {
|
Message::ReindexText => {
|
||||||
if !self.search_index_rebuild_running {
|
if !self.search_index_rebuild_running {
|
||||||
self.active_modal = Some(modal::ModalState::SearchIndexRepair);
|
self.active_modal = Some(modal::ModalState::SearchIndexRepair);
|
||||||
|
|||||||
@@ -639,10 +639,22 @@ metadataDiff-field = { $field }: DB = { $database } · Datei = { $file }
|
|||||||
metadataDiff-entity = { $entity } · { $path }
|
metadataDiff-entity = { $entity } · { $path }
|
||||||
metadataDiff-orphan = { $reason } · { $path }
|
metadataDiff-orphan = { $reason } · { $path }
|
||||||
engine-fillMissingTranslationsStarted = Fehlende Übersetzungen werden ergänzt…
|
engine-fillMissingTranslationsStarted = Fehlende Übersetzungen werden ergänzt…
|
||||||
engine-translatingContent = Inhalte werden übersetzt…
|
|
||||||
engine-fillMissingTranslationsComplete = Übersetzung abgeschlossen: { $posts } Beiträge, { $media } Medien, { $failed } fehlgeschlagen
|
engine-fillMissingTranslationsComplete = Übersetzung abgeschlossen: { $posts } Beiträge, { $media } Medien, { $failed } fehlgeschlagen
|
||||||
engine-autoTranslationStarted = Fehlende Übersetzungen werden erstellt…
|
engine-autoTranslationStarted = Fehlende Übersetzungen werden erstellt…
|
||||||
engine-autoTranslationComplete = { $count } Übersetzungsentwürfe erstellt
|
engine-autoTranslationComplete = { $count } Übersetzungsentwürfe erstellt
|
||||||
|
engine-progress-scanningPublishedPosts = Veröffentlichte Beiträge werden durchsucht…
|
||||||
|
engine-progress-translatingPost = { $title } wird übersetzt → { $language }
|
||||||
|
engine-progress-translationBatchComplete = Übersetzungsstapel abgeschlossen
|
||||||
|
engine-progress-loadingProjectMetadata = Projektmetadaten werden geladen…
|
||||||
|
engine-progress-scanningPosts = Beiträge werden durchsucht…
|
||||||
|
engine-progress-postItem = Beiträge: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-scanningMedia = Medien werden durchsucht…
|
||||||
|
engine-progress-mediaItem = Medien: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-rebuildingTemplates = Vorlagen werden neu aufgebaut…
|
||||||
|
engine-progress-rebuildingScripts = Skripte werden neu aufgebaut…
|
||||||
|
engine-progress-importingTags = Schlagwörter werden importiert…
|
||||||
|
engine-progress-refreshingSemanticIndex = Semantischer Index wird aktualisiert…
|
||||||
|
engine-progress-rebuildComplete = Neuaufbau abgeschlossen
|
||||||
translationValidation-run = Prüfen
|
translationValidation-run = Prüfen
|
||||||
translationValidation-running = Übersetzungen werden geprüft…
|
translationValidation-running = Übersetzungen werden geprüft…
|
||||||
translationValidation-idle = Prüfung starten, um Übersetzungen in Datenbank und Dateisystem zu kontrollieren.
|
translationValidation-idle = Prüfung starten, um Übersetzungen in Datenbank und Dateisystem zu kontrollieren.
|
||||||
|
|||||||
@@ -639,10 +639,22 @@ metadataDiff-field = { $field }: DB = { $database } · File = { $file }
|
|||||||
metadataDiff-entity = { $entity } · { $path }
|
metadataDiff-entity = { $entity } · { $path }
|
||||||
metadataDiff-orphan = { $reason } · { $path }
|
metadataDiff-orphan = { $reason } · { $path }
|
||||||
engine-fillMissingTranslationsStarted = Filling missing translations…
|
engine-fillMissingTranslationsStarted = Filling missing translations…
|
||||||
engine-translatingContent = Translating content…
|
|
||||||
engine-fillMissingTranslationsComplete = Translation batch complete: { $posts } posts, { $media } media, { $failed } failed
|
engine-fillMissingTranslationsComplete = Translation batch complete: { $posts } posts, { $media } media, { $failed } failed
|
||||||
engine-autoTranslationStarted = Creating missing translations…
|
engine-autoTranslationStarted = Creating missing translations…
|
||||||
engine-autoTranslationComplete = Created { $count } draft translations
|
engine-autoTranslationComplete = Created { $count } draft translations
|
||||||
|
engine-progress-scanningPublishedPosts = Scanning published posts…
|
||||||
|
engine-progress-translatingPost = Translating { $title } → { $language }
|
||||||
|
engine-progress-translationBatchComplete = Translation batch complete
|
||||||
|
engine-progress-loadingProjectMetadata = Loading project metadata…
|
||||||
|
engine-progress-scanningPosts = Scanning posts…
|
||||||
|
engine-progress-postItem = Posts: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-scanningMedia = Scanning media…
|
||||||
|
engine-progress-mediaItem = Media: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-rebuildingTemplates = Rebuilding templates…
|
||||||
|
engine-progress-rebuildingScripts = Rebuilding scripts…
|
||||||
|
engine-progress-importingTags = Importing tags…
|
||||||
|
engine-progress-refreshingSemanticIndex = Refreshing semantic index…
|
||||||
|
engine-progress-rebuildComplete = Rebuild complete
|
||||||
translationValidation-run = Validate
|
translationValidation-run = Validate
|
||||||
translationValidation-running = Validating translations…
|
translationValidation-running = Validating translations…
|
||||||
translationValidation-idle = Run validation to check translations in the database and filesystem.
|
translationValidation-idle = Run validation to check translations in the database and filesystem.
|
||||||
|
|||||||
@@ -639,10 +639,22 @@ metadataDiff-field = { $field }: BD = { $database } · Archivo = { $file }
|
|||||||
metadataDiff-entity = { $entity } · { $path }
|
metadataDiff-entity = { $entity } · { $path }
|
||||||
metadataDiff-orphan = { $reason } · { $path }
|
metadataDiff-orphan = { $reason } · { $path }
|
||||||
engine-fillMissingTranslationsStarted = Creando las traducciones que faltan…
|
engine-fillMissingTranslationsStarted = Creando las traducciones que faltan…
|
||||||
engine-translatingContent = Traduciendo el contenido…
|
|
||||||
engine-fillMissingTranslationsComplete = Traducción completada: { $posts } entradas, { $media } medios, { $failed } fallos
|
engine-fillMissingTranslationsComplete = Traducción completada: { $posts } entradas, { $media } medios, { $failed } fallos
|
||||||
engine-autoTranslationStarted = Creando las traducciones que faltan…
|
engine-autoTranslationStarted = Creando las traducciones que faltan…
|
||||||
engine-autoTranslationComplete = Se crearon { $count } borradores de traducción
|
engine-autoTranslationComplete = Se crearon { $count } borradores de traducción
|
||||||
|
engine-progress-scanningPublishedPosts = Analizando las entradas publicadas…
|
||||||
|
engine-progress-translatingPost = Traduciendo { $title } → { $language }
|
||||||
|
engine-progress-translationBatchComplete = Lote de traducciones completado
|
||||||
|
engine-progress-loadingProjectMetadata = Cargando los metadatos del proyecto…
|
||||||
|
engine-progress-scanningPosts = Analizando las entradas…
|
||||||
|
engine-progress-postItem = Entradas: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-scanningMedia = Analizando los medios…
|
||||||
|
engine-progress-mediaItem = Medios: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-rebuildingTemplates = Reconstruyendo las plantillas…
|
||||||
|
engine-progress-rebuildingScripts = Reconstruyendo los scripts…
|
||||||
|
engine-progress-importingTags = Importando las etiquetas…
|
||||||
|
engine-progress-refreshingSemanticIndex = Actualizando el índice semántico…
|
||||||
|
engine-progress-rebuildComplete = Reconstrucción completada
|
||||||
translationValidation-run = Validar
|
translationValidation-run = Validar
|
||||||
translationValidation-running = Validando traducciones…
|
translationValidation-running = Validando traducciones…
|
||||||
translationValidation-idle = Ejecuta la validación de las traducciones de la base de datos y el sistema de archivos.
|
translationValidation-idle = Ejecuta la validación de las traducciones de la base de datos y el sistema de archivos.
|
||||||
|
|||||||
@@ -639,10 +639,22 @@ metadataDiff-field = { $field } : base = { $database } · Fichier = { $file }
|
|||||||
metadataDiff-entity = { $entity } · { $path }
|
metadataDiff-entity = { $entity } · { $path }
|
||||||
metadataDiff-orphan = { $reason } · { $path }
|
metadataDiff-orphan = { $reason } · { $path }
|
||||||
engine-fillMissingTranslationsStarted = Création des traductions manquantes…
|
engine-fillMissingTranslationsStarted = Création des traductions manquantes…
|
||||||
engine-translatingContent = Traduction du contenu…
|
|
||||||
engine-fillMissingTranslationsComplete = Traduction terminée : { $posts } articles, { $media } médias, { $failed } échecs
|
engine-fillMissingTranslationsComplete = Traduction terminée : { $posts } articles, { $media } médias, { $failed } échecs
|
||||||
engine-autoTranslationStarted = Création des traductions manquantes…
|
engine-autoTranslationStarted = Création des traductions manquantes…
|
||||||
engine-autoTranslationComplete = { $count } brouillons de traduction créés
|
engine-autoTranslationComplete = { $count } brouillons de traduction créés
|
||||||
|
engine-progress-scanningPublishedPosts = Analyse des articles publiés…
|
||||||
|
engine-progress-translatingPost = Traduction de { $title } → { $language }
|
||||||
|
engine-progress-translationBatchComplete = Lot de traductions terminé
|
||||||
|
engine-progress-loadingProjectMetadata = Chargement des métadonnées du projet…
|
||||||
|
engine-progress-scanningPosts = Analyse des articles…
|
||||||
|
engine-progress-postItem = Articles : { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-scanningMedia = Analyse des médias…
|
||||||
|
engine-progress-mediaItem = Médias : { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-rebuildingTemplates = Reconstruction des modèles…
|
||||||
|
engine-progress-rebuildingScripts = Reconstruction des scripts…
|
||||||
|
engine-progress-importingTags = Importation des étiquettes…
|
||||||
|
engine-progress-refreshingSemanticIndex = Actualisation de l’index sémantique…
|
||||||
|
engine-progress-rebuildComplete = Reconstruction terminée
|
||||||
translationValidation-run = Valider
|
translationValidation-run = Valider
|
||||||
translationValidation-running = Validation des traductions…
|
translationValidation-running = Validation des traductions…
|
||||||
translationValidation-idle = Lancez la validation des traductions de la base et du système de fichiers.
|
translationValidation-idle = Lancez la validation des traductions de la base et du système de fichiers.
|
||||||
|
|||||||
@@ -639,10 +639,22 @@ metadataDiff-field = { $field }: DB = { $database } · File = { $file }
|
|||||||
metadataDiff-entity = { $entity } · { $path }
|
metadataDiff-entity = { $entity } · { $path }
|
||||||
metadataDiff-orphan = { $reason } · { $path }
|
metadataDiff-orphan = { $reason } · { $path }
|
||||||
engine-fillMissingTranslationsStarted = Creazione delle traduzioni mancanti…
|
engine-fillMissingTranslationsStarted = Creazione delle traduzioni mancanti…
|
||||||
engine-translatingContent = Traduzione dei contenuti…
|
|
||||||
engine-fillMissingTranslationsComplete = Traduzione completata: { $posts } articoli, { $media } media, { $failed } errori
|
engine-fillMissingTranslationsComplete = Traduzione completata: { $posts } articoli, { $media } media, { $failed } errori
|
||||||
engine-autoTranslationStarted = Creazione delle traduzioni mancanti…
|
engine-autoTranslationStarted = Creazione delle traduzioni mancanti…
|
||||||
engine-autoTranslationComplete = Create { $count } bozze di traduzione
|
engine-autoTranslationComplete = Create { $count } bozze di traduzione
|
||||||
|
engine-progress-scanningPublishedPosts = Scansione dei post pubblicati…
|
||||||
|
engine-progress-translatingPost = Traduzione di { $title } → { $language }
|
||||||
|
engine-progress-translationBatchComplete = Lotto di traduzioni completato
|
||||||
|
engine-progress-loadingProjectMetadata = Caricamento dei metadati del progetto…
|
||||||
|
engine-progress-scanningPosts = Scansione dei post…
|
||||||
|
engine-progress-postItem = Post: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-scanningMedia = Scansione dei media…
|
||||||
|
engine-progress-mediaItem = Media: { $current }/{ $total } — { $name }
|
||||||
|
engine-progress-rebuildingTemplates = Ricostruzione dei modelli…
|
||||||
|
engine-progress-rebuildingScripts = Ricostruzione degli script…
|
||||||
|
engine-progress-importingTags = Importazione dei tag…
|
||||||
|
engine-progress-refreshingSemanticIndex = Aggiornamento dell’indice semantico…
|
||||||
|
engine-progress-rebuildComplete = Ricostruzione completata
|
||||||
translationValidation-run = Verifica
|
translationValidation-run = Verifica
|
||||||
translationValidation-running = Verifica delle traduzioni…
|
translationValidation-running = Verifica delle traduzioni…
|
||||||
translationValidation-idle = Avvia la verifica delle traduzioni nel database e nel filesystem.
|
translationValidation-idle = Avvia la verifica delle traduzioni nel database e nel filesystem.
|
||||||
|
|||||||
Reference in New Issue
Block a user